diff --git a/Changelog.md b/Changelog.md index 1b3cc608..45896984 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,7 @@ +# Unreleased + +* Add mutation from `Date.parse` to more strict parsing methods #448 + # v0.8.5 2015-09-11 * Fix misimplementation of block gluing operator that diff --git a/config/flay.yml b/config/flay.yml index b5974848..e5bd15e9 100644 --- a/config/flay.yml +++ b/config/flay.yml @@ -1,3 +1,3 @@ --- threshold: 18 -total_score: 1233 +total_score: 1239 diff --git a/lib/mutant.rb b/lib/mutant.rb index f1c06d73..7b59d43d 100644 --- a/lib/mutant.rb +++ b/lib/mutant.rb @@ -50,6 +50,7 @@ require 'mutant/ast/named_children' require 'mutant/ast/node_predicates' require 'mutant/ast/meta' require 'mutant/ast/meta/send' +require 'mutant/ast/meta/const' require 'mutant/ast/meta/symbol' require 'mutant/ast/meta/optarg' require 'mutant/ast/meta/resbody' diff --git a/lib/mutant/ast/meta/const.rb b/lib/mutant/ast/meta/const.rb new file mode 100644 index 00000000..1ddc286f --- /dev/null +++ b/lib/mutant/ast/meta/const.rb @@ -0,0 +1,24 @@ +module Mutant + module AST + # Node meta information mixin + module Meta + + # Metadata for const nodes + class Const + include NamedChildren, Concord.new(:node), NodePredicates + + children :base, :name + + # Test if AST node is possibly a top level constant + # + # @return [Boolean] + # + # @api private + def possible_top_level? + base.nil? || n_cbase?(base) + end + + end # Const + end # Meta + end # AST +end # Mutant diff --git a/lib/mutant/ast/meta/send.rb b/lib/mutant/ast/meta/send.rb index a30cf25d..007a5859 100644 --- a/lib/mutant/ast/meta/send.rb +++ b/lib/mutant/ast/meta/send.rb @@ -5,7 +5,7 @@ module Mutant # Metadata for send nodes class Send - include NamedChildren, Concord.new(:node) + include NamedChildren, Concord.new(:node), NodePredicates children :receiver, :selector @@ -56,6 +56,17 @@ module Mutant Types::BINARY_METHOD_OPERATORS.include?(selector) end + # Test if receiver is possibly a top level constant + # + # @return [Boolean] + # + # @api private + def receiver_possible_top_level_const? + return false unless receiver && n_const?(receiver) + + Const.new(receiver).possible_top_level? + end + end # Send end # Meta end # AST diff --git a/lib/mutant/mutator/node/send.rb b/lib/mutant/mutator/node/send.rb index 451247e6..4de288e6 100644 --- a/lib/mutant/mutator/node/send.rb +++ b/lib/mutant/mutator/node/send.rb @@ -3,6 +3,7 @@ module Mutant class Node # Namespace for send mutators + # rubocop:disable ClassLength class Send < self include AST::Types @@ -34,6 +35,12 @@ module Mutant :< => %i[== <= eql? equal?] ) + RECEIVER_SELECTOR_REPLACEMENTS = IceNine.deep_freeze( + Date: { + parse: %i[jd civil strptime iso8601 rfc3339 xmlschema rfc2822 rfc822 httpdate jisx0301] + } + ) + private # Perform dispatch @@ -92,10 +99,25 @@ module Mutant emit_selector_replacement emit_const_get_mutation emit_argument_propagation + emit_receiver_selector_mutations mutate_receiver mutate_arguments end + # Emit selector mutations specific to top level constants + # + # @return [undefined] + # + # @api private + def emit_receiver_selector_mutations + return unless meta.receiver_possible_top_level_const? + + RECEIVER_SELECTOR_REPLACEMENTS + .fetch(receiver.children.last, EMPTY_HASH) + .fetch(selector, EMPTY_ARRAY) + .each(&method(:emit_selector)) + end + # Emit mutation from `const_get` to const literal # # @return [undefined] diff --git a/meta/date.rb b/meta/date.rb new file mode 100644 index 00000000..1f0a0ae4 --- /dev/null +++ b/meta/date.rb @@ -0,0 +1,57 @@ +Mutant::Meta::Example.add do + source 'Date.parse(nil)' + + singleton_mutations + mutation 'Date.parse' + mutation 'self.parse(nil)' + mutation 'Date' + mutation 'Date.jd(nil)' + mutation 'Date.civil(nil)' + mutation 'Date.strptime(nil)' + mutation 'Date.iso8601(nil)' + mutation 'Date.rfc3339(nil)' + mutation 'Date.xmlschema(nil)' + mutation 'Date.rfc2822(nil)' + mutation 'Date.rfc822(nil)' + mutation 'Date.httpdate(nil)' + mutation 'Date.jisx0301(nil)' +end + +Mutant::Meta::Example.add do + source '::Date.parse(nil)' + + singleton_mutations + mutation '::Date.parse' + mutation 'Date.parse(nil)' + mutation 'self.parse(nil)' + mutation '::Date' + mutation '::Date.jd(nil)' + mutation '::Date.civil(nil)' + mutation '::Date.strptime(nil)' + mutation '::Date.iso8601(nil)' + mutation '::Date.rfc3339(nil)' + mutation '::Date.xmlschema(nil)' + mutation '::Date.rfc2822(nil)' + mutation '::Date.rfc822(nil)' + mutation '::Date.httpdate(nil)' + mutation '::Date.jisx0301(nil)' +end + +Mutant::Meta::Example.add do + source 'Date.iso8601(nil)' + + singleton_mutations + mutation 'Date.iso8601' + mutation 'self.iso8601(nil)' + mutation 'Date' +end + +Mutant::Meta::Example.add do + source 'Foo::Date.parse(nil)' + + singleton_mutations + mutation 'Foo::Date.parse' + mutation 'Foo::Date' + mutation 'Date.parse(nil)' + mutation 'self.parse(nil)' +end diff --git a/spec/unit/mutant/ast/meta/send/receiver_possible_top_level_const_predicate_spec.rb b/spec/unit/mutant/ast/meta/send/receiver_possible_top_level_const_predicate_spec.rb new file mode 100644 index 00000000..cf9c93e9 --- /dev/null +++ b/spec/unit/mutant/ast/meta/send/receiver_possible_top_level_const_predicate_spec.rb @@ -0,0 +1,37 @@ +RSpec.describe Mutant::AST::Meta::Send, '#receiver_possible_top_level_const?' do + subject { described_class.new(node).receiver_possible_top_level_const? } + + def parse(source) + Parser::CurrentRuby.parse(source) + end + + context 'when implicit top level const' do + let(:node) { parse('Foo.bar') } + + it { should be true } + end + + context 'when cbase' do + let(:node) { parse('::Foo.bar') } + + it { should be true } + end + + context 'when nested const' do + let(:node) { parse('Baz::Foo.bar') } + + it { should be false } + end + + context 'when no receiver' do + let(:node) { parse('bar') } + + it { should be false } + end + + context 'when send receiver' do + let(:node) { parse('foo.bar') } + + it { should be false } + end +end