123456789_123456789_123456789_123456789_123456789_

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
  • Object
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

Instance Attribute Summary

Instance Method Summary

MethodCollector - Included

#visit_class_node

Track class/module nesting so method tuples carry the lexical class name.

#visit_def_node

def name(...) and def self.name(...) both produce DefNode.

#visit_module_node,
#constant_name

Render a constant path (e.g., Foo::Bar) as its source-form string.

#with_class

simplecov:enable.

Constructor Details

.newVisitor

[ GitHub ]

  
# 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).

[ GitHub ]

  
# 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

[ GitHub ]

  
# 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.

[ GitHub ]

  
# 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.

[ GitHub ]

  
# 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.

[ GitHub ]

  
# 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

#emit_safe_navigation(node) (private)

[ GitHub ]

  
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 103

def emit_safe_navigation(node)
  loc = node.location
  @branches[build_tuple(:"&.", loc)] = {
    build_tuple(:then, loc) => 0,
    build_tuple(:else, 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).

[ GitHub ]

  
# 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)
  emit_safe_navigation(node) if node.respond_to?(:safe_navigation?) && node.safe_navigation?
  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.

[ GitHub ]

  
# 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.

[ GitHub ]

  
# 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).

[ GitHub ]

  
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 79

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