diff --git a/docs/config/index.md b/docs/config/index.md
index d62f1ca06f55..fc4de4cb9eaf 100644
--- a/docs/config/index.md
+++ b/docs/config/index.md
@@ -1313,6 +1313,18 @@ Generate coverage report even when tests fail.
Collect coverage of files outside the [project `root`](#root).
+#### coverage.excludeAfterRemap 2.1.0 {#coverage-exclude-after-remap}
+
+- **Type:** `boolean`
+- **Default:** `false`
+- **Available for providers:** `'v8' | 'istanbul'`
+- **CLI:** `--coverage.excludeAfterRemap`, `--coverage.excludeAfterRemap=false`
+
+Apply exclusions again after coverage has been remapped to original sources.
+This is useful when your source files are transpiled and may contain source maps of non-source files.
+
+Use this option when you are seeing files that show up in report even if they match your `coverage.exclude` patterns.
+
#### coverage.skipFull
- **Type:** `boolean`
diff --git a/packages/coverage-istanbul/src/provider.ts b/packages/coverage-istanbul/src/provider.ts
index 98ed3df75bb1..0dd865631633 100644
--- a/packages/coverage-istanbul/src/provider.ts
+++ b/packages/coverage-istanbul/src/provider.ts
@@ -109,9 +109,7 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider implements Co
lines: config.thresholds['100'] ? 100 : config.thresholds.lines,
branches: config.thresholds['100'] ? 100 : config.thresholds.branches,
functions: config.thresholds['100'] ? 100 : config.thresholds.functions,
- statements: config.thresholds['100']
- ? 100
- : config.thresholds.statements,
+ statements: config.thresholds['100'] ? 100 : config.thresholds.statements,
},
}
@@ -292,6 +290,10 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider implements Co
coverageMap.merge(await transformCoverage(uncoveredCoverage))
}
+ if (this.options.excludeAfterRemap) {
+ coverageMap.filter(filename => this.testExclude.shouldInstrument(filename))
+ }
+
return coverageMap
}
diff --git a/packages/coverage-v8/src/provider.ts b/packages/coverage-v8/src/provider.ts
index 532b9d437e08..b6b0f8708b73 100644
--- a/packages/coverage-v8/src/provider.ts
+++ b/packages/coverage-v8/src/provider.ts
@@ -253,6 +253,10 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
coverageMap.merge(await transformCoverage(converted))
}
+ if (this.options.excludeAfterRemap) {
+ coverageMap.filter(filename => this.testExclude.shouldInstrument(filename))
+ }
+
return coverageMap
}
diff --git a/packages/vitest/src/defaults.ts b/packages/vitest/src/defaults.ts
index f95ad627a036..07bc35a37910 100644
--- a/packages/vitest/src/defaults.ts
+++ b/packages/vitest/src/defaults.ts
@@ -75,6 +75,7 @@ export const coverageConfigDefaults: ResolvedCoverageOptions = {
'.marko',
],
allowExternal: false,
+ excludeAfterRemap: false,
ignoreEmptyLines: true,
processingConcurrency: Math.min(
20,
diff --git a/packages/vitest/src/node/types/coverage.ts b/packages/vitest/src/node/types/coverage.ts
index a5d18dd152e2..993ed20cee92 100644
--- a/packages/vitest/src/node/types/coverage.ts
+++ b/packages/vitest/src/node/types/coverage.ts
@@ -251,6 +251,18 @@ export interface BaseCoverageOptions {
*/
allowExternal?: boolean
+ /**
+ * Apply exclusions again after coverage has been remapped to original sources.
+ * This is useful when your source files are transpiled and may contain source maps
+ * of non-source files.
+ *
+ * Use this option when you are seeing files that show up in report even if they
+ * match your `coverage.exclude` patterns.
+ *
+ * @default false
+ */
+ excludeAfterRemap?: boolean
+
/**
* Concurrency limit used when processing the coverage results.
* Defaults to `Math.min(20, os.availableParallelism?.() ?? os.cpus().length)`
diff --git a/test/coverage-test/test/exclude-after-remap.test.ts b/test/coverage-test/test/exclude-after-remap.test.ts
new file mode 100644
index 000000000000..087567484564
--- /dev/null
+++ b/test/coverage-test/test/exclude-after-remap.test.ts
@@ -0,0 +1,71 @@
+import { expect } from 'vitest'
+import { coverageTest, normalizeURL, readCoverageMap, runVitest, test } from '../utils.js'
+import * as transpiled from '../fixtures/src/pre-bundle/bundle.js'
+
+test('{ excludeAfterRemap: true } should exclude files that come up after remapping', async () => {
+ await runVitest({
+ include: [normalizeURL(import.meta.url)],
+ coverage: {
+ include: ['fixtures/src/**'],
+ exclude: ['fixtures/src/pre-bundle/second.ts'],
+ excludeAfterRemap: true,
+ reporter: 'json',
+ all: false,
+ },
+ })
+
+ const coverageMap = await readCoverageMap()
+ const files = coverageMap.files()
+
+ expect(files).toMatchInlineSnapshot(`
+ [
+ "/fixtures/src/pre-bundle/first.ts",
+ ]
+ `)
+})
+
+test('{ excludeAfterRemap: false } should not exclude files that come up after remapping', async () => {
+ await runVitest({
+ include: [normalizeURL(import.meta.url)],
+ coverage: {
+ include: ['fixtures/src/**'],
+ exclude: ['fixtures/src/pre-bundle/second.ts'],
+ reporter: 'json',
+ all: false,
+ },
+ })
+
+ const coverageMap = await readCoverageMap()
+ const files = coverageMap.files()
+
+ expect(files).toMatchInlineSnapshot(`
+ [
+ "/fixtures/src/pre-bundle/first.ts",
+ "/fixtures/src/pre-bundle/second.ts",
+ ]
+ `)
+})
+
+test('{ excludeAfterRemap: true } should exclude uncovered files that come up after remapping', async () => {
+ await runVitest({
+ include: ['fixtures/test/math.test.ts'],
+ coverage: {
+ include: ['fixtures/src/pre-bundle/**'],
+ exclude: ['fixtures/src/pre-bundle/second.ts'],
+ excludeAfterRemap: true,
+ reporter: 'json',
+ all: true,
+ },
+ })
+
+ const coverageMap = await readCoverageMap()
+ const files = coverageMap.files()
+
+ expect(files).contains('/fixtures/src/pre-bundle/first.ts')
+ expect(files).not.contains('/fixtures/src/pre-bundle/second.ts')
+})
+
+coverageTest('run bundled sources', () => {
+ expect(transpiled.first.covered()).toBe('First')
+ expect(transpiled.second.covered()).toBe('Second')
+})