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 like Coverage's, but the numbering order can differ. That's fine: the combiners intern on source span and the report output drops ids, so nothing downstream compares them.

Constant Summary

Constants included from ConditionFolding

ConditionFolding::CONTAINER_CONTENTS_NEED_STATIC_LITERALS, ConditionFolding::ELIMINABLE_READ_TYPES, ConditionFolding::FALSY_CONDITION_TYPES, ConditionFolding::FOLDS_SOURCE_FILE, ConditionFolding::PAREN_OPAQUE_TYPES, ConditionFolding::STATIC_CONDITION_TYPES, ConditionFolding::STATIC_LITERAL_LEAF_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

Prism's Visitor is a stateless dispatch table whose initializer is Object's, so no mutation of the super call can be told apart. It stays for the day that stops being true. mutant:disable



27
28
29
30
31
32
33
34
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 27

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.



21
22
23
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 21

def branches
  @branches
end

#methodsObject (readonly)

Returns the value of attribute methods.



21
22
23
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 21

def methods
  @methods
end

Instance Method Details

#visit_call_node(node) ⇒ Object



69
70
71
72
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 69

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



81
82
83
84
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 81

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

#visit_case_node(node) ⇒ Object

When there's no explicit else, Coverage synthesizes one at the case's range.



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

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

#visit_if_node(node) ⇒ Object

if / unless / postfix / ternary all parse as IfNode (or UnlessNode). Both carry a then arm 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, and so do we.

A folded condition emits no tuple, and only its live arm is descended into: the compiler eliminates the dead arm's entire subtree, so a branch or method nested there would be a phantom no loaded run can produce.



53
54
55
56
57
58
59
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 53

def visit_if_node(node)
  verdict = folded_condition(node.predicate)
  return visit_folded_arms(verdict, node.statements, PrismCompat.subsequent(node)) if verdict

  emit_if_like(node, :if)
  super
end

#visit_match_predicate_node(node) ⇒ Object



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

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

#visit_match_required_node(node) ⇒ Object

One-line pattern matching: x => pattern and x in pattern. Ruby 3.3's Coverage reports these as a :case with an :in and an :else arm; 3.4 dropped them entirely, 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.



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

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

#visit_program_node(node) ⇒ Object

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.



39
40
41
42
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 39

def visit_program_node(node)
  @value_positions = ValuePositions.call(node) if LEGACY_COVERAGE_LOCATIONS
  super
end

#visit_unless_node(node) ⇒ Object



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

def visit_unless_node(node)
  verdict = folded_condition(node.predicate)
  return visit_folded_arms(verdict, PrismCompat.else_clause(node), node.statements) if verdict

  emit_if_like(node, :unless)
  super
end

#visit_until_node(node) ⇒ Object



107
108
109
110
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 107

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

#visit_while_node(node) ⇒ Object

A loop gets a single :body arm and no synthetic else.



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

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