Class: SimpleCov::StaticCoverageExtractor::Visitor
| Relationships & Source Files | |
| Super Chains via Extension / Inclusion / Inheritance | |
|
Class Chain:
self,
Prism::Visitor
|
|
|
Instance Chain:
|
|
| Inherits: |
Prism::Visitor
|
| 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;
available? is the runtime gate.
Constant Summary
LocationConventions - Included
ConditionFolding - Included
Class Method Summary
- .new ⇒ Visitor constructor
Instance Attribute Summary
Instance Method Summary
- #visit_call_node(node)
- #visit_case_match_node(node)
-
#visit_case_node(node)
case/whenandcase/in(pattern matching) parse as CaseNode and CaseMatchNode respectively. -
#visit_if_node(node)
if/unless/ postfix-if / postfix-unless / ternary all parse as IfNode (or UnlessNode). - #visit_match_predicate_node(node)
-
#visit_match_required_node(node)
One-line pattern matching:
x => pattern(MatchRequiredNode) andx in pattern(MatchPredicateNode). -
#visit_program_node(node)
Entry point for a parsed file.
- #visit_unless_node(node)
- #visit_until_node(node)
-
#visit_while_node(node)
while/untilloops get a single:bodyarm. - #build_tuple(type, location) private
-
#emit_case_like(node, when_type)
private
simplecov:enable.
-
#emit_if_like(node, type)
private
IfNode and UnlessNode share a shape (predicate + then body + optional else/elsif) but expose the trailing arm under different accessors.
- #emit_loop(node, type) private
-
#emit_oneline_pattern(node, else_location)
private
simplecov:disable — legacy-only (3.4 emits no branch for one-line patterns).
- #emit_safe_navigation(node) private
ConditionFolding - Included
| #static_condition? | Parentheses are transparent to the fold ( |
| #unwrap_parentheses | |
LocationConventions - Included
| #begin_modifier_loop? |
|
| #case_arm_location | Arm location for a when/in clause: its body statements, or — when the body is empty — the clause's own range on modern Rubies, a point at the pattern's end for a legacy |
| #else_arm_location | Resolve the source range Coverage attributes to a synthetic-or-real |
| #elsif_node?, | |
| #empty_arm_collapses? | Whether an empty then arm collapses to a point at the predicate's end. |
| #empty_else_location | Location of an empty explicit |
| #following_case_content, | |
| #if_like_else_location | Resolve the source range Coverage attributes to a real-or-synthetic |
| #if_like_location | The range Coverage assigns to an if-like node itself. |
| #if_like_subsequent | The |
| #if_like_then_location | Location of the then arm. |
| #legacy_case_tail_end | The last body content in the case after |
| #legacy_content_end | Where an if/elsif chain's content ends, for the legacy range convention: the deepest trailing clause's statements, or that clause's predicate / |
| #legacy_do_while_body_location, #legacy_when_value_location, | |
| #loop_body_location | An empty loop body falls back to the loop's range on modern Rubies and collapses to a point at the predicate's end on legacy ones. |
| #point_at_end, | |
| #safe_navigation_location | Coverage's safe-navigation branch spans the receiver through the end of the call's arguments (or just the message when there are none), but never includes a trailing block: |
| #value_position? | Whether |
MethodCollector - Included
| #visit_class_node | Track class/module nesting so method tuples carry the lexical class name. |
| #visit_def_node |
|
| #visit_module_node, | |
| #constant_name | Render a constant path (e.g., |
| #with_class | simplecov:enable. |
Constructor Details
.new ⇒ Visitor
# 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
#branches (readonly)
[ GitHub ]# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 58
attr_reader :branches, :methods
#methods (readonly)
[ GitHub ]# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 58
attr_reader :branches, :methods
Instance Method Details
#build_tuple(type, location) (private)
[ GitHub ]# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 189
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
#emit_case_like(node, when_type) (private)
simplecov:enable
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 176
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
#emit_if_like(node, type) (private)
IfNode and UnlessNode share a shape (predicate + then body +
optional else/elsif) but expose the trailing arm under different
accessors. if_like_else_location hides that split.
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 150
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
#emit_loop(node, type) (private)
[ GitHub ]# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 184
def emit_loop(node, type) cond_tuple = build_tuple(type, node.location) @branches[cond_tuple] = {build_tuple(:body, loop_body_location(node)) => 0} end
#emit_oneline_pattern(node, else_location) (private)
simplecov:disable — legacy-only (3.4 emits no branch for one-line patterns)
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 168
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
#visit_call_node(node)
[ GitHub ]# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 97
def visit_call_node(node) (node) if node.respond_to?(:) && node. super end
#visit_case_match_node(node)
[ GitHub ]# 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)
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.
# 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)
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.
# 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)
[ GitHub ]# 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)
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
# 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)
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.
# 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)
[ GitHub ]# 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)
[ GitHub ]# 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)
while / until loops get a single :body arm. No synthetic
else (the loop either runs the body or doesn't).
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 135
def visit_while_node(node) emit_loop(node, :while) super end