Class: Mutineer::Mutators::ConditionForcing
- Defined in:
- lib/mutineer/mutators/condition_forcing.rb
Overview
Shared base of the condition-forcing mutators (Tier-2).
Replaces the condition of an if, elsif, unless, ternary, modifier
guard (x if y) or case/in guard with a literal, so its branch always
runs or never runs. ConditionTrue forces true and ConditionFalse
forces false; one mutation per condition each.
A condition that is already true, false or nil, even in
parentheses, is left alone: forcing it changes nothing or repeats the
boolean_literal flip. This holds even when boolean_literal does not run.
A condition that holds a heredoc is left alone too: the heredoc body lies
outside the condition, so it would stay behind as code. A condition that
assigns a variable keeps its code and only its value is forced.
The never-runs side of an else-less conditional can be the same program
as the nil that statement_removal or return_nil puts in place of the
whole conditional. It is still made: dropping it would make the mutants
of one operator depend on which other operators run, and on what the user
suppressed for them. The two then get the same verdict.
condition_negation wraps the condition in !( ... ), which neither
forced value is.
Direct Known Subclasses
Constant Summary collapse
- LITERALS =
Condition nodes that
boolean_literalalready mutates. [Prism::TrueNode, Prism::FalseNode, Prism::NilNode].freeze
Instance Method Summary collapse
-
#force(node) ⇒ void
private
Emits the forced condition unless the condition is a literal or holds a heredoc, whose body would stay behind as code.
-
#replacement(predicate) ⇒ String
private
Returns the forced value for the condition, always in parentheses, so it cannot fuse with a keyword or a
?next to it:x if@abecomesx if(true)and@a?1:2becomes(true)?1:2. -
#unwrap(node) ⇒ Prism::Node
private
Returns the node inside parentheses that hold exactly one expression, so
(true)and((true))read astrue. -
#visit_def_node(node) ⇒ void
Nested method definitions are discovered as their own subjects; do not recurse into them (prevents double-counting their conditions).
-
#visit_if_node(node) ⇒ void
Visits if nodes:
if,elsif, a ternary, a modifierifand an `in ... -
#visit_unless_node(node) ⇒ void
Visits unless nodes:
unless, a modifierunlessand an `in ... -
#writes?(node) ⇒ Boolean
private
Returns whether the node writes a variable or constant anywhere inside it: a local, instance, class or global variable, or a constant, in a plain, compound, multiple or pattern write, or a named regex capture.
Methods inherited from Base
Instance Method Details
#force(node) ⇒ void (private)
This method returns an undefined value.
Emits the forced condition unless the condition is a literal or holds a heredoc, whose body would stay behind as code.
67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/mutineer/mutators/condition_forcing.rb', line 67 def force(node) predicate = node.predicate return if LITERALS.include?(unwrap(predicate).class) || heredoc?(predicate) loc = predicate.location @mutations << Mutation.new( start_offset: loc.start_offset, end_offset: loc.end_offset, replacement: replacement(predicate), operator: self.class::OPERATOR ) end |
#replacement(predicate) ⇒ String (private)
Returns the forced value for the condition, always in parentheses, so it
cannot fuse with a keyword or a ? next to it: x if@a becomes
x if(true) and @a?1:2 becomes (true)?1:2. A condition that assigns
a variable keeps its code, so later reads still see the variable:
(m = x; true).
88 89 90 91 |
# File 'lib/mutineer/mutators/condition_forcing.rb', line 88 def replacement(predicate) value = self.class::VALUE.to_s writes?(predicate) ? "(#{predicate.slice}; #{value})" : "(#{value})" end |
#unwrap(node) ⇒ Prism::Node (private)
Returns the node inside parentheses that hold exactly one expression,
so (true) and ((true)) read as true.
110 111 112 113 114 |
# File 'lib/mutineer/mutators/condition_forcing.rb', line 110 def unwrap(node) body = node.body if node.is_a?(Prism::ParenthesesNode) body = body.body.first if body.is_a?(Prism::StatementsNode) && body.body.size == 1 body && !body.is_a?(Prism::StatementsNode) ? unwrap(body) : node 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 conditions).
58 |
# File 'lib/mutineer/mutators/condition_forcing.rb', line 58 def visit_def_node(node); end |
#visit_if_node(node) ⇒ void
This method returns an undefined value.
Visits if nodes: if, elsif, a ternary, a modifier if and an
in ... if guard.
38 39 40 41 |
# File 'lib/mutineer/mutators/condition_forcing.rb', line 38 def visit_if_node(node) force(node) super end |
#visit_unless_node(node) ⇒ void
This method returns an undefined value.
Visits unless nodes: unless, a modifier unless and an
in ... unless guard.
48 49 50 51 |
# File 'lib/mutineer/mutators/condition_forcing.rb', line 48 def visit_unless_node(node) force(node) super end |
#writes?(node) ⇒ Boolean (private)
Returns whether the node writes a variable or constant anywhere inside it: a local, instance, class or global variable, or a constant, in a plain, compound, multiple or pattern write, or a named regex capture.
99 100 101 102 103 |
# File 'lib/mutineer/mutators/condition_forcing.rb', line 99 def writes?(node) return true if node.type.name.end_with?("_write_node", "_target_node") node.compact_child_nodes.any? { |child| writes?(child) } end |