From fca5b8a1681c6da610014c7e0a599c378980ab57 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Tue, 4 Jun 2013 19:22:33 +0200 Subject: [PATCH] Port method extraction to parser based ast --- Guardfile | 2 +- lib/mutant.rb | 2 + lib/mutant/context/scope.rb | 7 ++-- lib/mutant/loader.rb | 2 +- lib/mutant/matcher/method.rb | 42 +++++++++++++++---- lib/mutant/matcher/method/instance.rb | 10 +++-- lib/mutant/matcher/method/singleton.rb | 25 ++++++----- lib/mutant/node_helpers.rb | 18 ++++++++ spec/shared/method_matcher_behavior.rb | 8 ++-- spec/shared/mutator_behavior.rb | 2 +- spec/spec_helper.rb | 11 +++++ spec/unit/mutant/context/scope/root_spec.rb | 8 ++-- .../loader/eval/class_methods/run_spec.rb | 4 +- .../matcher/method/instance/each_spec.rb | 10 ++--- .../matcher/method/singleton/each_spec.rb | 8 ++-- .../mutator/node/block/mutation_spec.rb | 14 +++---- 16 files changed, 120 insertions(+), 53 deletions(-) create mode 100644 lib/mutant/node_helpers.rb diff --git a/Guardfile b/Guardfile index 1a19fdad..b0ce4ed9 100644 --- a/Guardfile +++ b/Guardfile @@ -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' } diff --git a/lib/mutant.rb b/lib/mutant.rb index 26f4439c..9f6ae075 100644 --- a/lib/mutant.rb +++ b/lib/mutant.rb @@ -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' diff --git a/lib/mutant/context/scope.rb b/lib/mutant/context/scope.rb index fb737455..2e1b8e3a 100644 --- a/lib/mutant/context/scope.rb +++ b/lib/mutant/context/scope.rb @@ -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 diff --git a/lib/mutant/loader.rb b/lib/mutant/loader.rb index b51e44a5..e6607df9 100644 --- a/lib/mutant/loader.rb +++ b/lib/mutant/loader.rb @@ -50,7 +50,7 @@ module Mutant # @api private # def source - ToSource.to_source(@root) + Unparser.unparse(@root) end end # Eval diff --git a/lib/mutant/matcher/method.rb b/lib/mutant/matcher/method.rb index 70b514fb..9f64de36 100644 --- a/lib/mutant/matcher/method.rb +++ b/lib/mutant/matcher/method.rb @@ -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 diff --git a/lib/mutant/matcher/method/instance.rb b/lib/mutant/matcher/method/instance.rb index f20da8e4..d4d67f5b 100644 --- a/lib/mutant/matcher/method/instance.rb +++ b/lib/mutant/matcher/method/instance.rb @@ -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 diff --git a/lib/mutant/matcher/method/singleton.rb b/lib/mutant/matcher/method/singleton.rb index 50567c24..69a9eedc 100644 --- a/lib/mutant/matcher/method/singleton.rb +++ b/lib/mutant/matcher/method/singleton.rb @@ -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 diff --git a/lib/mutant/node_helpers.rb b/lib/mutant/node_helpers.rb new file mode 100644 index 00000000..bfbd6cab --- /dev/null +++ b/lib/mutant/node_helpers.rb @@ -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 diff --git a/spec/shared/method_matcher_behavior.rb b/spec/shared/method_matcher_behavior.rb index ca062614..326c5037 100644 --- a/spec/shared/method_matcher_behavior.rb +++ b/spec/shared/method_matcher_behavior.rb @@ -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 diff --git a/spec/shared/mutator_behavior.rb b/spec/shared/mutator_behavior.rb index ac326581..5c58ae54 100644 --- a/spec/shared/mutator_behavior.rb +++ b/spec/shared/mutator_behavior.rb @@ -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' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 11aec662..b57e408b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -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 diff --git a/spec/unit/mutant/context/scope/root_spec.rb b/spec/unit/mutant/context/scope/root_spec.rb index b1d49553..bee4bbe6 100644 --- a/spec/unit/mutant/context/scope/root_spec.rb +++ b/spec/unit/mutant/context/scope/root_spec.rb @@ -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 diff --git a/spec/unit/mutant/loader/eval/class_methods/run_spec.rb b/spec/unit/mutant/loader/eval/class_methods/run_spec.rb index 47592107..4b764e3a 100644 --- a/spec/unit/mutant/loader/eval/class_methods/run_spec.rb +++ b/spec/unit/mutant/loader/eval/class_methods/run_spec.rb @@ -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 diff --git a/spec/unit/mutant/matcher/method/instance/each_spec.rb b/spec/unit/mutant/matcher/method/instance/each_spec.rb index bbac59f6..023a6297 100644 --- a/spec/unit/mutant/matcher/method/instance/each_spec.rb +++ b/spec/unit/mutant/matcher/method/instance/each_spec.rb @@ -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 diff --git a/spec/unit/mutant/matcher/method/singleton/each_spec.rb b/spec/unit/mutant/matcher/method/singleton/each_spec.rb index 58363eb0..1fd0ca39 100644 --- a/spec/unit/mutant/matcher/method/singleton/each_spec.rb +++ b/spec/unit/mutant/matcher/method/singleton/each_spec.rb @@ -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 diff --git a/spec/unit/mutant/mutator/node/block/mutation_spec.rb b/spec/unit/mutant/mutator/node/block/mutation_spec.rb index 617b52fe..39e55350 100644 --- a/spec/unit/mutant/mutator/node/block/mutation_spec.rb +++ b/spec/unit/mutant/mutator/node/block/mutation_spec.rb @@ -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 = []