Port method extraction to parser based ast
This commit is contained in:
parent
6747e636cd
commit
fca5b8a168
16 changed files with 120 additions and 53 deletions
|
@ -4,7 +4,7 @@ guard :bundler do
|
|||
watch('Gemfile')
|
||||
end
|
||||
|
||||
guard :rspec, :all_on_start => false, :all_after_pass => false do
|
||||
guard :rspec, :cli => '--fail-fast', :all_on_start => false, :all_after_pass => false do
|
||||
# run all specs if the spec_helper or supporting files files are modified
|
||||
watch('spec/spec_helper.rb') { 'spec/unit' }
|
||||
watch(%r{\Aspec/(?:lib|support|shared)/.+\.rb\z}) { 'spec/unit' }
|
||||
|
|
|
@ -8,6 +8,7 @@ require 'equalizer'
|
|||
require 'digest/sha1'
|
||||
require 'inflecto'
|
||||
require 'parser'
|
||||
require 'parser/current'
|
||||
require 'unparser'
|
||||
require 'ice_nine'
|
||||
require 'diff/lcs'
|
||||
|
@ -20,6 +21,7 @@ require 'concord'
|
|||
module Mutant
|
||||
end
|
||||
|
||||
require 'mutant/node_helpers'
|
||||
require 'mutant/singleton_methods'
|
||||
require 'mutant/constants'
|
||||
require 'mutant/support/method_object'
|
||||
|
|
|
@ -3,6 +3,7 @@ module Mutant
|
|||
# Scope context for mutation (Class or Module)
|
||||
class Scope < self
|
||||
include Adamantium::Flat, Equalizer.new(:scope, :source_path)
|
||||
extend NodeHelpers
|
||||
|
||||
# Return AST wrapping mutated node
|
||||
#
|
||||
|
@ -40,12 +41,12 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def self.wrap(scope, node)
|
||||
name = scope.name.split('::').last.to_sym
|
||||
name = s(:const, nil, scope.name.split('::').last.to_sym)
|
||||
case scope
|
||||
when ::Class
|
||||
::Rubinius::AST::Class.new(0, name, nil, node)
|
||||
s(:class, name, nil, node)
|
||||
when ::Module
|
||||
::Rubinius::AST::Module.new(0, name, node)
|
||||
s(:module, name, node)
|
||||
else
|
||||
raise "Cannot wrap scope: #{scope.inspect}"
|
||||
end
|
||||
|
|
|
@ -50,7 +50,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def source
|
||||
ToSource.to_source(@root)
|
||||
Unparser.unparse(@root)
|
||||
end
|
||||
end # Eval
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def ast
|
||||
File.read(source_path).to_ast
|
||||
Parser::CurrentRuby.parse(File.read(source_path))
|
||||
end
|
||||
|
||||
# Return path to source
|
||||
|
@ -128,19 +128,47 @@ module Mutant
|
|||
end
|
||||
memoize :subject
|
||||
|
||||
class Finder
|
||||
def self.run(root, &predicate)
|
||||
new(root, predicate).match
|
||||
end
|
||||
|
||||
private_class_method :new
|
||||
|
||||
attr_reader :match
|
||||
|
||||
private
|
||||
|
||||
def initialize(root, predicate)
|
||||
@root, @predicate = root, predicate
|
||||
test(root)
|
||||
end
|
||||
|
||||
def test(node)
|
||||
if @predicate.call(node)
|
||||
@match = node
|
||||
end
|
||||
|
||||
node.children.each do |child|
|
||||
test(child) if child.kind_of?(Parser::AST::Node)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Return matched node
|
||||
#
|
||||
# @return [Rubinus::AST::Node]
|
||||
# @return [Parser::AST::Node]
|
||||
# if node could be found
|
||||
#
|
||||
# @return [nil]
|
||||
# otherwise
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def matched_node
|
||||
last_match = nil
|
||||
ast.walk do |predicate, node|
|
||||
last_match = node if match?(node)
|
||||
predicate
|
||||
Finder.run(ast) do |node|
|
||||
match?(node)
|
||||
end
|
||||
last_match
|
||||
end
|
||||
|
||||
end # Method
|
||||
|
|
|
@ -16,6 +16,8 @@ module Mutant
|
|||
end
|
||||
memoize :identification
|
||||
|
||||
NAME_INDEX = 0
|
||||
|
||||
private
|
||||
|
||||
# Check if node is matched
|
||||
|
@ -31,9 +33,11 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def match?(node)
|
||||
node.line == source_line &&
|
||||
node.class == Rubinius::AST::Define &&
|
||||
node.name == method_name
|
||||
location = node.location || return
|
||||
expression = location.expression || return
|
||||
expression.line == source_line &&
|
||||
node.type == :def &&
|
||||
node.children[NAME_INDEX] == method_name
|
||||
end
|
||||
|
||||
end # Instance
|
||||
|
|
|
@ -16,6 +16,10 @@ module Mutant
|
|||
end
|
||||
memoize :identification
|
||||
|
||||
RECEIVER_INDEX = 0
|
||||
NAME_INDEX = 1
|
||||
CONST_NAME_INDEX = 1
|
||||
|
||||
private
|
||||
|
||||
# Test for node match
|
||||
|
@ -31,9 +35,8 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def match?(node)
|
||||
node.class == Rubinius::AST::DefineSingleton &&
|
||||
line?(node) &&
|
||||
name?(node) &&
|
||||
line?(node) &&
|
||||
name?(node) &&
|
||||
receiver?(node)
|
||||
end
|
||||
|
||||
|
@ -50,7 +53,8 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def line?(node)
|
||||
node.line == source_line
|
||||
expression = node.location.expression || return
|
||||
expression.line == source_line
|
||||
end
|
||||
|
||||
# Test for name match
|
||||
|
@ -66,7 +70,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def name?(node)
|
||||
node.body.name == method_name
|
||||
node.children[NAME_INDEX] == method_name
|
||||
end
|
||||
|
||||
# Test for receiver match
|
||||
|
@ -82,11 +86,11 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def receiver?(node)
|
||||
receiver = node.receiver
|
||||
case receiver
|
||||
when Rubinius::AST::Self
|
||||
receiver = node.children[RECEIVER_INDEX]
|
||||
case receiver.type
|
||||
when :self
|
||||
true
|
||||
when Rubinius::AST::ConstantAccess
|
||||
when :const
|
||||
receiver_name?(receiver)
|
||||
else
|
||||
$stderr.puts "Unable to find singleton method definition can only match receiver on Rubinius::AST::Self or Rubinius::AST::ConstantAccess, got #{receiver.class}"
|
||||
|
@ -107,7 +111,8 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def receiver_name?(node)
|
||||
node.name.to_s == context.unqualified_name
|
||||
name = node.children[CONST_NAME_INDEX]
|
||||
name.to_s == context.unqualified_name
|
||||
end
|
||||
|
||||
end # Singleton
|
||||
|
|
18
lib/mutant/node_helpers.rb
Normal file
18
lib/mutant/node_helpers.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
module Mutant
|
||||
# Mixin for node helpers
|
||||
module NodeHelpers
|
||||
|
||||
# Build node
|
||||
#
|
||||
# @param [Symbol] type
|
||||
#
|
||||
# @return [Parser::AST::Node]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def s(type, *children)
|
||||
Parser::AST::Node.new(type, children)
|
||||
end
|
||||
|
||||
end # NodeHelpers
|
||||
end # Mutant
|
|
@ -18,18 +18,18 @@ shared_examples_for 'a method matcher' do
|
|||
end
|
||||
|
||||
it 'should have correct line number' do
|
||||
(node.line - base).should eql(method_line)
|
||||
(node.location.expression.line - base).should eql(method_line)
|
||||
end
|
||||
|
||||
it 'should have correct arity' do
|
||||
arguments.required.length.should eql(method_arity)
|
||||
arguments.children.length.should eql(method_arity)
|
||||
end
|
||||
|
||||
it 'should have correct scope in context' do
|
||||
context.send(:scope).should eql(scope)
|
||||
end
|
||||
|
||||
it 'should have the correct node class' do
|
||||
node.should be_a(node_class)
|
||||
it 'should have the correct node type' do
|
||||
node.type.should be(type)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ shared_examples_for 'a mutator' do
|
|||
let(:object) { described_class }
|
||||
|
||||
unless instance_methods.map(&:to_s).include?('node')
|
||||
let(:node) { source.to_ast }
|
||||
let(:node) { parse(source) }
|
||||
end
|
||||
|
||||
it_should_behave_like 'a command method'
|
||||
|
|
|
@ -8,6 +8,17 @@ $: << File.join(TestApp.root,'lib')
|
|||
require 'test_app'
|
||||
require 'mutant'
|
||||
|
||||
module ParserHelper
|
||||
def generate(node)
|
||||
Unparser.unparse(node)
|
||||
end
|
||||
|
||||
def parse(string)
|
||||
Parser::CurrentRuby.parse(string)
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.include(CompressHelper)
|
||||
config.include(ParserHelper)
|
||||
end
|
||||
|
|
|
@ -5,13 +5,13 @@ describe Mutant::Context::Scope, '#root' do
|
|||
|
||||
let(:object) { described_class.new(TestApp::Literal, path) }
|
||||
let(:path) { mock('Path') }
|
||||
let(:node) { ':node'.to_ast }
|
||||
let(:node) { parse(':node') }
|
||||
|
||||
let(:scope) { subject.body }
|
||||
let(:scope_body) { scope.body }
|
||||
|
||||
let(:expected_source) do
|
||||
ToSource.to_source(<<-RUBY.to_ast)
|
||||
generate(parse(<<-RUBY))
|
||||
module TestApp
|
||||
class Literal
|
||||
:node
|
||||
|
@ -21,11 +21,11 @@ describe Mutant::Context::Scope, '#root' do
|
|||
end
|
||||
|
||||
let(:generated_source) do
|
||||
ToSource.to_source(subject)
|
||||
Unparser.unparse(subject)
|
||||
end
|
||||
|
||||
let(:round_tripped_source) do
|
||||
ToSource.to_source(expected_source.to_ast)
|
||||
Unparser.unparse(parse(expected_source))
|
||||
end
|
||||
|
||||
it 'should create correct source' do
|
||||
|
|
|
@ -26,7 +26,7 @@ describe Mutant::Loader::Eval, '.run' do
|
|||
end
|
||||
|
||||
let(:node) do
|
||||
source.to_ast
|
||||
parse(source)
|
||||
end
|
||||
|
||||
it 'should load nodes into vm' do
|
||||
|
@ -36,6 +36,6 @@ describe Mutant::Loader::Eval, '.run' do
|
|||
|
||||
it 'should set file and line correctly' do
|
||||
subject
|
||||
::SomeNamespace::Bar.instance_method(:some_method).source_location.should eql(['test.rb', 3])
|
||||
::SomeNamespace::Bar.instance_method(:some_method).source_location.should eql(['test.rb', 4])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,16 +14,16 @@ describe Mutant::Matcher::Method::Instance, '#each' do
|
|||
|
||||
subject { object.each { |subject| yields << subject } }
|
||||
|
||||
let(:node_class) { Rubinius::AST::Define }
|
||||
let(:method_name) { :bar }
|
||||
let(:method_arity) { 0 }
|
||||
let(:type) { :def }
|
||||
let(:method_name) { :bar }
|
||||
let(:method_arity) { 0 }
|
||||
|
||||
def name
|
||||
node.name
|
||||
node.children[0]
|
||||
end
|
||||
|
||||
def arguments
|
||||
node.arguments
|
||||
node.children[1]
|
||||
end
|
||||
|
||||
context 'when method is defined once' do
|
||||
|
|
|
@ -14,15 +14,15 @@ describe Mutant::Matcher::Method::Singleton, '#each' do
|
|||
|
||||
subject { object.each { |subject| yields << subject } }
|
||||
|
||||
let(:node_class) { Rubinius::AST::DefineSingleton }
|
||||
let(:method_arity) { 0 }
|
||||
let(:type) { :defs }
|
||||
let(:method_arity) { 0 }
|
||||
|
||||
def name
|
||||
node.body.name
|
||||
node.children[1]
|
||||
end
|
||||
|
||||
def arguments
|
||||
node.body.arguments
|
||||
node.children[2]
|
||||
end
|
||||
|
||||
context 'on singleton methods' do
|
||||
|
|
|
@ -9,22 +9,20 @@ describe Mutant::Mutator, 'block' do
|
|||
mutations = []
|
||||
|
||||
# Mutation of each statement in block
|
||||
mutations << "foo\nself.bar".to_ast
|
||||
mutations << "self.foo\nbar".to_ast
|
||||
mutations << "foo\nself.bar"
|
||||
mutations << "self.foo\nbar"
|
||||
|
||||
# Remove statement in block
|
||||
mutations << Rubinius::AST::Block.new(1, ['self.foo'.to_ast])
|
||||
mutations << Rubinius::AST::Block.new(1, ['self.bar'.to_ast])
|
||||
mutations << Rubinius::AST::Block.new(1, ['nil'.to_ast])
|
||||
mutations << 'self.foo'
|
||||
mutations << 'self.bar'
|
||||
mutations << 'nil'
|
||||
end
|
||||
|
||||
it_should_behave_like 'a mutator'
|
||||
end
|
||||
|
||||
|
||||
|
||||
context 'with one statement' do
|
||||
let(:node) { Rubinius::AST::Block.new(1, ['self.foo'.to_ast]) }
|
||||
let(:node) { 'self.foo' }
|
||||
|
||||
let(:mutations) do
|
||||
mutations = []
|
||||
|
|
Loading…
Add table
Reference in a new issue