123456789_123456789_123456789_123456789_123456789_

Module: SimpleCov::StaticCoverageExtractor::ValuePositions

Relationships & Source Files
Defined in: lib/simplecov/static_coverage_extractor/value_position.rb

Overview

Ruby 3.3 value-position analysis for the extractor's legacy branch conventions (see LocationConventions and the #1233 audit).

On Ruby 3.3, the source range Coverage assigns to an EMPTY branch arm depends on whether its construct is in value position — its result is the method's return value — or void position, where the result is discarded. Value position keeps the whole-construct range; void collapses the arm to a point at its header's end. Ruby 3.4 dropped the distinction, so this pass only runs on legacy Rubies.

"Value position" here is narrower than general value-use: it is strictly method-return (tail) position. It reaches a node only through statement tails and if/unless/when arms. Assignments, blocks, lambdas, method arguments, case/in arms, and loop bodies all discard it (Coverage treats their empty arms as void). So .tail_children names the constructs that forward tail position and everything else falls through to the void default.

Class Method Summary

Class Method Details

.call(root) (mod_func)

An identity set (a compare_by_identity Hash used as a set) of the Prism nodes Coverage treats as being in value position.

[ GitHub ]

  
# File 'lib/simplecov/static_coverage_extractor/value_position.rb', line 33

def call(root)
  positions = {}.compare_by_identity #: Hash[Prism::Node, bool]
  mark(root, true, positions)
  positions
end

.else_clause(node) (mod_func)

[ GitHub ]

  
# File 'lib/simplecov/static_coverage_extractor/value_position.rb', line 75

def else_clause(node)
  node.public_send(ELSE_CLAUSE_METHOD)
end

.mark(node, in_value, positions) (mod_func)

[ GitHub ]

  
# File 'lib/simplecov/static_coverage_extractor/value_position.rb', line 39

def mark(node, in_value, positions)
  return unless node.is_a?(::Prism::Node)

  positions[node] = true if in_value
  children = tail_children(node, in_value)
  node.compact_child_nodes.each do |child|
    mark(child, children.any? { |c| c.equal?(child) }, positions)
  end
end

.subsequent(node) (mod_func)

The else/elsif clause of an if-like node, and the else clause of a case, under whichever accessor this Prism version exposes. case/in (CaseMatchNode) is intentionally not a tail construct: its in arms and else both discard tail position.

[ GitHub ]

  
# File 'lib/simplecov/static_coverage_extractor/value_position.rb', line 71

def subsequent(node)
  node.is_a?(::Prism::IfNode) ? node.public_send(IF_NODE_SUBSEQUENT_METHOD) : else_clause(node)
end

.tail_children(node, in_value) (mod_func)

The children of node that inherit its tail position; empty for the void default. A method body is a tail context even when the def itself is not (the method still returns its last expression), so it is included regardless of in_value.

[ GitHub ]

  
# File 'lib/simplecov/static_coverage_extractor/value_position.rb', line 53

def tail_children(node, in_value)
  # A method body is a tail context even when the `def` is not.
  return [node.body] if node.is_a?(::Prism::DefNode)
  return [] unless in_value

  case node
  when ::Prism::StatementsNode then [node.body.last]
  when ::Prism::IfNode, ::Prism::UnlessNode then [node.statements, subsequent(node)]
  when ::Prism::CaseNode then [*node.conditions, else_clause(node)]
  when ::Prism::ElseNode, ::Prism::WhenNode, ::Prism::BeginNode, ::Prism::ProgramNode then [node.statements]
  else []
  end
end