Skip to content

Commit

Permalink
Fix def_node_search's backtrace with better specs for all NodePattern…
Browse files Browse the repository at this point in the history
…::Macros

Note: backtrace was wrong on mingw only.
  • Loading branch information
marcandre committed Jun 14, 2020
1 parent 6dd6d2a commit 421e04d
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 14 deletions.
3 changes: 2 additions & 1 deletion lib/rubocop/ast/node_pattern.rb
Expand Up @@ -784,7 +784,8 @@ def def_node_matcher(method_name, pattern_str)
# yield all descendants which match.
def def_node_search(method_name, pattern_str)
compiler = Compiler.new(pattern_str, 'node')
called_from = caller(1..1).first.split(':')
location = caller_locations(1, 1).first
called_from = [location.path, location.lineno]

if method_name.to_s.end_with?('?')
node_search_first(method_name, compiler, called_from)
Expand Down
199 changes: 186 additions & 13 deletions spec/rubocop/ast/node_pattern_spec.rb
Expand Up @@ -1783,31 +1783,204 @@ def withargs(foo, bar, qux)
end

context 'macros' do
include RuboCop::AST::Sexp

before do
stub_const('MyClass', Class.new do
extend RuboCop::AST::NodePattern::Macros
end)
end

context 'def_node_matcher' do
let(:pattern) { '(sym :hello)' }
let(:method_name) { :my_matcher }
let(:defined_class) do
MyClass.def_node_matcher method_name, pattern
MyClass
let(:method_name) { :my_matcher }
let(:line_no) { __LINE__ + 2 }
let(:defined_class) do
MyClass.public_send helper_name, method_name, pattern
MyClass
end
let(:ruby) { ':hello' }
let(:result) { defined_class.new.send(method_name, node, *params) }

context 'with a pattern without captures' do
let(:pattern) { '(sym _)' }

context 'def_node_matcher' do
let(:helper_name) { :def_node_matcher }

context 'when called on matching code' do
it_behaves_like 'matching'
end

context 'when called on non-matching code' do
let(:ruby) { '"world"' }

it_behaves_like 'nonmatching'
end

context 'when it errors' do
let(:params) { [:extra] }

it 'raises an error with the right location' do
expect { result }.to(raise_error do |err|
expect(err.is_a?(ArgumentError)).to be(true)
expect(err.message).to include('wrong number of arguments')
expect(err.backtrace_locations.first.lineno).to be(line_no)
end)
end
end
end
let(:result) { defined_class.new.send(method_name, node, *params) }

context 'when called on matching code' do
let(:ruby) { ':hello' }
context 'def_node_search' do
let(:helper_name) { :def_node_search }
let(:ruby) { 'foo(:hello, :world)' }

it_behaves_like 'matching'
context('without a predicate name') do
context 'when called on matching code' do
it 'returns an enumerator yielding the matches' do
expect(result.is_a?(Enumerator)).to be(true)
expect(result.to_a).to match_array [s(:sym, :hello), s(:sym, :world)]
end
end

context 'when called on non-matching code' do
let(:ruby) { 'foo("hello", "world")' }

it 'returns an enumerator yielding nothing' do
expect(result.is_a?(Enumerator)).to be(true)
expect(result.to_a).to eq []
end
end

context 'when it errors' do
let(:params) { [:extra] }

it 'raises an error with the right location' do
expect { result }.to(raise_error do |err|
expect(err.is_a?(ArgumentError)).to be(true)
expect(err.message).to include('wrong number of arguments')
expect(err.backtrace_locations.first.lineno).to be(line_no)
end)
end
end
end

context('with a predicate name') do
let(:method_name) { :my_matcher? }

context 'when called on matching code' do
it_behaves_like 'matching'
end

context 'when called on non-matching code' do
let(:ruby) { '"world"' }

it_behaves_like 'nonmatching'
end

context 'when it errors' do
let(:params) { [:extra] }

it 'raises an error with the right location' do
expect { result }.to(raise_error do |err|
expect(err.is_a?(ArgumentError)).to be(true)
expect(err.message).to include('wrong number of arguments')
expect(err.backtrace_locations.first.lineno).to be(line_no)
end)
end
end
end
end
end

context 'when called on non-matching code' do
let(:ruby) { ':world' }
context 'with a pattern with captures' do
let(:pattern) { '(sym $_)' }

it_behaves_like 'nonmatching'
context 'def_node_matcher' do
let(:helper_name) { :def_node_matcher }

context 'when called on matching code' do
let(:captured_val) { :hello }

it_behaves_like 'single capture'
end

context 'when called on non-matching code' do
let(:ruby) { '"world"' }

it_behaves_like 'nonmatching'
end

context 'when it errors' do
let(:params) { [:extra] }

it 'raises an error with the right location' do
expect { result }.to(raise_error do |err|
expect(err.is_a?(ArgumentError)).to be(true)
expect(err.message).to include('wrong number of arguments')
expect(err.backtrace_locations.first.lineno).to be(line_no)
end)
end
end
end

context 'def_node_search' do
let(:helper_name) { :def_node_search }
let(:ruby) { 'foo(:hello, :world)' }

context('without a predicate name') do
context 'when called on matching code' do
it 'returns an enumerator yielding the captures' do
expect(result.is_a?(Enumerator)).to be(true)
expect(result.to_a).to match_array %i[hello world]
end
end

context 'when called on non-matching code' do
let(:ruby) { 'foo("hello", "world")' }

it 'returns an enumerator yielding nothing' do
expect(result.is_a?(Enumerator)).to be(true)
expect(result.to_a).to eq []
end
end

context 'when it errors' do
let(:params) { [:extra] }

it 'raises an error with the right location' do
expect { result }.to(raise_error do |err|
expect(err.is_a?(ArgumentError)).to be(true)
expect(err.message).to include('wrong number of arguments')
expect(err.backtrace_locations.first.lineno).to be(line_no)
end)
end
end
end

context('with a predicate name') do
let(:method_name) { :my_matcher? }

context 'when called on matching code' do
it_behaves_like 'matching'
end

context 'when called on non-matching code' do
let(:ruby) { '"world"' }

it_behaves_like 'nonmatching'
end

context 'when it errors' do
let(:params) { [:extra] }

it 'raises an error with the right location' do
expect { result }.to(raise_error do |err|
expect(err.is_a?(ArgumentError)).to be(true)
expect(err.message).to include('wrong number of arguments')
expect(err.backtrace_locations.first.lineno).to be(line_no)
end)
end
end
end
end
end
end
Expand Down

0 comments on commit 421e04d

Please sign in to comment.