class SimpleCov::StaticCoverageExtractor::Visitor
‘StaticCoverageExtractor.available?` is the runtime gate.
conventional shape. Only defined when Prism is loadable;
the file — `Coverage` uses sequential ids too, so this matches the
shape Ruby’s ‘Coverage` reports. Tuple ids are sequential across
Prism visitor that accumulates branch and method tuples in the
def build_tuple(type, location)
def build_tuple(type, location) id = @next_id @next_id += 1 [type, id, location.start_line, location.start_column, location.end_line, location.end_column] end
def emit_case_like(node, when_type)
def emit_case_like(node, when_type) arms = node.conditions.to_h do |when_node| [build_tuple(when_type, case_arm_location(node, when_node, when_type)), 0] end arms[build_tuple(:else, else_arm_location(node))] = 0 @branches[build_tuple(:case, node.location)] = arms end
def emit_if_like(node, type)
optional else/elsif) but expose the trailing arm under different
IfNode and UnlessNode share a shape (predicate + then body +
def emit_if_like(node, type) then_loc = if_like_then_location(node, type) else_loc = if_like_else_location(node, type) @branches[build_tuple(type, if_like_location(node, type))] = { build_tuple(:then, then_loc) => 0, build_tuple(:else, else_loc) => 0 } end
def emit_loop(node, type)
def emit_loop(node, type) cond_tuple = build_tuple(type, node.location) @branches[cond_tuple] = {build_tuple(:body, loop_body_location(node)) => 0} end
def emit_oneline_pattern(node, else_location)
def emit_oneline_pattern(node, else_location) @branches[build_tuple(:case, node.location)] = { build_tuple(:in, node.pattern.location) => 0, build_tuple(:else, else_location) => 0 } end
def emit_safe_navigation(node)
def emit_safe_navigation(node) loc = safe_navigation_location(node) @branches[build_tuple(:"&.", loc)] = { build_tuple(:then, loc) => 0, build_tuple(:else, loc) => 0 } end
def initialize
def initialize super @branches = {} @methods = {} @next_id = 0 @class_stack = [] @value_positions = nil end
def visit_call_node(node)
def visit_call_node(node) emit_safe_navigation(node) if node.respond_to?(:safe_navigation?) && node.safe_navigation? super end
def visit_case_match_node(node)
def visit_case_match_node(node) emit_case_like(node, :in) super end
def visit_case_node(node)
and CaseMatchNode respectively. When there's no explicit `else`,
`case`/`when` and `case`/`in` (pattern matching) parse as CaseNode
def visit_case_node(node) emit_case_like(node, :when) super end
def visit_if_node(node)
missing, Coverage synthesizes a `:else` arm attributed to the
`else`, another IfNode for `elsif`). When the subsequent is
statements body) and an optional `subsequent` (an ElseNode for
as IfNode (or UnlessNode). Both carry a `then` arm (the
`if` / `unless` / postfix-if / postfix-unless / ternary all parse
def visit_if_node(node) emit_if_like(node, :if) unless static_condition?(node.predicate) super end
def visit_match_predicate_node(node)
def visit_match_predicate_node(node) emit_oneline_pattern(node, node.pattern.location) if LEGACY_COVERAGE_LOCATIONS super end
def visit_match_required_node(node)
`=>` uses the whole expression, `in` uses just the pattern.
differ only in where Coverage anchors the synthesized `:else`:
them entirely (no branch), so this is legacy-only. The two forms
these as a `:case` with an `:in` and an `:else` arm; 3.4 dropped
`x in pattern` (MatchPredicateNode). Ruby 3.3's Coverage reports
One-line pattern matching: `x => pattern` (MatchRequiredNode) and
def visit_match_required_node(node) emit_oneline_pattern(node, node.location) if LEGACY_COVERAGE_LOCATIONS super end
def visit_program_node(node)
emitting anything. Modern Rubies don't need it (see
(tail) position, so precompute that once for the whole tree before
empty branch arm depends on whether its construct is in value
Entry point for a parsed file. On legacy Rubies the location of an
def visit_program_node(node) # simplecov:disable branch — legacy-only arm; unreachable on the modern dogfood Ruby @value_positions = ValuePositions.call(node) if LEGACY_COVERAGE_LOCATIONS # simplecov:enable branch super end
def visit_unless_node(node)
def visit_unless_node(node) emit_if_like(node, :unless) unless static_condition?(node.predicate) super end
def visit_until_node(node)
def visit_until_node(node) emit_loop(node, :until) super end
def visit_while_node(node)
`while` / `until` loops get a single `:body` arm. No synthetic
def visit_while_node(node) emit_loop(node, :while) super end