Class: SimpleCov::StaticCoverageExtractor::Visitor

Inherits:
Prism::Visitor
  • Object
show all
Includes:
ConditionFolding, LocationConventions, MethodCollector
Defined in:
lib/simplecov/static_coverage_extractor/visitor.rb

Overview

Prism visitor that accumulates branch and method tuples in the shape Ruby's Coverage reports. Tuple ids are sequential across the file — Coverage uses sequential ids too, so this matches the conventional shape. Only defined when Prism is loadable; StaticCoverageExtractor.available? is the runtime gate.

Constant Summary

Constants included from ConditionFolding

ConditionFolding::STATIC_CONDITION_TYPES

Constants included from LocationConventions

LocationConventions::LEGACY_COVERAGE_LOCATIONS

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from MethodCollector

#visit_class_node, #visit_def_node, #visit_module_node

Constructor Details

#initializeVisitor

Returns a new instance of Visitor.



60
61
62
63
64
65
66
67
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 60

def initialize
  super
  @branches = {}
  @methods = {}
  @next_id = 0
  @class_stack = []
  @value_positions = nil
end

Instance Attribute Details

#branchesObject (readonly)

Returns the value of attribute branches.



58
59
60
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 58

def branches
  @branches
end

#methodsObject (readonly)

Returns the value of attribute methods.



58
59
60
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 58

def methods
  @methods
end

Instance Method Details

#visit_call_node(node) ⇒ Object



97
98
99
100
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 97

def visit_call_node(node)
  emit_safe_navigation(node) if node.respond_to?(:safe_navigation?) && node.safe_navigation?
  super
end

#visit_case_match_node(node) ⇒ Object



110
111
112
113
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 110

def visit_case_match_node(node)
  emit_case_like(node, :in)
  super
end

#visit_case_node(node) ⇒ Object

case/when and case/in (pattern matching) parse as CaseNode and CaseMatchNode respectively. When there's no explicit else, Coverage synthesizes one at the case's range.



105
106
107
108
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 105

def visit_case_node(node)
  emit_case_like(node, :when)
  super
end

#visit_if_node(node) ⇒ Object

if / unless / postfix-if / postfix-unless / ternary all parse as IfNode (or UnlessNode). Both carry a then arm (the statements body) and an optional subsequent (an ElseNode for else, another IfNode for elsif). When the subsequent is missing, Coverage synthesizes a :else arm attributed to the whole condition's range — we do the same.



87
88
89
90
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 87

def visit_if_node(node)
  emit_if_like(node, :if) unless static_condition?(node.predicate)
  super
end

#visit_match_predicate_node(node) ⇒ Object



127
128
129
130
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 127

def visit_match_predicate_node(node)
  emit_oneline_pattern(node, node.pattern.location) if LEGACY_COVERAGE_LOCATIONS
  super
end

#visit_match_required_node(node) ⇒ Object

One-line pattern matching: x => pattern (MatchRequiredNode) and x in pattern (MatchPredicateNode). Ruby 3.3's Coverage reports these as a :case with an :in and an :else arm; 3.4 dropped them entirely (no branch), so this is legacy-only. The two forms differ only in where Coverage anchors the synthesized :else: => uses the whole expression, in uses just the pattern. simplecov:disable branch — legacy-only arms; unreachable on the modern dogfood Ruby



122
123
124
125
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 122

def visit_match_required_node(node)
  emit_oneline_pattern(node, node.location) if LEGACY_COVERAGE_LOCATIONS
  super
end

#visit_program_node(node) ⇒ Object

Entry point for a parsed file. On legacy Rubies the location of an empty branch arm depends on whether its construct is in value (tail) position, so precompute that once for the whole tree before emitting anything. Modern Rubies don't need it (see LocationConventions), so the pass is skipped there.



74
75
76
77
78
79
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 74

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

#visit_unless_node(node) ⇒ Object



92
93
94
95
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 92

def visit_unless_node(node)
  emit_if_like(node, :unless) unless static_condition?(node.predicate)
  super
end

#visit_until_node(node) ⇒ Object



140
141
142
143
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 140

def visit_until_node(node)
  emit_loop(node, :until)
  super
end

#visit_while_node(node) ⇒ Object

while / until loops get a single :body arm. No synthetic else (the loop either runs the body or doesn't).



135
136
137
138
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 135

def visit_while_node(node)
  emit_loop(node, :while)
  super
end