Fix constant scope mutations are emitted in
This commit is contained in:
parent
4f823e795c
commit
386a2bc2df
9 changed files with 75 additions and 84 deletions
|
@ -8,6 +8,8 @@
|
|||
* [feature] Mutate define and block arguments
|
||||
* [feature] Mutate block arguments, inklusive pattern args
|
||||
* [feature] Recurse into block bodies
|
||||
* [change] Unvendor inflector use mbj-inflector from rubygems
|
||||
* [fixed] Insert mutations at correct constant scope
|
||||
* [fixed] Crash on mutating yield, added a noop for now
|
||||
* [fixed] Crash on singleton methods defined on other than constants or self
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ group :guard do
|
|||
gem 'guard', '~> 1.5.4'
|
||||
gem 'guard-bundler', '~> 1.0.0'
|
||||
gem 'guard-rspec', '~> 2.1.1'
|
||||
gem 'rb-inotify', :git => 'https://github.com/mbj/rb-inotify'
|
||||
end
|
||||
|
||||
group :benchmarks do
|
||||
|
|
|
@ -4,7 +4,7 @@ guard :bundler do
|
|||
watch('Gemfile')
|
||||
end
|
||||
|
||||
guard :rspec, :all_on_start => false do
|
||||
guard :rspec, :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' }
|
||||
|
|
|
@ -2,41 +2,7 @@ module Mutant
|
|||
class Context
|
||||
# Scope context for mutation (Class or Module)
|
||||
class Scope < self
|
||||
include Adamantium::Flat, AbstractType, Equalizer.new(:scope, :source_path)
|
||||
|
||||
# Class context for mutation
|
||||
class Class < self
|
||||
SCOPE_CLASS = Rubinius::AST::ClassScope
|
||||
KEYWORD = 'class'.freeze
|
||||
end
|
||||
|
||||
# Module context for mutation
|
||||
class Module < self
|
||||
SCOPE_CLASS = Rubinius::AST::ModuleScope
|
||||
KEYWORD = 'module'.freeze
|
||||
end
|
||||
|
||||
TABLE = {
|
||||
::Module => Module,
|
||||
::Class => Class
|
||||
}.freeze
|
||||
|
||||
# Build scope context from class or module
|
||||
#
|
||||
# @param [String] source_path
|
||||
#
|
||||
# @param [::Class|::Module] scope
|
||||
#
|
||||
# @return [Context::Scope]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.build(scope, source_path)
|
||||
scope_class = scope.class
|
||||
klass = TABLE.fetch(scope_class) do
|
||||
raise ArgumentError, "Can only build mutation scope from class or module got: #{scope_class.inspect}"
|
||||
end.new(scope, source_path)
|
||||
end
|
||||
include Adamantium::Flat, Equalizer.new(:scope, :source_path)
|
||||
|
||||
# Return AST wrapping mutated node
|
||||
#
|
||||
|
@ -45,12 +11,51 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def root(node)
|
||||
root = root_ast
|
||||
block = Rubinius::AST::Block.new(1, [node])
|
||||
root.body = scope_class.new(1, root.name, block)
|
||||
root
|
||||
nesting.reverse.inject(node) do |current, scope|
|
||||
self.class.wrap(scope, current)
|
||||
end
|
||||
end
|
||||
|
||||
# Wrap node into ast node
|
||||
#
|
||||
# @param [Class, Module] scope
|
||||
# @param [Rubinius::AST::Node] node
|
||||
#
|
||||
# @return [Rubinius::AST::Class]
|
||||
# if scope is of kind Class
|
||||
#
|
||||
# @return [Rubinius::AST::Module]
|
||||
# if scope is of kind module
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.wrap(scope, node)
|
||||
name = scope.name.split('::').last
|
||||
case scope
|
||||
when ::Class
|
||||
::Rubinius::AST::Class.new(0, name.to_sym, nil, node)
|
||||
when ::Module
|
||||
::Rubinius::AST::Module.new(0, name.to_sym, node)
|
||||
else
|
||||
raise "Cannot wrap scope: #{scope.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
# Return nesting
|
||||
#
|
||||
# @return [Enumerable<Class,Module>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def nesting
|
||||
const = ::Object
|
||||
name_nesting.each_with_object([]) do |name, nesting|
|
||||
const = const.const_get(name)
|
||||
nesting << const
|
||||
end
|
||||
end
|
||||
memoize :nesting
|
||||
|
||||
# Return unqualified name of scope
|
||||
#
|
||||
# @return [String]
|
||||
|
|
|
@ -103,7 +103,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def context
|
||||
Context::Scope.build(scope, source_path)
|
||||
Context::Scope.new(scope, source_path)
|
||||
end
|
||||
|
||||
# Return full ast
|
||||
|
|
|
@ -7,11 +7,11 @@ class CodeLoadingSubject
|
|||
end
|
||||
|
||||
describe Mutant, 'code loading' do
|
||||
let(:context) { Mutant::Context::Scope.build(CodeLoadingSubject,"/some/path") }
|
||||
let(:node) { 'def foo; :bar; end'.to_ast }
|
||||
let(:root) { context.root(node) }
|
||||
let(:context) { Mutant::Context::Scope.new(CodeLoadingSubject, "/some/path") }
|
||||
let(:node) { 'def foo; :bar; end'.to_ast }
|
||||
let(:root) { context.root(node) }
|
||||
|
||||
subject { Mutant::Loader::Eval.run(root) }
|
||||
subject { Mutant::Loader::Eval.run(root) }
|
||||
|
||||
before { subject }
|
||||
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Mutant::Context::Scope, '.build' do
|
||||
subject { described_class.build(constant, path) }
|
||||
|
||||
let(:object) { described_class }
|
||||
let(:context) { mock('Context') }
|
||||
let(:path) { mock('Path') }
|
||||
|
||||
context 'when constant is a module' do
|
||||
let(:constant) { Module.new }
|
||||
|
||||
it { should be_kind_of(described_class::Module) }
|
||||
end
|
||||
|
||||
context 'when constant is a class' do
|
||||
let(:constant) { Class.new }
|
||||
|
||||
it { should be_kind_of(described_class::Class) }
|
||||
end
|
||||
|
||||
context 'when constant is not a class nor a module' do
|
||||
let(:constant) { Object.new }
|
||||
|
||||
it 'should raise error' do
|
||||
expect { subject }.to raise_error(ArgumentError, 'Can only build mutation scope from class or module got: Object')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,20 +3,32 @@ require 'spec_helper'
|
|||
describe Mutant::Context::Scope, '#root' do
|
||||
subject { object.root(node) }
|
||||
|
||||
let(:object) { described_class.build(TestApp::Literal, path) }
|
||||
let(:object) { described_class.new(TestApp::Literal, path) }
|
||||
let(:path) { mock('Path') }
|
||||
let(:node) { mock('Node') }
|
||||
let(:node) { ':node'.to_ast }
|
||||
|
||||
let(:scope) { subject.body }
|
||||
let(:scope_body) { scope.body }
|
||||
|
||||
it 'should wrap the ast under constant' do
|
||||
scope.should be_kind_of(Rubinius::AST::ClassScope)
|
||||
let(:expected_source) do
|
||||
ToSource.to_source(<<-RUBY.to_ast)
|
||||
module TestApp
|
||||
class Literal
|
||||
:node
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
|
||||
it 'should place the ast under scope inside of block' do
|
||||
scope_body.should be_a(Rubinius::AST::Block)
|
||||
scope_body.array.should eql([node])
|
||||
scope_body.array.first.should be(node)
|
||||
let(:generated_source) do
|
||||
ToSource.to_source(subject)
|
||||
end
|
||||
|
||||
let(:round_tripped_source) do
|
||||
ToSource.to_source(expected_source.to_ast)
|
||||
end
|
||||
|
||||
it 'should create correct source' do
|
||||
generated_source.should eql(expected_source)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,7 +6,7 @@ describe Mutant::Context::Scope, '#unqualified_name' do
|
|||
let(:path) { mock('Path') }
|
||||
|
||||
context 'with top level constant name' do
|
||||
let(:object) { described_class.build(TestApp, path) }
|
||||
let(:object) { described_class.new(TestApp, path) }
|
||||
|
||||
it 'should return the unqualified name' do
|
||||
should eql('TestApp')
|
||||
|
@ -16,7 +16,7 @@ describe Mutant::Context::Scope, '#unqualified_name' do
|
|||
end
|
||||
|
||||
context 'with scoped constant name' do
|
||||
let(:object) { described_class.build(TestApp::Literal, path) }
|
||||
let(:object) { described_class.new(TestApp::Literal, path) }
|
||||
|
||||
it 'should return the unqualified name' do
|
||||
should eql('Literal')
|
||||
|
|
Loading…
Reference in a new issue