Class: Mutineer::Mutators::ChainLink
- Defined in:
- lib/mutineer/mutators/chain_link.rb
Overview
Chain-link mutator (Tier-2).
Drops one dotted call from a chain of calls, with its arguments and block:
user.account.owner.name becomes user.owner.name and user.account.name.
The chain's receiver and its final call stay, so a chain of n dotted calls
gives at most n - 1 mutations. A chain begins at a receiver that is not a
dotted call: a local, a constant, self, list[i], (a + b). A call in
an argument or a block starts a chain of its own.
The mutant survives when no test tells the chain apart from the same chain without that step: a scope, a filter or a lookup the tests never see.
Links in SKIPPED are never dropped. On a value that already has the
target type or needs no copy, a conversion (name.to_s.strip) or a copy
(list.dup.sort) is a no-op, so dropping it most often makes an
equivalent mutant. Dropping new sends the next call to the class, which
raises (killed by any test that runs the line) or reaches a class method
that builds the instance itself (equivalent); neither says anything about
the tests.
Constant Summary collapse
- SKIPPED =
Method names whose link is never dropped: core conversions and copies, plus
new. %i[ to_s to_str to_sym to_i to_int to_f to_r to_c to_a to_ary to_h to_hash to_proc to_set dup clone freeze itself new ].freeze
Instance Method Summary collapse
-
#chain(top) ⇒ void
private
Walks a final call's receiver chain, recording each link so it is not taken for the final call of a chain of its own, and dropping each link not in SKIPPED.
-
#dotted?(node) ⇒ Boolean
private
Returns whether a node is a call through
.,&.or::. -
#drop(link) ⇒ void
private
Emits the removal of one link: from the end of its receiver to the end of its arguments and block, so a chain split across lines keeps the layout of the lines that remain.
-
#mutations_for(subject, source) ⇒ Array<Mutineer::Mutation>
Resets the per-subject record of calls already placed in a chain.
-
#visit_call_and_write_node(node) ⇒ void
Visits
a.b.c &&= 1. -
#visit_call_node(node) ⇒ void
Visits a call.
-
#visit_call_operator_write_node(node) ⇒ void
Visits
a.b.c += 1, whose final call Prism parses as its own node. -
#visit_call_or_write_node(node) ⇒ void
Visits
a.b.c ||= 1. -
#visit_call_target_node(node) ⇒ void
Visits a call used as an assignment target, as in
a.b.c, d = 1, 2. -
#visit_def_node(node) ⇒ void
Nested method definitions are discovered as their own subjects; do not recurse into them (prevents double-counting their chains).
Methods inherited from Base
Instance Method Details
#chain(top) ⇒ void (private)
This method returns an undefined value.
Walks a final call's receiver chain, recording each link so it is not taken for the final call of a chain of its own, and dropping each link not in SKIPPED.
106 107 108 109 110 111 112 113 |
# File 'lib/mutineer/mutators/chain_link.rb', line 106 def chain(top) link = top.receiver while dotted?(link) @links[link] = true drop(link) unless SKIPPED.include?(link.name) link = link.receiver end end |
#dotted?(node) ⇒ Boolean (private)
Returns whether a node is a call through ., &. or ::.
134 135 136 |
# File 'lib/mutineer/mutators/chain_link.rb', line 134 def dotted?(node) node.is_a?(Prism::CallNode) && !node.call_operator_loc.nil? end |
#drop(link) ⇒ void (private)
This method returns an undefined value.
Emits the removal of one link: from the end of its receiver to the end of its arguments and block, so a chain split across lines keeps the layout of the lines that remain.
121 122 123 124 125 126 127 128 |
# File 'lib/mutineer/mutators/chain_link.rb', line 121 def drop(link) @mutations << Mutation.new( start_offset: link.receiver.location.end_offset, end_offset: link.location.end_offset, replacement: "", operator: :chain_link ) end |
#mutations_for(subject, source) ⇒ Array<Mutineer::Mutation>
Resets the per-subject record of calls already placed in a chain.
40 41 42 43 |
# File 'lib/mutineer/mutators/chain_link.rb', line 40 def mutations_for(subject, source) @links = {}.compare_by_identity super end |
#visit_call_and_write_node(node) ⇒ void
This method returns an undefined value.
Visits a.b.c &&= 1.
77 78 79 80 |
# File 'lib/mutineer/mutators/chain_link.rb', line 77 def visit_call_and_write_node(node) chain(node) super end |
#visit_call_node(node) ⇒ void
This method returns an undefined value.
Visits a call. The outermost dotted call of a chain is its final call; the dotted calls below it in the receiver are its links.
50 51 52 53 |
# File 'lib/mutineer/mutators/chain_link.rb', line 50 def visit_call_node(node) chain(node) if dotted?(node) && !@links.key?(node) super end |
#visit_call_operator_write_node(node) ⇒ void
This method returns an undefined value.
Visits a.b.c += 1, whose final call Prism parses as its own node.
59 60 61 62 |
# File 'lib/mutineer/mutators/chain_link.rb', line 59 def visit_call_operator_write_node(node) chain(node) super end |
#visit_call_or_write_node(node) ⇒ void
This method returns an undefined value.
Visits a.b.c ||= 1.
68 69 70 71 |
# File 'lib/mutineer/mutators/chain_link.rb', line 68 def visit_call_or_write_node(node) chain(node) super end |
#visit_call_target_node(node) ⇒ void
This method returns an undefined value.
Visits a call used as an assignment target, as in a.b.c, d = 1, 2.
86 87 88 89 |
# File 'lib/mutineer/mutators/chain_link.rb', line 86 def visit_call_target_node(node) chain(node) super end |
#visit_def_node(node) ⇒ void
This method returns an undefined value.
Nested method definitions are discovered as their own subjects; do not recurse into them (prevents double-counting their chains).
96 |
# File 'lib/mutineer/mutators/chain_link.rb', line 96 def visit_def_node(node); end |