Fix specification holes in Mutant::AST::Meta

* Reduce implementation
* Fix assumption node specific classes under AST::Meta all have
  #assignable? method. They are intentionalyl not LSP compatible and do
  NOT share the class hierarchy for that reason.
* Inline APIs with one call side
This commit is contained in:
Markus Schirp 2015-06-13 14:20:36 +00:00
parent f066de6778
commit 04a4a44e18
7 changed files with 62 additions and 99 deletions

View file

@ -1,3 +1,3 @@
---
threshold: 18
total_score: 1215
total_score: 1208

View file

@ -3,53 +3,16 @@ module Mutant
# Node meta information mixin
module Meta
REGISTRY = {}
# Return meta for node
#
# @param [Parser::AST::Node] node
#
# @return [Meta]
#
# @api private
#
def self.for(node)
REGISTRY.fetch(node.type, Generic).new(node)
end
# Mixin to define meta nodes
class Node < Module
include Concord.new(:type)
CONCORD = Concord.new(:node)
# Hook called when module gets included
#
# @param [Class, Module] host
#
# @return [undefined]
#
# @api private
#
def included(host)
REGISTRY[type] = host
host.class_eval do
include CONCORD, NamedChildren
end
end
end # Node
# Metadata for resbody nods
class Resbody
include Node.new(:resbody)
include NamedChildren, Concord.new(:node)
children :captures, :assignment, :body
end # Resbody
# Metadata for send nodes
class Send
include Node.new(:send)
include NamedChildren, Concord.new(:node)
children :receiver, :selector
@ -81,7 +44,8 @@ module Mutant
# @api private
#
def attribute_assignment?
arguments.one? && attribute_assignment_selector?
!Types::METHOD_OPERATORS.include?(selector) &&
selector.to_s.end_with?(ATTRIBUTE_ASSIGNMENT_SELECTOR_SUFFIX)
end
# Test if AST node is an index assign
@ -91,7 +55,7 @@ module Mutant
# @api private
#
def index_assignment?
arguments.length.equal?(2) && index_assignment_selector?
selector.equal?(INDEX_ASSIGNMENT_SELECTOR)
end
# Test for binary operator implemented as method
@ -101,49 +65,10 @@ module Mutant
# @api private
#
def binary_method_operator?
arguments.one? && Types::BINARY_METHOD_OPERATORS.include?(selector)
end
private
# Test for index assignment operator
#
# @return [Boolean]
#
# @api private
#
def index_assignment_selector?
selector.equal?(INDEX_ASSIGNMENT_SELECTOR)
end
# Test for attribute assignment selector
#
# @return [Boolean]
#
# @api private
#
def attribute_assignment_selector?
!Types::METHOD_OPERATORS.include?(selector) && selector.to_s.end_with?(ATTRIBUTE_ASSIGNMENT_SELECTOR_SUFFIX)
Types::BINARY_METHOD_OPERATORS.include?(selector)
end
end # Send
# Generic node metadata
class Generic
include Adamantium, Concord.new(:node)
# Test if AST node is a valid assign target
#
# @return [Boolean]
#
# @api private
#
def assignment?
Types::ASSIGNABLE_VARIABLES.include?(node.type)
end
end # Generic
end #
end # Meta
end # AST
end # Mutant

View file

@ -51,17 +51,6 @@ module Mutant
#
alias_method :dup_node, :dup_input
# Return ast meta description
#
# @return [AST::Meta]
#
# @api private
#
def meta
AST::Meta.for(node)
end
memoize :meta
# Return children
#
# @return [Array<Parser::AST::Node>]

View file

@ -22,7 +22,7 @@ module Mutant
emit_right_mutations
return if n_ivasgn?(left)
emit_left_mutations do |node|
AST::Meta.for(node).assignment?
AST::Types::ASSIGNABLE_VARIABLES.include?(node.type)
end
end

View file

@ -37,7 +37,7 @@ module Mutant
rescue_body = children.at(index)
next unless rescue_body
mutate_child(index)
resbody_body = AST::Meta.for(rescue_body).body
resbody_body = AST::Meta::Resbody.new(rescue_body).body
emit_concat(resbody_body) if resbody_body
end
end

View file

@ -55,16 +55,23 @@ module Mutant
# @api private
#
def non_index_dispatch
case
when meta.binary_method_operator?
if meta.binary_method_operator?
run(Binary)
when meta.attribute_assignment?
elsif meta.attribute_assignment?
run(AttributeAssignment)
else
normal_dispatch
end
end
# Return AST metadata for node
#
# @return [AST::Meta::Send]
def meta
AST::Meta::Send.new(node)
end
memoize :meta
# Return arguments
#
# @return [Enumerable<Parser::AST::Node>]

View file

@ -0,0 +1,42 @@
RSpec.describe Mutant::AST::Meta::Send do
let(:object) { described_class.new(node) }
def parse(source)
Parser::CurrentRuby.parse(source)
end
let(:method_call) { parse('foo.bar(baz)') }
let(:attribute_read) { parse('foo.bar') }
let(:index_assignment) { parse('foo[0] = bar') }
let(:attribute_assignment) { parse('foo.bar = baz') }
let(:binary_method_operator) { parse('foo == bar') }
class Expectation
include Adamantium, Anima.new(:name, :assignment, :attribute_assignment, :index_assignment, :binary_method_operator)
ALL = [
[:method_call, false, false, false, false],
[:attribute_read, false, false, false, false],
[:index_assignment, true, false, true, false],
[:attribute_assignment, true, true, false, false],
[:binary_method_operator, false, false, false, true]
].map do |values|
new(Hash[anima.attribute_names.zip(values)])
end.freeze
end
# Rspec should have a build in for this kind of "n-dimensional assertion with context"
(Expectation.anima.attribute_names - %i[name]).each do |name|
describe "##{name}?" do
subject { object.public_send(:"#{name}?") }
Expectation::ALL.each do |expectation|
context "on #{expectation.name}" do
let(:node) { public_send(expectation.name) }
it { should be(expectation.public_send(name)) }
end
end
end
end
end