Module: SimpleCov::StaticCoverageExtractor
| Relationships & Source Files | |
| Namespace Children | |
|
Classes:
| |
| Defined in: | lib/simplecov/static_coverage_extractor.rb, lib/simplecov/static_coverage_extractor/visitor.rb |
Overview
Static enumeration of the branches and methods Ruby's Coverage library
WOULD have reported if a file had been loaded with branches: true /
methods: true. Used by SimulateCoverage to backfill data for files
added via cover / track_files that were never require'd during the
run — so unloaded files contribute to the branch/method denominators
symmetrically with their line coverage, instead of vanishing from the
totals (see #1059).
Implementation uses Prism (stdlib in Ruby 3.3+, gem on older Rubies).
When Prism isn't available, .available? returns false and SimulateCoverage
falls back to the previous behavior — older Rubies keep working, just
without the synthesized data.
The emitted shape mirrors Coverage.result[path] for the same file:
branches are nested as {condition_tuple => {arm_tuple => 0, ...}} and
methods as {["ClassName", . Position info
comes from Prism's reported source locations; it doesn't always match
:name, lines/cols] => 0}Coverage's byte-for-byte (the two parsers report slightly different
column conventions for some constructs), but lines are reliable and
downstream consumers that key off line numbers (the HTML formatter,
SonarQube, etc.) see the data they expect.
Constant Summary
-
IF_NODE_SUBSEQUENT_METHOD =
# File 'lib/simplecov/static_coverage_extractor/visitor.rb', line 13
Prism::IfNode#subsequentwas renamed fromconsequentin Prism 1.3 (Dec 2024). Ruby 3.3's stdlib still ships an older Prism that only exposesconsequent; 3.4+ and any project that's donegem install prismexposessubsequent. Resolve the method name ONCE here so the per-node hot path stays branch-free. The not-taken arm on whichever Prism version we're on can't be exercised by our own dogfood (we only run on one Prism at a time). simplecov:disableif ::Prism::IfNode.method_defined?(:subsequent) :subsequent else :consequent end
Class Attribute Summary
-
.available? ⇒ Boolean
readonly
mod_func
simplecov:disable branch The Prism-unavailable arm of this ternary is unreachable when Prism itself IS loadable — i.e., on every engine that exercises the dogfood report.
Class Method Summary
-
.call(source)
mod_func
Parse
source(a string of Ruby) and return a hash of the form{"branches" => {...}, "methods" => {...}}matching the shape thatCoverage.result[path]produces. -
.real_source_positions(source)
mod_func
Summarize a source file's REAL branch and method positions, for the
:eval_generatedfilter (SimpleCov.ignore_branches /SimpleCov.ignore_methods,#1046).
Class Attribute Details
.available? ⇒ Boolean (readonly, mod_func)
simplecov:disable branch
The Prism-unavailable arm of this ternary is unreachable when Prism
itself IS loadable — i.e., on every engine that exercises the dogfood
report. Asserted-on by callers; tested indirectly via the
available?-returns-false fallback path in SimulateCoverage's spec.
# File 'lib/simplecov/static_coverage_extractor.rb', line 43
def available? defined?(::Prism) ? true : false end
Class Method Details
.call(source) (mod_func)
Parse source (a string of Ruby) and return a hash of the form
{"branches" => {...}, "methods" => {...}} matching the shape that
Coverage.result[path] produces. Returns nil on parse failure or
when Prism isn't available; callers should treat that as "couldn't
extract — fall back to empty hashes."
# File 'lib/simplecov/static_coverage_extractor.rb', line 53
def call(source) # simplecov:disable branch — `then` arm unreachable when Prism IS loadable return nil unless available? # simplecov:enable branch result = ::Prism.parse(source) return nil if result.failure? visitor = Visitor.new visitor.visit(result.value) {"branches" => visitor.branches, "methods" => visitor.methods} rescue StandardError # simplecov:disable line # Parser errors beyond the .failure? check, unsupported AST shapes, # or anything else: fall back to empty hashes rather than crashing # the whole report. Defensive; hard to trigger from a real source # input that Prism accepts at parse time. nil # simplecov:enable line end
.real_source_positions(source) (mod_func)
Summarize a source file's REAL branch and method positions, for the
:eval_generated filter (SimpleCov.ignore_branches /
SimpleCov.ignore_methods, #1046). Returns a hash:
{
branches: Set[start_line, ...], # e.g., [3, 12, 20]
methods: Set[[name, start_line], ...] # e.g., [[:foo, 7], [:bar, 13]]
}
Branch matching is start_line-only because Coverage's condition type
vocabulary (:if, :unless, :case, :while, :until) does not
always match Prism's emitted type (the existing visitor reports
:if for unless and ternary). Coincidental line-sharing between
a real branch and an eval-generated one will keep both, which is
an acceptable false-negative for an opt-in filter. Method matching
uses (name, start_line) since a method name is unique at any line.
Returns nil when Prism is unavailable or parsing fails, signaling callers to keep every Coverage entry (no false drops).
# File 'lib/simplecov/static_coverage_extractor.rb', line 94
def real_source_positions(source) extracted = call(source) return nil unless extracted { branches: extracted["branches"].keys.to_set { |tuple| tuple[2] }, methods: extracted["methods"].keys.to_set { |tuple| [tuple[1], tuple[2]] } } end