Improve internals of method matcher
* Fix name clash with Kernel#method * Match full paths of node enabling next changes to fix exclusion of evaled methods from direct matches
This commit is contained in:
parent
39ff5b772b
commit
bfac03f29d
5 changed files with 68 additions and 29 deletions
|
@ -1,3 +1,3 @@
|
|||
---
|
||||
threshold: 18
|
||||
total_score: 1085
|
||||
total_score: 1094
|
||||
|
|
|
@ -4,7 +4,8 @@ module Mutant
|
|||
|
||||
# Walk all ast nodes
|
||||
#
|
||||
# @param [Parser::AST::Node]
|
||||
# @param [Parser::AST::Node] root
|
||||
# @param [Array<Parser::AST::Node>] stack
|
||||
#
|
||||
# @yield [Parser::AST::Node]
|
||||
# all nodes recursively including root
|
||||
|
@ -13,16 +14,19 @@ module Mutant
|
|||
#
|
||||
# @api private
|
||||
#
|
||||
def self.walk(node, &block)
|
||||
def self.walk(node, stack, &block)
|
||||
raise ArgumentError, 'block expected' unless block_given?
|
||||
|
||||
block.call(node)
|
||||
block.call(node, stack)
|
||||
node.children.grep(Parser::AST::Node).each do |child|
|
||||
walk(child, &block)
|
||||
stack.push(child)
|
||||
walk(child, stack, &block)
|
||||
stack.pop
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
private_class_method :walk
|
||||
|
||||
# Find last node satisfying predicate (as block)
|
||||
#
|
||||
|
@ -39,13 +43,15 @@ module Mutant
|
|||
#
|
||||
# @api private
|
||||
#
|
||||
def self.find_last(node, &predicate)
|
||||
def self.find_last_path(node, &predicate)
|
||||
raise ArgumentError, 'block expected' unless block_given?
|
||||
neddle = nil
|
||||
walk(node) do |candidate|
|
||||
neddle = candidate if predicate.call(candidate, &predicate)
|
||||
path = []
|
||||
walk(node, [node]) do |candidate, stack|
|
||||
if predicate.call(candidate, &predicate)
|
||||
path = stack.dup
|
||||
end
|
||||
end
|
||||
neddle
|
||||
path
|
||||
end
|
||||
|
||||
end # AST
|
||||
|
|
|
@ -2,7 +2,7 @@ module Mutant
|
|||
class Matcher
|
||||
# Matcher for subjects that are a specific method
|
||||
class Method < self
|
||||
include Adamantium::Flat, Concord::Public.new(:env, :scope, :method)
|
||||
include Adamantium::Flat, Concord::Public.new(:env, :scope, :target_method)
|
||||
include Equalizer.new(:identification)
|
||||
|
||||
# Methods within rbx kernel directory are precompiled and their source
|
||||
|
@ -40,7 +40,7 @@ module Mutant
|
|||
def skip?
|
||||
location = source_location
|
||||
if location.nil? || BLACKLIST.match(location.first)
|
||||
env.warn(format('%s does not have valid source location unable to emit matcher', method.inspect))
|
||||
env.warn(format('%s does not have valid source location unable to emit matcher', target_method.inspect))
|
||||
true
|
||||
else
|
||||
false
|
||||
|
@ -54,7 +54,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def method_name
|
||||
method.name
|
||||
target_method.name
|
||||
end
|
||||
|
||||
# Return context
|
||||
|
@ -104,7 +104,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def source_location
|
||||
method.source_location
|
||||
target_method.source_location
|
||||
end
|
||||
|
||||
# Return subject
|
||||
|
@ -118,27 +118,22 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def subject
|
||||
node = matched_node
|
||||
node = matched_node_path.last
|
||||
return unless node
|
||||
self.class::SUBJECT_CLASS.new(env.config, context, node)
|
||||
end
|
||||
memoize :subject
|
||||
|
||||
# Return matched node
|
||||
# Return matched node path
|
||||
#
|
||||
# @return [Parser::AST::Node]
|
||||
# if node could be found
|
||||
#
|
||||
# @return [nil]
|
||||
# otherwise
|
||||
# @return [Array<Parser::AST::Node>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def matched_node
|
||||
AST.find_last(ast) do |node|
|
||||
match?(node)
|
||||
end
|
||||
def matched_node_path
|
||||
AST.find_last_path(ast, &method(:match?))
|
||||
end
|
||||
memoize :matched_node_path
|
||||
|
||||
end # Method
|
||||
end # Matcher
|
||||
|
|
|
@ -15,10 +15,10 @@ module Mutant
|
|||
#
|
||||
# @api private
|
||||
#
|
||||
def self.build(env, scope, method)
|
||||
name = method.name
|
||||
def self.build(env, scope, target_method)
|
||||
name = target_method.name
|
||||
if scope.ancestors.include?(::Memoizable) && scope.memoized?(name)
|
||||
return Memoized.new(env, scope, method)
|
||||
return Memoized.new(env, scope, target_method)
|
||||
end
|
||||
super
|
||||
end
|
||||
|
@ -68,7 +68,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def source_location
|
||||
scope.unmemoized_instance_method(method.name).source_location
|
||||
scope.unmemoized_instance_method(method_name).source_location
|
||||
end
|
||||
|
||||
end # Memoized
|
||||
|
|
38
spec/unit/mutant/ast_spec.rb
Normal file
38
spec/unit/mutant/ast_spec.rb
Normal file
|
@ -0,0 +1,38 @@
|
|||
RSpec.describe Mutant::AST do
|
||||
let(:object) { described_class }
|
||||
|
||||
describe '.find_last_path' do
|
||||
subject { object.find_last_path(root, &block) }
|
||||
|
||||
let(:root) { s(:root, parent) }
|
||||
let(:child_a) { s(:child_a) }
|
||||
let(:child_b) { s(:child_b) }
|
||||
let(:parent) { s(:parent, child_a, child_b) }
|
||||
|
||||
def path
|
||||
subject.map(&:type)
|
||||
end
|
||||
|
||||
context 'when no node matches' do
|
||||
let(:block) { ->(_) { false } }
|
||||
|
||||
it { should eql([]) }
|
||||
end
|
||||
|
||||
context 'when one node matches' do
|
||||
let(:block) { ->(node) { node.equal?(child_a) } }
|
||||
|
||||
it 'returns the full path' do
|
||||
expect(path).to eql([:root, :parent, :child_a])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when two nodes match' do
|
||||
let(:block) { ->(node) { node.equal?(child_a) || node.equal?(child_b) } }
|
||||
|
||||
it 'returns the last full path' do
|
||||
expect(path).to eql([:root, :parent, :child_b])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue