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