Module: SimpleCov::StaticCoverageExtractor

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` for the same file: branches are nested as `=> {arm_tuple => 0, …}` and methods as `:name, lines/cols] => 0`. Position info comes from Prism’s reported source locations; it doesn’t always match ‘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.

Defined Under Namespace

Classes: Visitor

Constant Summary collapse

IF_NODE_SUBSEQUENT_METHOD =

‘Prism::IfNode#subsequent` was renamed from `consequent` in Prism 1.3 (Dec 2024). Ruby 3.3’s stdlib still ships an older Prism that only exposes ‘consequent`; 3.4+ and any project that’s done ‘gem install prism` exposes `subsequent`. 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:disable

if ::Prism::IfNode.method_defined?(:subsequent)
  :subsequent
else
  :consequent
end

Class Method Summary collapse

Class Method Details

.available?Boolean

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.

Returns:

  • (Boolean)


43
44
45
# File 'lib/simplecov/static_coverage_extractor.rb', line 43

def available?
  defined?(::Prism) ? true : false
end

.call(source) ⇒ Object

Parse ‘source` (a string of Ruby) and return a hash of the form `=> {…, “methods” => …}` matching the shape that `Coverage.result` 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.”



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# 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) ⇒ Object

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



94
95
96
97
98
99
100
101
102
# 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