Class: Mutineer::Mutators::RangeLiteral

Inherits:
Base
  • Object
show all
Defined in:
lib/mutineer/mutators/range_literal.rb

Overview

Range mutator (Tier-2).

Swaps .. with ... and ... with .., one mutation per range. The .. -> ... mutant survives when no test checks the last element. Flip-flops are FlipFlopNode, not RangeNode, so this mutator skips them.

Endless ranges (1.., 1..nil) are skipped. (1..) and (1...) give the same result for slicing, include?, ===, size and patterns, so the mutant is equivalent and no normal test can kill it.

Constant Summary collapse

SWAPS =

Maps each range operator to its opposite.

{ ".." => "...", "..." => ".." }.freeze

Instance Method Summary collapse

Methods inherited from Base

#heredoc?, #mutations_for

Instance Method Details

#emit(node) ⇒ void (private)

This method returns an undefined value.

Emits the mutation that swaps the range operator.

Parameters:

  • node (Prism::RangeNode) —

    range node to mutate.



43
44
45
46
47
48
49
50
51
# File 'lib/mutineer/mutators/range_literal.rb', line 43

def emit(node)
  loc = node.operator_loc
  @mutations << Mutation.new(
    start_offset: loc.start_offset,
    end_offset: loc.end_offset,
    replacement: SWAPS.fetch(loc.slice),
    operator: :range
  )
end

#endless?(node) ⇒ Boolean (private)

Whether the range has no end: 1.. or 1..nil.

Parameters:

  • node (Prism::RangeNode) —

    range node to inspect.

Returns:

  • (Boolean)


35
36
37
# File 'lib/mutineer/mutators/range_literal.rb', line 35

def endless?(node)
  node.right.nil? || node.right.is_a?(Prism::NilNode)
end

#visit_range_node(node) ⇒ void

This method returns an undefined value.

Visits range nodes and emits range mutations.

Parameters:

  • node (Prism::RangeNode) —

    range node to inspect.



24
25
26
27
# File 'lib/mutineer/mutators/range_literal.rb', line 24

def visit_range_node(node)
  emit(node) unless endless?(node)
  super # nested ranges ((1..2)...(3..4)) each get their own mutation
end