From 04a4a44e18906271fa235f91bd8ba1ee3aa7f771 Mon Sep 17 00:00:00 2001 From: Markus Schirp Date: Sat, 13 Jun 2015 14:20:36 +0000 Subject: [PATCH] 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 --- config/flay.yml | 2 +- lib/mutant/ast/meta.rb | 89 ++------------------------ lib/mutant/mutator/node.rb | 11 ---- lib/mutant/mutator/node/or_asgn.rb | 2 +- lib/mutant/mutator/node/rescue.rb | 2 +- lib/mutant/mutator/node/send.rb | 13 +++- spec/unit/mutant/ast/meta/send_spec.rb | 42 ++++++++++++ 7 files changed, 62 insertions(+), 99 deletions(-) create mode 100644 spec/unit/mutant/ast/meta/send_spec.rb diff --git a/config/flay.yml b/config/flay.yml index 32453e6b..3c2258be 100644 --- a/config/flay.yml +++ b/config/flay.yml @@ -1,3 +1,3 @@ --- threshold: 18 -total_score: 1215 +total_score: 1208 diff --git a/lib/mutant/ast/meta.rb b/lib/mutant/ast/meta.rb index 423e49fa..af2955a9 100644 --- a/lib/mutant/ast/meta.rb +++ b/lib/mutant/ast/meta.rb @@ -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 diff --git a/lib/mutant/mutator/node.rb b/lib/mutant/mutator/node.rb index 3a9a6a07..6f6cc543 100644 --- a/lib/mutant/mutator/node.rb +++ b/lib/mutant/mutator/node.rb @@ -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] diff --git a/lib/mutant/mutator/node/or_asgn.rb b/lib/mutant/mutator/node/or_asgn.rb index 1c99901e..ee1350f7 100644 --- a/lib/mutant/mutator/node/or_asgn.rb +++ b/lib/mutant/mutator/node/or_asgn.rb @@ -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 diff --git a/lib/mutant/mutator/node/rescue.rb b/lib/mutant/mutator/node/rescue.rb index 812e1a56..fccc441a 100644 --- a/lib/mutant/mutator/node/rescue.rb +++ b/lib/mutant/mutator/node/rescue.rb @@ -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 diff --git a/lib/mutant/mutator/node/send.rb b/lib/mutant/mutator/node/send.rb index 10866f52..d559defb 100644 --- a/lib/mutant/mutator/node/send.rb +++ b/lib/mutant/mutator/node/send.rb @@ -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] diff --git a/spec/unit/mutant/ast/meta/send_spec.rb b/spec/unit/mutant/ast/meta/send_spec.rb new file mode 100644 index 00000000..e49ff501 --- /dev/null +++ b/spec/unit/mutant/ast/meta/send_spec.rb @@ -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