Skip to content

fix(lib-coverage): merge ranges that differ between transforms - #25

Open
stevez wants to merge 1 commit into
vitest-dev:mainfrom
stevez:fix/merge-lenient-locations
Open

stevez wants to merge 1 commit into
vitest-dev:mainfrom
stevez:fix/merge-lenient-locations

Conversation

@stevez

@stevez stevez commented Oct 5, 2026

Copy link
Copy Markdown

Problem

When the same file is covered by multiple transforms, FileCoverage#merge() duplicates statements, functions and branches. For example, Vitest projects with jsdom + browser, or a plain project + one using SWC. Items are matched by their exact start|end location, but transforms can disagree on columns:

  • Statements/branches: the end column differs. SSR transforms produce end.column: null, browser transforms resolve it (1:22->7:null vs 1:22->7:10).
  • Functions: the body range (loc) differs in start column (1:42 vs 1:40 for export function sum(a: number, b: number) {), while the declaration range (decl) is identical. This is why the earlier end-column-only approach in istanbuljs#838 still duplicated functions (review).

Fix

mergeProp keeps exact matching and adds an optional fallback key for items that have no exact match:

  • statements and branches: location ignoring the end column
  • functions: the decl range

Fallback matches are only applied when they're one-to-one. If multiple items share a fallback key, nothing is collapsed and the existing nearest-container logic applies, so no hits are dropped. When matched items differ, the one with a resolved end column is kept.

Tests

New unit tests are built from hand-made coverage objects using locations captured from real Vitest runs, so there's no SWC or third-party plugin dependency:

  • statements with null vs resolved end columns
  • functions whose body start column differs (the math.ts case from the Vitest review)
  • branches with different end columns
  • ambiguous fallback matches are not collapsed
  • identical null-end ranges still merge (the case that crashed istanbuljs#838 earlier)
  • functions without decl don't throw

The 3 merge tests fail without the fix. The full suite passes after pnpm build.

Verified end-to-end

I swapped the built @vitest/istanbul-lib-coverage into Vitest 5.0.3 projects:

  • https://github.com/stevez/vitest-coverage-merge-bug: constants.ts goes from 6 statements to 3
  • A Next.js app with jsdom + browser projects: 2 of 8 shared files had inflated counts (constants.ts 29 → 15, validators.ts 27 → 19). All 8 now match the single-project counts.

Note

This PR was written by Claude (AI agent, Claude Code), working with @stevez. @stevez reviewed the approach and the verification results before it was opened.

🤖 Generated with Claude Code

Coverage of the same file from different transforms (e.g. jsdom vs browser
projects, or different transpilers) can disagree on range columns, which
caused merged reports to contain duplicate statements, functions and branches.

When an exact location match fails, fall back to:
- statements and branches: match ignoring the end column
- functions: match by declaration range, as body ranges can differ in start column

Fallback matches are only applied when unambiguous (one-to-one), so distinct
items are never collapsed. Resolved end columns are preferred over `null`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

coverage-v8: Duplicate statements when merging coverage from multiple projects with different environments

1 participant