Class: Mutineer::Mutators::ConditionForcing

Inherits:
Base
  • Object
show all
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

ConditionFalse, ConditionTrue

Constant Summary collapse

LITERALS =

Condition nodes that boolean_literal already mutates.

[Prism::TrueNode, Prism::FalseNode, Prism::NilNode].freeze

Instance Method Summary collapse

Methods inherited from Base

#heredoc?, #mutations_for

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.

Parameters:

  • node (Prism::IfNode, Prism::UnlessNode) —

    the conditional.



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).

Parameters:

  • predicate (Prism::Node) —

    the condition.

Returns:

  • (String) —

    the replacement source.



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.

Parameters:

  • node (Prism::Node) —

    the condition.

Returns:

  • (Prism::Node) —

    the innermost wrapped node, or node itself.



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).

Parameters:

  • node (Prism::DefNode) —

    nested definition node.



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.

Parameters:

  • node (Prism::IfNode) —

    node to inspect.



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.

Parameters:

  • node (Prism::UnlessNode) —

    node to inspect.



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.

Parameters:

  • node (Prism::Node) —

    the node to inspect.

Returns:

  • (Boolean) —

    true when a write or target node is inside.



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