Module: SimpleCov::StaticCoverageExtractor::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 (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 collapse
-
.call(root) ⇒ Object
An identity set (a
compare_by_identityHash used as a set) of the Prism nodes Coverage treats as being in value position. - .else_clause(node) ⇒ Object
- .mark(node, in_value, positions) ⇒ Object
-
.subsequent(node) ⇒ Object
The
else/elsifclause of an if-like node, and theelseclause of a case, under whichever accessor this Prism version exposes. -
.tail_children(node, in_value) ⇒ Object
The children of
nodethat inherit its tail position; empty for the void default.
Class Method Details
.call(root) ⇒ Object
An identity set (a compare_by_identity Hash used as a set) of the
Prism nodes Coverage treats as being in value position.
33 34 35 36 37 38 |
# File 'lib/simplecov/static_coverage_extractor/value_position.rb', line 33 def call(root) positions = {} #: Hash[untyped, bool] positions.compare_by_identity mark(root, true, positions) positions end |
.else_clause(node) ⇒ Object
76 77 78 |
# File 'lib/simplecov/static_coverage_extractor/value_position.rb', line 76 def else_clause(node) node.public_send(ELSE_CLAUSE_METHOD) end |
.mark(node, in_value, positions) ⇒ Object
40 41 42 43 44 45 46 47 48 |
# File 'lib/simplecov/static_coverage_extractor/value_position.rb', line 40 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) ⇒ Object
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.
72 73 74 |
# File 'lib/simplecov/static_coverage_extractor/value_position.rb', line 72 def subsequent(node) node.is_a?(::Prism::IfNode) ? node.public_send(IF_NODE_SUBSEQUENT_METHOD) : else_clause(node) end |
.tail_children(node, in_value) ⇒ Object
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.
54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/simplecov/static_coverage_extractor/value_position.rb', line 54 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 |