diff --git a/changelog/new_add_blocknodefirstlast_argument_helpers.md b/changelog/new_add_blocknodefirstlast_argument_helpers.md new file mode 100644 index 000000000..b79f27810 --- /dev/null +++ b/changelog/new_add_blocknodefirstlast_argument_helpers.md @@ -0,0 +1 @@ +* [#x](https://github.com/rubocop/rubocop-ast/pull/x): Add `BlockNode#{first,last}_argument` helpers. ([@sambostock][]) diff --git a/lib/rubocop/ast/node/block_node.rb b/lib/rubocop/ast/node/block_node.rb index 523690586..4db5d3f74 100644 --- a/lib/rubocop/ast/node/block_node.rb +++ b/lib/rubocop/ast/node/block_node.rb @@ -21,6 +21,24 @@ def send_node node_parts[0] end + # A shorthand for getting the first argument of this block. + # Equivalent to `arguments.first`. + # + # @return [Node, nil] the first argument of this block, + # or `nil` if there are no arguments + def first_argument + arguments[0] + end + + # A shorthand for getting the last argument of this block. + # Equivalent to `arguments.last`. + # + # @return [Node, nil] the last argument of this block, + # or `nil` if there are no arguments + def last_argument + arguments[-1] + end + # The arguments of this block. # Note that if the block has destructured arguments, `arguments` will # return a `mlhs` node, whereas `argument_list` will return only diff --git a/spec/rubocop/ast/block_node_spec.rb b/spec/rubocop/ast/block_node_spec.rb index c40486b1a..d7073d848 100644 --- a/spec/rubocop/ast/block_node_spec.rb +++ b/spec/rubocop/ast/block_node_spec.rb @@ -302,4 +302,84 @@ it { expect(block_node.receiver.source).to eq('foo') } end end + + describe '#first_argument' do + context 'with no arguments' do + let(:source) { 'foo { bar }' } + + it { expect(block_node.first_argument).to be_nil } + end + + context 'with a single literal argument' do + let(:source) { 'foo { |q| bar(q) }' } + + it { expect(block_node.first_argument.source).to eq('q') } + end + + context 'with a single splat argument' do + let(:source) { 'foo { |*q| bar(q) }' } + + it { expect(block_node.first_argument.source).to eq('*q') } + end + + context 'with multiple mixed arguments' do + let(:source) { 'foo { |q, *z| bar(q, z) }' } + + it { expect(block_node.first_argument.source).to eq('q') } + end + + context 'with destructured arguments' do + let(:source) { 'foo { |(q, r), s| bar(q, r, s) }' } + + it { expect(block_node.first_argument.source).to eq('(q, r)') } + end + + context '>= Ruby 2.7', :ruby27 do + context 'using numbered parameters' do + let(:source) { 'foo { _1 }' } + + it { expect(block_node.first_argument).to be_nil } + end + end + end + + describe '#last_argument' do + context 'with no arguments' do + let(:source) { 'foo { bar }' } + + it { expect(block_node.last_argument).to be_nil } + end + + context 'with a single literal argument' do + let(:source) { 'foo { |q| bar(q) }' } + + it { expect(block_node.last_argument.source).to eq('q') } + end + + context 'with a single splat argument' do + let(:source) { 'foo { |*q| bar(q) }' } + + it { expect(block_node.last_argument.source).to eq('*q') } + end + + context 'with multiple mixed arguments' do + let(:source) { 'foo { |q, *z| bar(q, z) }' } + + it { expect(block_node.last_argument.source).to eq('*z') } + end + + context 'with destructured arguments' do + let(:source) { 'foo { |q, (r, s)| bar(q, r, s) }' } + + it { expect(block_node.last_argument.source).to eq('(r, s)') } + end + + context '>= Ruby 2.7', :ruby27 do + context 'using numbered parameters' do + let(:source) { 'foo { _1 }' } + + it { expect(block_node.last_argument).to be_nil } + end + end + end end