Class: SimpleCov::StaticCoverageExtractor::Visitor
| Relationships & Source Files | |
| Super Chains via Extension / Inclusion / Inheritance | |
|
Class Chain:
self,
Prism::Visitor
|
|
|
Instance Chain:
self,
MethodCollector,
Prism::Visitor
|
|
| 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.
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_unless_node(node)
- #visit_until_node(node)
-
#visit_while_node(node)
while/untilloops get a single:bodyarm. -
#arm_location(statements, fallback_location)
private
Body location for an arm.
-
#build_tuple(type, location)
private
simplecov:enable branch.
-
#else_arm_location(node)
private
Resolve the source range Coverage attributes to a synthetic-or-real
:elsearm of a case construct: the body of an explicit else, or the case's full range when no else is present. -
#else_body_of(else_node)
private
simplecov:disable branch The
else_nodefallback is defensive: every Prism node passed in here in practice responds to:statements. - #emit_case_like(node, when_type) private
-
#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_safe_navigation(node) private
-
#if_like_else_location(node)
private
Resolve the source range Coverage attributes to a real-or-synthetic
:elsearm of an if-like construct.
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 35
def initialize super @branches = {} @methods = {} @next_id = 0 @class_stack = [] end
Instance Attribute Details
#branches (readonly)
[ GitHub ]# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 33
attr_reader :branches, :methods
#methods (readonly)
[ GitHub ]# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 33
attr_reader :branches, :methods
Instance Method Details
#arm_location(statements, fallback_location) (private)
Body location for an arm. Prism's statements is a StatementsNode
whose span covers the contained expressions; fall back to the
parent when the arm body is empty (e.g., if cond then end).
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 152
def arm_location(statements, fallback_location) statements&.location || fallback_location end
#build_tuple(type, location) (private)
simplecov:enable branch
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 164
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
#else_arm_location(node) (private)
Resolve the source range Coverage attributes to a synthetic-or-real
:else arm of a case construct: the body of an explicit else,
or the case's full range when no else is present.
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 137
def else_arm_location(node) return node.location unless node.else_clause arm_location(else_body_of(node.else_clause), node.else_clause.location) end
#else_body_of(else_node) (private)
simplecov:disable branch
The else_node fallback is defensive: every Prism node passed
in here in practice responds to :statements.
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 159
def else_body_of(else_node) else_node.respond_to?(:statements) ? else_node.statements : else_node end
#emit_case_like(node, when_type) (private)
[ GitHub ]# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 125
def emit_case_like(node, when_type) arms = node.conditions.to_h do |when_node| loc = arm_location(when_node.statements, when_node.location) [build_tuple(when_type, loc), 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 94
def emit_if_like(node, type) then_loc = arm_location(node.statements, node.location) else_loc = if_like_else_location(node) @branches[build_tuple(type, node.location)] = { 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 143
def emit_loop(node, type) cond_tuple = build_tuple(type, node.location) body_loc = arm_location(node.statements, node.location) @branches[cond_tuple] = {build_tuple(:body, body_loc) => 0} end
#if_like_else_location(node) (private)
Resolve the source range Coverage attributes to a real-or-synthetic
:else arm of an if-like construct. IfNode uses
subsequent / consequent depending on Prism version (resolved
to IF_NODE_SUBSEQUENT_METHOD at load time); UnlessNode uses
else_clause. When neither is present, the synthesized else
inherits the whole condition's range (matches Coverage's
convention).
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 118
def if_like_else_location(node) sub = node.is_a?(::Prism::IfNode) ? node.public_send(IF_NODE_SUBSEQUENT_METHOD) : node.else_clause return node.location unless sub arm_location(else_body_of(sub), sub.location) end
#visit_call_node(node)
[ GitHub ]# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 59
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 72
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 67
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 49
def visit_if_node(node) emit_if_like(node, :if) super end
#visit_unless_node(node)
[ GitHub ]# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 54
def visit_unless_node(node) emit_if_like(node, :unless) super end
#visit_until_node(node)
[ GitHub ]# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 84
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 79
def visit_while_node(node) emit_loop(node, :while) super end