Introduce AST::Meta to externalize semantic analysis
* Add Mutant::AST namespace to hold all AST related data / helpers. * Mutant::AST will be externalized into an ast-meta gem that can be shared with unparser for deduplication. * Over the time the mutators itself will not need to deal with semantic analysis of the AST anymore by themselves. * Move AST analysis for send nodes to AST::Meta * Fix #209
This commit is contained in:
parent
774c7aa50e
commit
13cd04d9be
31 changed files with 418 additions and 225 deletions
|
@ -1,3 +1,3 @@
|
|||
---
|
||||
threshold: 18
|
||||
total_score: 960
|
||||
total_score: 977
|
||||
|
|
|
@ -113,7 +113,7 @@ UncommunicativeMethodName:
|
|||
enabled: true
|
||||
exclude:
|
||||
- Mutant::Mutation#sha1
|
||||
- Mutant::NodeHelpers#s
|
||||
- Mutant::AST::Sexp#s
|
||||
reject:
|
||||
- !ruby/regexp /^[a-z]$/
|
||||
- !ruby/regexp /[0-9]$/
|
||||
|
@ -159,5 +159,5 @@ UtilityFunction:
|
|||
- Mutant::Meta::Example::Verification#format_mutation
|
||||
- Mutant::Mutation::Evil#success?
|
||||
- Mutant::Mutation::Neutral#success?
|
||||
- Mutant::NodeHelpers#s
|
||||
- Mutant::AST::Sexp#s
|
||||
max_helper_calls: 0
|
||||
|
|
|
@ -25,45 +25,8 @@ module Mutant
|
|||
# The frozen empty array used within mutant
|
||||
EMPTY_ARRAY = [].freeze
|
||||
|
||||
symbolset = ->(strings) { strings.map(&:to_sym).to_set.freeze }
|
||||
|
||||
SCOPE_OPERATOR = '::'.freeze
|
||||
|
||||
# Set of nodes that cannot be on the LHS of an assignment
|
||||
NOT_ASSIGNABLE = symbolset.(%w[int float str dstr class module self nil])
|
||||
# Set of op-assign types
|
||||
OP_ASSIGN = symbolset.call(%w[or_asgn and_asgn op_asgn])
|
||||
# Set of node types that are not valid when emitted standalone
|
||||
NOT_STANDALONE = symbolset.(%w[splat restarg block_pass])
|
||||
INDEX_OPERATORS = symbolset.(%w[[] []=])
|
||||
UNARY_METHOD_OPERATORS = symbolset.(%w[~@ +@ -@ !])
|
||||
|
||||
# Operators ruby implementeds as methods
|
||||
METHOD_OPERATORS = symbolset.(%w[
|
||||
<=> === []= [] <= >= == !~ != =~ <<
|
||||
>> ** * % / | ^ & < > + - ~@ +@ -@ !
|
||||
])
|
||||
|
||||
BINARY_METHOD_OPERATORS = (
|
||||
METHOD_OPERATORS - (INDEX_OPERATORS + UNARY_METHOD_OPERATORS)
|
||||
).to_set.freeze
|
||||
|
||||
OPERATOR_METHODS = (
|
||||
METHOD_OPERATORS + INDEX_OPERATORS + UNARY_METHOD_OPERATORS
|
||||
).to_set.freeze
|
||||
|
||||
# Nodes that are NOT handled by mutant.
|
||||
#
|
||||
# not - 1.8 only, mutant does not support 1.8
|
||||
#
|
||||
NODE_BLACKLIST = symbolset.(%w[not])
|
||||
|
||||
# Nodes that are NOT generated by parser but used by mutant / unparser.
|
||||
NODE_EXTRA = symbolset.(%w[empty])
|
||||
|
||||
# All node types mutant handles
|
||||
NODE_TYPES = ((Parser::Meta::NODE_TYPES + NODE_EXTRA) - NODE_BLACKLIST).to_set.freeze
|
||||
|
||||
# Lookup constant for location
|
||||
#
|
||||
# @param [String] location
|
||||
|
@ -116,9 +79,15 @@ module Mutant
|
|||
end # Mutant
|
||||
|
||||
require 'mutant/version'
|
||||
require 'mutant/ast'
|
||||
require 'mutant/ast/sexp'
|
||||
require 'mutant/ast/types'
|
||||
require 'mutant/ast/nodes'
|
||||
require 'mutant/ast/named_children'
|
||||
require 'mutant/ast/node_predicates'
|
||||
require 'mutant/ast/meta'
|
||||
require 'mutant/cache'
|
||||
require 'mutant/delegator'
|
||||
require 'mutant/node_helpers'
|
||||
require 'mutant/warning_filter'
|
||||
require 'mutant/warning_expectation'
|
||||
require 'mutant/walker'
|
||||
|
|
5
lib/mutant/ast.rb
Normal file
5
lib/mutant/ast.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
module Mutant
|
||||
# AST helpers
|
||||
module AST
|
||||
end # AST
|
||||
end # Mutant
|
131
lib/mutant/ast/meta.rb
Normal file
131
lib/mutant/ast/meta.rb
Normal file
|
@ -0,0 +1,131 @@
|
|||
module Mutant
|
||||
module AST
|
||||
# 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
|
||||
|
||||
# Generic metadata for send nodes
|
||||
class Send
|
||||
include Concord.new(:node), NamedChildren
|
||||
|
||||
children :receiver, :selector
|
||||
|
||||
REGISTRY[:send] = self
|
||||
|
||||
INDEX_ASSIGNMENT_SELECTOR = :[]=
|
||||
ATTRIBUTE_ASSIGNMENT_SELECTOR_SUFFIX = '='.freeze
|
||||
|
||||
# Return arguments
|
||||
#
|
||||
# @return [Enumerable<Parser::AST::Node>]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
alias_method :arguments, :remaining_children
|
||||
|
||||
# Test if AST node is a valid assignment target
|
||||
#
|
||||
# @return [Boolean]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def assignment?
|
||||
index_assignment? || attribute_assignment?
|
||||
end
|
||||
|
||||
# Test if AST node is an attribute assignment?
|
||||
#
|
||||
# @return [Boolean]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def attribute_assignment?
|
||||
arguments.one? && attribute_assignment_selector?
|
||||
end
|
||||
|
||||
# Test if AST node is an index assign
|
||||
#
|
||||
# @return [Boolean]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def index_assignment?
|
||||
arguments.length.equal?(2) && index_assignment_selector?
|
||||
end
|
||||
|
||||
# Test for binary operator implemented as method
|
||||
#
|
||||
# @return [Boolean]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def binary_method_operator?
|
||||
arguments.one? && Types::BINARY_METHOD_OPERATORS.include?(selector)
|
||||
end
|
||||
|
||||
# Test if node is part of an mlhs
|
||||
#
|
||||
# @return [Boolean]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def mlhs?
|
||||
(index_assignment_selector? && arguments.one?) || (arguments.empty? && attribute_assignment_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)
|
||||
end
|
||||
|
||||
end # Send
|
||||
|
||||
# Generic node metatada
|
||||
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 # AST
|
||||
end # Mutant
|
98
lib/mutant/ast/named_children.rb
Normal file
98
lib/mutant/ast/named_children.rb
Normal file
|
@ -0,0 +1,98 @@
|
|||
module Mutant
|
||||
module AST
|
||||
|
||||
# Helper methods to define named children
|
||||
module NamedChildren
|
||||
|
||||
# Hook called when module gets included
|
||||
#
|
||||
# @param [Class, Module] host
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.included(host)
|
||||
super
|
||||
host.class_eval do
|
||||
include InstanceMethods
|
||||
extend ClassMethods
|
||||
end
|
||||
end
|
||||
|
||||
# Methods mixed int ot instance level
|
||||
module InstanceMethods
|
||||
|
||||
private
|
||||
|
||||
# Return children
|
||||
#
|
||||
# @return [Array<Parser::AST::Node]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def children
|
||||
node.children
|
||||
end
|
||||
|
||||
end # InstanceMethods
|
||||
|
||||
# Methods mixed in at class level
|
||||
module ClassMethods
|
||||
|
||||
private
|
||||
|
||||
# Define named child
|
||||
#
|
||||
# @param [Symbol] name
|
||||
# @param [Fixnum] index
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def define_named_child(name, index)
|
||||
define_method(name) do
|
||||
children.at(index)
|
||||
end
|
||||
end
|
||||
|
||||
# Define remaining children
|
||||
#
|
||||
# @param [Array<Symbol>] names
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def define_remaining_children(names)
|
||||
define_method(:remaining_children_with_index) do
|
||||
children.each_with_index.drop(names.length)
|
||||
end
|
||||
|
||||
define_method(:remaining_children_indices) do
|
||||
children.each_index.drop(names.length)
|
||||
end
|
||||
|
||||
define_method(:remaining_children) do
|
||||
children.drop(names.length)
|
||||
end
|
||||
end
|
||||
|
||||
# Create name helpers
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def children(*names)
|
||||
names.each_with_index do |name, index|
|
||||
define_named_child(name, index)
|
||||
end
|
||||
define_remaining_children(names)
|
||||
end
|
||||
|
||||
end # ClassMethods
|
||||
end # NamedChildren
|
||||
end # AST
|
||||
end # Mutant
|
19
lib/mutant/ast/node_predicates.rb
Normal file
19
lib/mutant/ast/node_predicates.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
module Mutant
|
||||
module AST
|
||||
# Module for node predicates
|
||||
module NodePredicates
|
||||
|
||||
Types::ALL.each do |type|
|
||||
fail "method: #{type} is already defined" if instance_methods(true).include?(type)
|
||||
|
||||
name = "n_#{type.to_s.sub(/\??\z/, '?')}"
|
||||
|
||||
define_method(name) do |node|
|
||||
node.type.equal?(type)
|
||||
end
|
||||
private name
|
||||
end
|
||||
|
||||
end # NodePredicates
|
||||
end # AST
|
||||
end # Mutant
|
21
lib/mutant/ast/nodes.rb
Normal file
21
lib/mutant/ast/nodes.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Mutant
|
||||
module AST
|
||||
# Singleton nodes
|
||||
module Nodes
|
||||
extend Sexp
|
||||
|
||||
N_NAN = s(:send, s(:float, 0.0), :/, s(:float, 0.0))
|
||||
N_INFINITY = s(:send, s(:float, 1.0), :/, s(:float, 0.0))
|
||||
N_NEGATIVE_INFINITY = s(:send, s(:float, -1.0), :/, s(:float, 0.0))
|
||||
N_RAISE = s(:send, nil, :raise)
|
||||
N_TRUE = s(:true)
|
||||
N_FALSE = s(:false)
|
||||
N_NIL = s(:nil)
|
||||
N_EMPTY = s(:empty)
|
||||
N_SELF = s(:self)
|
||||
N_ZSUPER = s(:zsuper)
|
||||
N_EMPTY_SUPER = s(:super)
|
||||
|
||||
end # Node
|
||||
end # AST
|
||||
end # Mutant
|
34
lib/mutant/ast/sexp.rb
Normal file
34
lib/mutant/ast/sexp.rb
Normal file
|
@ -0,0 +1,34 @@
|
|||
module Mutant
|
||||
module AST
|
||||
# Mixin for node sexp syntax
|
||||
module Sexp
|
||||
|
||||
private
|
||||
|
||||
# Build node
|
||||
#
|
||||
# @param [Symbol] type
|
||||
#
|
||||
# @return [Parser::AST::Node]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def s(type, *children)
|
||||
Parser::AST::Node.new(type, children)
|
||||
end
|
||||
|
||||
# Build a negated boolean node
|
||||
#
|
||||
# @param [Parser::AST::Node] node
|
||||
#
|
||||
# @return [Parser::AST::Node]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def n_not(node)
|
||||
s(:send, node, :!)
|
||||
end
|
||||
|
||||
end # Sexp
|
||||
end # AST
|
||||
end # Mutant
|
48
lib/mutant/ast/types.rb
Normal file
48
lib/mutant/ast/types.rb
Normal file
|
@ -0,0 +1,48 @@
|
|||
module Mutant
|
||||
module AST
|
||||
# Groups of node types
|
||||
module Types
|
||||
symbolset = ->(strings) { strings.map(&:to_sym).to_set.freeze }
|
||||
|
||||
ASSIGNABLE_VARIABLES = symbolset.(%w[ivasgn lvasgn cvasgn gvasgn])
|
||||
|
||||
INDEX_ASSIGN_OPERATOR = :[]=
|
||||
|
||||
# Set of nodes that cannot be on the LHS of an assignment
|
||||
NOT_ASSIGNABLE = symbolset.(%w[int float str dstr class module self nil])
|
||||
|
||||
# Set of op-assign types
|
||||
OP_ASSIGN = symbolset.(%w[or_asgn and_asgn op_asgn])
|
||||
# Set of node types that are not valid when emitted standalone
|
||||
NOT_STANDALONE = symbolset.(%w[splat restarg block_pass])
|
||||
INDEX_OPERATORS = symbolset.(%w[[] []=])
|
||||
UNARY_METHOD_OPERATORS = symbolset.(%w[~@ +@ -@ !])
|
||||
|
||||
# Operators ruby implementeds as methods
|
||||
METHOD_OPERATORS = symbolset.(%w[
|
||||
<=> === []= [] <= >= == !~ != =~ <<
|
||||
>> ** * % / | ^ & < > + - ~@ +@ -@ !
|
||||
])
|
||||
|
||||
BINARY_METHOD_OPERATORS = (
|
||||
METHOD_OPERATORS - (INDEX_OPERATORS + UNARY_METHOD_OPERATORS)
|
||||
).to_set.freeze
|
||||
|
||||
OPERATOR_METHODS = (
|
||||
METHOD_OPERATORS + INDEX_OPERATORS + UNARY_METHOD_OPERATORS
|
||||
).to_set.freeze
|
||||
|
||||
# Nodes that are NOT handled by mutant.
|
||||
#
|
||||
# not - 1.8 only, mutant does not support 1.8
|
||||
#
|
||||
BLACKLIST = symbolset.(%w[not])
|
||||
|
||||
# Nodes that are NOT generated by parser but used by mutant / unparser.
|
||||
EXTRA = symbolset.(%w[empty])
|
||||
|
||||
# All node types mutant handles
|
||||
ALL = ((Parser::Meta::NODE_TYPES + EXTRA) - BLACKLIST).to_set.freeze
|
||||
end # Types
|
||||
end # AST
|
||||
end # Mutant
|
|
@ -4,7 +4,7 @@ module Mutant
|
|||
|
||||
# Comandline parser
|
||||
class CLI
|
||||
include Adamantium::Flat, Equalizer.new(:config), NodeHelpers
|
||||
include Adamantium::Flat, Equalizer.new(:config)
|
||||
|
||||
# Error raised when CLI argv is invalid
|
||||
Error = Class.new(RuntimeError)
|
||||
|
@ -186,7 +186,7 @@ module Mutant
|
|||
@builder.add_subject_ignore(Expression.parse(pattern))
|
||||
end
|
||||
opts.on('--code CODE', 'Scope execution to subjects with CODE') do |code|
|
||||
@builder.add_subject_selector(Morpher.compile(s(:eql, s(:attribute, :code), s(:static, code))))
|
||||
@builder.add_subject_selector(:code, code)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ module Mutant
|
|||
# Scope context for mutation (Class or Module)
|
||||
class Scope < self
|
||||
include Adamantium::Flat, Concord::Public.new(:scope, :source_path)
|
||||
extend NodeHelpers
|
||||
extend AST::Sexp
|
||||
|
||||
NAMESPACE_DELIMITER = '::'.freeze
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ module Mutant
|
|||
|
||||
METHOD_NAME_PATTERN = Regexp.union(
|
||||
/[A-Za-z_][A-Za-z\d_]*[!?=]?/,
|
||||
*OPERATOR_METHODS.map(&:to_s)
|
||||
*AST::Types::OPERATOR_METHODS.map(&:to_s)
|
||||
).freeze
|
||||
|
||||
SCOPE_PATTERN = /#{SCOPE_NAME_PATTERN}(?:#{SCOPE_OPERATOR}#{SCOPE_NAME_PATTERN})*/.freeze
|
||||
|
|
|
@ -2,7 +2,7 @@ module Mutant
|
|||
class Matcher
|
||||
# Builder for complex matchers
|
||||
class Builder
|
||||
include NodeHelpers, Concord.new(:cache)
|
||||
include Concord.new(:cache), AST::Sexp
|
||||
|
||||
# Initalize object
|
||||
#
|
||||
|
@ -40,8 +40,8 @@ module Mutant
|
|||
#
|
||||
# @api private
|
||||
#
|
||||
def add_subject_selector(selector)
|
||||
@subject_selectors << selector
|
||||
def add_subject_selector(attribute, value)
|
||||
@subject_selectors << Morpher.compile(s(:eql, s(:attribute, attribute), s(:static, value)))
|
||||
self
|
||||
end
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
module Mutant
|
||||
# Namespace for mutant metadata
|
||||
module Meta
|
||||
|
||||
require 'mutant/meta/example'
|
||||
require 'mutant/meta/example/dsl'
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ module Mutant
|
|||
|
||||
# Example DSL
|
||||
class DSL
|
||||
include NodeHelpers
|
||||
include AST::Sexp
|
||||
|
||||
# Run DSL on block
|
||||
#
|
||||
|
|
|
@ -5,11 +5,13 @@ module Mutant
|
|||
|
||||
# Abstract base class for node mutators
|
||||
class Node < self
|
||||
include AbstractType, NodeHelpers, Unparser::Constants
|
||||
include AbstractType, Unparser::Constants
|
||||
include AST::NamedChildren, AST::NodePredicates, AST::Sexp, AST::Nodes
|
||||
|
||||
# Define named child
|
||||
# Helper to define a named child
|
||||
#
|
||||
# @param [Parser::AST::Node] node
|
||||
#
|
||||
# @param [Symbol] name
|
||||
# @param [Fixnum] index
|
||||
#
|
||||
# @return [undefined]
|
||||
|
@ -17,9 +19,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def self.define_named_child(name, index)
|
||||
define_method(name) do
|
||||
children.at(index)
|
||||
end
|
||||
super
|
||||
|
||||
define_method("emit_#{name}_mutations") do |&block|
|
||||
mutate_child(index, &block)
|
||||
|
@ -29,43 +29,6 @@ module Mutant
|
|||
emit_child_update(index, node)
|
||||
end
|
||||
end
|
||||
private_class_method :define_named_child
|
||||
|
||||
# Define remaining children
|
||||
#
|
||||
# @param [Array<Symbol>] names
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.define_remaining_children(names)
|
||||
define_method(:remaining_children_with_index) do
|
||||
children.each_with_index.drop(names.length)
|
||||
end
|
||||
|
||||
define_method(:remaining_children_indices) do
|
||||
children.each_index.drop(names.length)
|
||||
end
|
||||
|
||||
define_method(:remaining_children) do
|
||||
children.drop(names.length)
|
||||
end
|
||||
end
|
||||
private_class_method :define_remaining_children
|
||||
|
||||
# Create name helpers
|
||||
#
|
||||
# @return [undefined]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def self.children(*names)
|
||||
names.each_with_index do |name, index|
|
||||
define_named_child(name, index)
|
||||
end
|
||||
define_remaining_children(names)
|
||||
end
|
||||
private_class_method :children
|
||||
|
||||
private
|
||||
|
@ -98,6 +61,15 @@ module Mutant
|
|||
end
|
||||
end
|
||||
|
||||
# Return ast meta description
|
||||
#
|
||||
# @return [AST::Meta]
|
||||
#
|
||||
def meta
|
||||
AST::Meta.for(node)
|
||||
end
|
||||
memoize :meta
|
||||
|
||||
# Return children
|
||||
#
|
||||
# @return [Array<Parser::AST::Node>]
|
||||
|
@ -246,7 +218,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def asgn_left?
|
||||
OP_ASSIGN.include?(parent_type) && parent.node.children.first.equal?(node)
|
||||
AST::Types::OP_ASSIGN.include?(parent_type) && parent.node.children.first.equal?(node)
|
||||
end
|
||||
|
||||
end # Node
|
||||
|
|
|
@ -22,7 +22,7 @@ module Mutant
|
|||
emit_right_mutations
|
||||
return if n_ivasgn?(left)
|
||||
emit_left_mutations do |node|
|
||||
!n_self?(node)
|
||||
AST::Meta.for(node).assignment?
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ module Mutant
|
|||
|
||||
# Namespace for send mutators
|
||||
class Send < self
|
||||
include AST::Types
|
||||
|
||||
handle(:send)
|
||||
|
||||
|
@ -19,12 +20,6 @@ module Mutant
|
|||
:== => [:eql?, :equal?]
|
||||
)
|
||||
|
||||
INDEX_REFERENCE = :[]
|
||||
INDEX_ASSIGN = :[]=
|
||||
VARIABLE_ASSIGN = :'='
|
||||
ASSIGNMENT_OPERATORS = [INDEX_ASSIGN, VARIABLE_ASSIGN].to_set.freeze
|
||||
ATTRIBUTE_ASSIGNMENT = /\A[a-z\d_]+=\z/.freeze
|
||||
|
||||
private
|
||||
|
||||
# Perform dispatch
|
||||
|
@ -35,7 +30,7 @@ module Mutant
|
|||
#
|
||||
def dispatch
|
||||
emit_singletons
|
||||
if selector.equal?(INDEX_ASSIGN)
|
||||
if meta.index_assignment?
|
||||
run(Index::Assign)
|
||||
else
|
||||
non_index_dispatch
|
||||
|
@ -50,9 +45,9 @@ module Mutant
|
|||
#
|
||||
def non_index_dispatch
|
||||
case
|
||||
when binary_operator?
|
||||
when meta.binary_method_operator?
|
||||
run(Binary)
|
||||
when attribute_assignment?
|
||||
when meta.attribute_assignment?
|
||||
run(AttributeAssignment)
|
||||
else
|
||||
normal_dispatch
|
||||
|
@ -103,26 +98,6 @@ module Mutant
|
|||
emit(receiver) if receiver && !NOT_ASSIGNABLE.include?(receiver.type)
|
||||
end
|
||||
|
||||
# Test for binary operator
|
||||
#
|
||||
# @return [Boolean]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def binary_operator?
|
||||
arguments.one? && BINARY_METHOD_OPERATORS.include?(selector)
|
||||
end
|
||||
|
||||
# Test for attribute assignment
|
||||
#
|
||||
# @return [Boolean]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def attribute_assignment?
|
||||
arguments.one? && ATTRIBUTE_ASSIGNMENT =~ selector
|
||||
end
|
||||
|
||||
# Mutate arguments
|
||||
#
|
||||
# @return [undefined]
|
||||
|
@ -173,40 +148,10 @@ module Mutant
|
|||
KEYWORDS.include?(selector) ||
|
||||
METHOD_OPERATORS.include?(selector) ||
|
||||
OP_ASSIGN.include?(parent_type) ||
|
||||
attribute_assignment?
|
||||
meta.attribute_assignment?
|
||||
)
|
||||
end
|
||||
|
||||
# Test for assignment
|
||||
#
|
||||
# @return [Boolean]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def assignment?
|
||||
arguments.one? && (ASSIGNMENT_OPERATORS.include?(selector) || attribute_assignment?)
|
||||
end
|
||||
|
||||
# Test if node is part of an mlhs
|
||||
#
|
||||
# @return [Boolean]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def mlhs?
|
||||
assignment? && !arguments?
|
||||
end
|
||||
|
||||
# Test for empty arguments
|
||||
#
|
||||
# @return [Boolean]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def arguments?
|
||||
arguments.any?
|
||||
end
|
||||
|
||||
end # Send
|
||||
end # Node
|
||||
end # Mutator
|
||||
|
|
|
@ -7,9 +7,6 @@ module Mutant
|
|||
|
||||
handle(:super)
|
||||
|
||||
Z_SUPER = NodeHelpers.s(:zsuper)
|
||||
EMPTY_SUPER = NodeHelpers.s(:super)
|
||||
|
||||
private
|
||||
|
||||
# Emit mutations
|
||||
|
@ -20,8 +17,8 @@ module Mutant
|
|||
#
|
||||
def dispatch
|
||||
emit_singletons
|
||||
emit(Z_SUPER)
|
||||
emit(EMPTY_SUPER)
|
||||
emit(N_ZSUPER)
|
||||
emit(N_EMPTY_SUPER)
|
||||
children.each_index do |index|
|
||||
mutate_child(index)
|
||||
delete_child(index)
|
||||
|
|
|
@ -64,7 +64,7 @@ module Mutant
|
|||
# @api private
|
||||
#
|
||||
def self.assert_valid_type(type)
|
||||
unless NODE_TYPES.include?(type) || type.kind_of?(Class)
|
||||
unless AST::Types::ALL.include?(type) || type.kind_of?(Class)
|
||||
raise InvalidTypeError, "invalid type registration: #{type}"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
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
|
||||
module_function :s
|
||||
|
||||
N_NAN = s(:send, s(:float, 0.0), :/, s(:float, 0.0))
|
||||
N_INFINITY = s(:send, s(:float, 1.0), :/, s(:float, 0.0))
|
||||
N_NEGATIVE_INFINITY = s(:send, s(:float, -1.0), :/, s(:float, 0.0))
|
||||
N_RAISE = s(:send, nil, :raise)
|
||||
N_TRUE = s(:true)
|
||||
N_FALSE = s(:false)
|
||||
N_NIL = s(:nil)
|
||||
N_EMPTY = s(:empty)
|
||||
N_SELF = s(:self)
|
||||
|
||||
# Build a negated boolean node
|
||||
#
|
||||
# @param [Parser::AST::Node] node
|
||||
#
|
||||
# @return [Parser::AST::Node]
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
def n_not(node)
|
||||
s(:send, node, :!)
|
||||
end
|
||||
|
||||
NODE_TYPES.each do |type|
|
||||
fail "method: #{type} is already defined" if instance_methods(true).include?(type)
|
||||
|
||||
name = "n_#{type.to_s.sub(/\??\z/, '?')}"
|
||||
|
||||
define_method(name) do |node|
|
||||
node.type.equal?(type)
|
||||
end
|
||||
private name
|
||||
end
|
||||
|
||||
end # NodeHelpers
|
||||
end # Mutant
|
|
@ -39,7 +39,7 @@ module Mutant
|
|||
|
||||
# Mutator for memoized instance methods
|
||||
class Memoized < self
|
||||
include NodeHelpers
|
||||
include AST::Sexp
|
||||
|
||||
# Return source
|
||||
#
|
||||
|
|
|
@ -2,7 +2,7 @@ module Mutant
|
|||
class Zombifier
|
||||
# File containing source beeing zombified
|
||||
class File
|
||||
include NodeHelpers, Adamantium::Flat, Concord::Public.new(:path)
|
||||
include Adamantium::Flat, Concord::Public.new(:path), AST::Sexp
|
||||
|
||||
# Zombify contents of file
|
||||
#
|
||||
|
|
|
@ -22,3 +22,14 @@ Mutant::Meta::Example.add do
|
|||
mutation '@a ||= -1'
|
||||
mutation '@a ||= 2'
|
||||
end
|
||||
|
||||
Mutant::Meta::Example.add do
|
||||
source 'foo[:bar] ||= 1'
|
||||
|
||||
singleton_mutations
|
||||
mutation 'foo[:bar] ||= nil'
|
||||
mutation 'foo[:bar] ||= self'
|
||||
mutation 'foo[:bar] ||= 0'
|
||||
mutation 'foo[:bar] ||= -1'
|
||||
mutation 'foo[:bar] ||= 2'
|
||||
end
|
||||
|
|
|
@ -259,7 +259,7 @@ Mutant::Meta::Example.add do
|
|||
mutation 'self[*bar]'
|
||||
end
|
||||
|
||||
(Mutant::BINARY_METHOD_OPERATORS - [:==, :eql?]).each do |operator|
|
||||
(Mutant::AST::Types::BINARY_METHOD_OPERATORS - [:==, :eql?]).each do |operator|
|
||||
Mutant::Meta::Example.add do
|
||||
source "true #{operator} false"
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ require 'spec_helper'
|
|||
describe do
|
||||
|
||||
specify 'mutant should not crash for any node parser can generate' do
|
||||
Mutant::NODE_TYPES.each do |type|
|
||||
Mutant::Mutator::Registry.lookup(Mutant::NodeHelpers.s(type))
|
||||
Mutant::AST::Types::ALL.each do |type|
|
||||
Mutant::Mutator::Registry.lookup(s(type))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -46,7 +46,8 @@ end
|
|||
RSpec.configure do |config|
|
||||
config.include(CompressHelper)
|
||||
config.include(ParserHelper)
|
||||
config.include(Mutant::NodeHelpers)
|
||||
config.include(Mutant::AST::Sexp)
|
||||
|
||||
config.expect_with :rspec do |rspec|
|
||||
rspec.syntax = :expect
|
||||
end
|
||||
|
|
|
@ -6,7 +6,7 @@ describe Mutant::Mutation do
|
|||
SYMBOL = 'test'.freeze
|
||||
end
|
||||
|
||||
let(:object) { TestMutation.new(mutation_subject, Mutant::NodeHelpers::N_NIL) }
|
||||
let(:object) { TestMutation.new(mutation_subject, Mutant::AST::Nodes::N_NIL) }
|
||||
let(:mutation_subject) { double('Subject', identification: 'subject', source: 'original') }
|
||||
let(:node) { double('Node') }
|
||||
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Mutant::Subject::Method::Instance do
|
||||
include Mutant::NodeHelpers
|
||||
|
||||
let(:object) { described_class.new(context, node) }
|
||||
let(:context) { double }
|
||||
|
||||
|
@ -73,8 +71,6 @@ describe Mutant::Subject::Method::Instance do
|
|||
end
|
||||
|
||||
describe Mutant::Subject::Method::Instance::Memoized do
|
||||
include Mutant::NodeHelpers
|
||||
|
||||
let(:object) { described_class.new(context, node) }
|
||||
let(:context) { double }
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Mutant::Subject::Method::Singleton do
|
||||
include Mutant::NodeHelpers
|
||||
|
||||
let(:object) { described_class.new(context, node) }
|
||||
let(:context) { double }
|
||||
|
|
Loading…
Add table
Reference in a new issue