Module: SimpleCov::StaticCoverageExtractor::ValuePositions

Extended by:
ValuePositions
Included in:
ValuePositions
Defined in:
lib/simplecov/static_coverage_extractor/value_position.rb

Overview

Ruby 3.3 value-position analysis for the extractor's legacy branch conventions (#1233). On 3.3 the source range Coverage assigns to an empty branch arm depends on whether its construct is in value position, where 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.

"Value position" here 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, so tail_children names the constructs that forward tail position and everything else falls through to the void default.

Instance Method Summary collapse

Instance Method Details

#call(root) ⇒ Object

An identity set of the Prism nodes Coverage treats as being in value position. This pass runs natively only on legacy Rubies; elsewhere the specs drive it directly, pinned by the differential tuple-equivalence spec against real Coverage on 3.3.



26
27
28
29
30
31
# File 'lib/simplecov/static_coverage_extractor/value_position.rb', line 26

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

#mark(node, in_value, positions) ⇒ Object



33
34
35
36
37
38
39
40
41
# File 'lib/simplecov/static_coverage_extractor/value_position.rb', line 33

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

#tail_children(node, in_value) ⇒ Object

A method body is a tail context even when the def itself is not, since the method still returns its last expression, so it is included regardless of in_value. case/in is intentionally absent: its in arms and else both discard tail position.



47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/simplecov/static_coverage_extractor/value_position.rb', line 47

def tail_children(node, in_value)
  return [node.body] if node.instance_of?(Prism::DefNode)
  return [] unless in_value

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