diff --git a/docs/guide/workspace.md b/docs/guide/workspace.md index 69e80b79d0fa..8f55882b2c64 100644 --- a/docs/guide/workspace.md +++ b/docs/guide/workspace.md @@ -231,6 +231,4 @@ All configuration options that are not supported inside a project config have @@ -401,7 +402,7 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage const { originalSource } = await this.getSources( filename.href, transformResults, - file => this.ctx.vitenode.transformRequest(file), + transform, ) const coverage = { diff --git a/packages/vitest/src/utils/coverage.ts b/packages/vitest/src/utils/coverage.ts index 4d40855fdd6e..285a2ba59299 100644 --- a/packages/vitest/src/utils/coverage.ts +++ b/packages/vitest/src/utils/coverage.ts @@ -1,6 +1,7 @@ import { relative } from 'pathe' import mm from 'micromatch' import type { CoverageMap } from 'istanbul-lib-coverage' +import type { Vitest } from '../node/core' import type { BaseCoverageOptions, ResolvedCoverageOptions } from '../node/types/coverage' import { resolveCoverageReporters } from '../node/config/resolveConfig' @@ -272,6 +273,37 @@ export class BaseCoverageProvider { return chunks }, []) } + + createUncoveredFileTransformer(ctx: Vitest) { + const servers = [ + ...ctx.projects.map(project => ({ + root: project.config.root, + vitenode: project.vitenode, + })), + // Check core last as it will match all files anyway + { root: ctx.config.root, vitenode: ctx.vitenode }, + ] + + return async function transformFile(filename: string) { + let lastError + + for (const { root, vitenode } of servers) { + if (!filename.startsWith(root)) { + continue + } + + try { + return await vitenode.transformRequest(filename) + } + catch (error) { + lastError = error + } + } + + // All vite-node servers failed to transform the file + throw lastError + } + } } /** diff --git a/test/coverage-test/fixtures/configs/vitest.workspace.multi-transforms.ts b/test/coverage-test/fixtures/configs/vitest.workspace.multi-transforms.ts new file mode 100644 index 000000000000..507ede9cf27c --- /dev/null +++ b/test/coverage-test/fixtures/configs/vitest.workspace.multi-transforms.ts @@ -0,0 +1,78 @@ +import { readFileSync } from "node:fs"; +import { Plugin, defineWorkspace } from "vitest/config"; +import MagicString from "magic-string"; + +export default defineWorkspace([ + // Project that uses its own "root" and custom transform plugin + { + test: { + name: "custom-with-root", + root: "fixtures/workspaces/custom-2", + }, + plugins: [customFilePlugin("2")], + }, + + // Project that cannot transform "*.custom-x" files + { + test: { + name: "normal", + include: ["fixtures/test/math.test.ts"], + }, + }, + + // Project that uses default "root" and has custom transform plugin + { + test: { + name: "custom", + include: ["fixtures/test/custom-1-syntax.test.ts"], + }, + plugins: [customFilePlugin("1")], + }, +]); + +/** + * Plugin for transforming `.custom-1` and/or `.custom-2` files to Javascript + */ +function customFilePlugin(postfix: "1" | "2"): Plugin { + function transform(code: MagicString) { + code.replaceAll( + "", + ` +function covered() { + return "Custom-${postfix} file loaded!" +} + `.trim() + ); + + code.replaceAll( + "", + ` +function uncovered() { + return "This should be uncovered!" +} + `.trim() + ); + + code.replaceAll("", "export default covered()"); + code.replaceAll("", "export default uncovered()"); + } + + return { + name: `custom-${postfix}-file-plugin`, + transform(_, id) { + const filename = id.split("?")[0]; + + if (filename.endsWith(`.custom-${postfix}`)) { + const content = readFileSync(filename, "utf8"); + + const s = new MagicString(content); + transform(s); + + return { + code: s.toString(), + map: s.generateMap({ hires: "boundary" }), + }; + } + }, + }; +} diff --git a/test/coverage-test/fixtures/src/covered.custom-1 b/test/coverage-test/fixtures/src/covered.custom-1 new file mode 100644 index 000000000000..095b52aa44c8 --- /dev/null +++ b/test/coverage-test/fixtures/src/covered.custom-1 @@ -0,0 +1,5 @@ + + + + + diff --git a/test/coverage-test/fixtures/src/uncovered.custom-1 b/test/coverage-test/fixtures/src/uncovered.custom-1 new file mode 100644 index 000000000000..f4e16be2968b --- /dev/null +++ b/test/coverage-test/fixtures/src/uncovered.custom-1 @@ -0,0 +1,3 @@ + + + diff --git a/test/coverage-test/fixtures/test/custom-1-syntax.test.ts b/test/coverage-test/fixtures/test/custom-1-syntax.test.ts new file mode 100644 index 000000000000..6da0aea4b553 --- /dev/null +++ b/test/coverage-test/fixtures/test/custom-1-syntax.test.ts @@ -0,0 +1,8 @@ +import { expect, test } from 'vitest' + +// @ts-expect-error -- untyped +import output from '../src/covered.custom-1' + +test('custom file loads fine', () => { + expect(output).toMatch('Custom-1 file loaded!') +}) diff --git a/test/coverage-test/fixtures/workspaces/custom-2/src/covered.custom-2 b/test/coverage-test/fixtures/workspaces/custom-2/src/covered.custom-2 new file mode 100644 index 000000000000..095b52aa44c8 --- /dev/null +++ b/test/coverage-test/fixtures/workspaces/custom-2/src/covered.custom-2 @@ -0,0 +1,5 @@ + + + + + diff --git a/test/coverage-test/fixtures/workspaces/custom-2/src/uncovered.custom-2 b/test/coverage-test/fixtures/workspaces/custom-2/src/uncovered.custom-2 new file mode 100644 index 000000000000..f4e16be2968b --- /dev/null +++ b/test/coverage-test/fixtures/workspaces/custom-2/src/uncovered.custom-2 @@ -0,0 +1,3 @@ + + + diff --git a/test/coverage-test/fixtures/workspaces/custom-2/test/custom-2-syntax.test.ts b/test/coverage-test/fixtures/workspaces/custom-2/test/custom-2-syntax.test.ts new file mode 100644 index 000000000000..ffc0d6c55b6e --- /dev/null +++ b/test/coverage-test/fixtures/workspaces/custom-2/test/custom-2-syntax.test.ts @@ -0,0 +1,8 @@ +import { expect, test } from 'vitest' + +// @ts-expect-error -- untyped +import output from '../src/covered.custom-2' + +test('custom-2 file loads fine', () => { + expect(output).toMatch('Custom-2 file loaded!') +}) diff --git a/test/coverage-test/test/__snapshots__/custom-file-covered-1-istanbul.snapshot.json b/test/coverage-test/test/__snapshots__/custom-file-covered-1-istanbul.snapshot.json new file mode 100644 index 000000000000..8ebdab27587b --- /dev/null +++ b/test/coverage-test/test/__snapshots__/custom-file-covered-1-istanbul.snapshot.json @@ -0,0 +1,83 @@ +{ + "path": "/fixtures/src/covered.custom-1", + "statementMap": { + "0": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 18 + } + }, + "1": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 20 + } + } + }, + "fnMap": { + "0": { + "name": "covered", + "decl": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 18 + } + }, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 18 + } + } + }, + "1": { + "name": "uncovered", + "decl": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 20 + } + }, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 20 + } + } + } + }, + "branchMap": {}, + "s": { + "0": 1, + "1": 0 + }, + "f": { + "0": 1, + "1": 0 + }, + "b": {} +} \ No newline at end of file diff --git a/test/coverage-test/test/__snapshots__/custom-file-covered-1-v8.snapshot.json b/test/coverage-test/test/__snapshots__/custom-file-covered-1-v8.snapshot.json new file mode 100644 index 000000000000..35a17c7894b8 --- /dev/null +++ b/test/coverage-test/test/__snapshots__/custom-file-covered-1-v8.snapshot.json @@ -0,0 +1,150 @@ +{ + "path": "/fixtures/src/covered.custom-1", + "all": false, + "statementMap": { + "0": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 18 + } + }, + "1": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 2, + "column": 0 + } + }, + "2": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 20 + } + }, + "3": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 4, + "column": 0 + } + }, + "4": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 24 + } + } + }, + "s": { + "0": 1, + "1": 1, + "2": 0, + "3": 1, + "4": 1 + }, + "branchMap": { + "0": { + "type": "branch", + "line": 1, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 18 + } + }, + "locations": [ + { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 18 + } + } + ] + } + }, + "b": { + "0": [ + 1 + ] + }, + "fnMap": { + "0": { + "name": "covered", + "decl": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 18 + } + }, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 18 + } + }, + "line": 1 + }, + "1": { + "name": "uncovered", + "decl": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 20 + } + }, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 20 + } + }, + "line": 3 + } + }, + "f": { + "0": 1, + "1": 0 + } +} \ No newline at end of file diff --git a/test/coverage-test/test/__snapshots__/custom-file-covered-2-istanbul.snapshot.json b/test/coverage-test/test/__snapshots__/custom-file-covered-2-istanbul.snapshot.json new file mode 100644 index 000000000000..996a4a66c39e --- /dev/null +++ b/test/coverage-test/test/__snapshots__/custom-file-covered-2-istanbul.snapshot.json @@ -0,0 +1,83 @@ +{ + "path": "/fixtures/workspaces/custom-2/src/covered.custom-2", + "statementMap": { + "0": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 18 + } + }, + "1": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 20 + } + } + }, + "fnMap": { + "0": { + "name": "covered", + "decl": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 18 + } + }, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 18 + } + } + }, + "1": { + "name": "uncovered", + "decl": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 20 + } + }, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 20 + } + } + } + }, + "branchMap": {}, + "s": { + "0": 1, + "1": 0 + }, + "f": { + "0": 1, + "1": 0 + }, + "b": {} +} \ No newline at end of file diff --git a/test/coverage-test/test/__snapshots__/custom-file-covered-2-v8.snapshot.json b/test/coverage-test/test/__snapshots__/custom-file-covered-2-v8.snapshot.json new file mode 100644 index 000000000000..e5380af0ec31 --- /dev/null +++ b/test/coverage-test/test/__snapshots__/custom-file-covered-2-v8.snapshot.json @@ -0,0 +1,150 @@ +{ + "path": "/fixtures/workspaces/custom-2/src/covered.custom-2", + "all": false, + "statementMap": { + "0": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 18 + } + }, + "1": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 2, + "column": 0 + } + }, + "2": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 20 + } + }, + "3": { + "start": { + "line": 4, + "column": 0 + }, + "end": { + "line": 4, + "column": 0 + } + }, + "4": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 24 + } + } + }, + "s": { + "0": 1, + "1": 1, + "2": 0, + "3": 1, + "4": 1 + }, + "branchMap": { + "0": { + "type": "branch", + "line": 1, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 18 + } + }, + "locations": [ + { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 18 + } + } + ] + } + }, + "b": { + "0": [ + 1 + ] + }, + "fnMap": { + "0": { + "name": "covered", + "decl": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 18 + } + }, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 18 + } + }, + "line": 1 + }, + "1": { + "name": "uncovered", + "decl": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 20 + } + }, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 20 + } + }, + "line": 3 + } + }, + "f": { + "0": 1, + "1": 0 + } +} \ No newline at end of file diff --git a/test/coverage-test/test/__snapshots__/custom-file-uncovered-1-istanbul.snapshot.json b/test/coverage-test/test/__snapshots__/custom-file-uncovered-1-istanbul.snapshot.json new file mode 100644 index 000000000000..af9efc13611e --- /dev/null +++ b/test/coverage-test/test/__snapshots__/custom-file-uncovered-1-istanbul.snapshot.json @@ -0,0 +1,48 @@ +{ + "path": "/fixtures/src/uncovered.custom-1", + "statementMap": { + "0": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 20 + } + } + }, + "fnMap": { + "0": { + "name": "uncovered", + "decl": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 20 + } + }, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 20 + } + } + } + }, + "branchMap": {}, + "s": { + "0": 0 + }, + "f": { + "0": 0 + }, + "b": {} +} \ No newline at end of file diff --git a/test/coverage-test/test/__snapshots__/custom-file-uncovered-1-v8.snapshot.json b/test/coverage-test/test/__snapshots__/custom-file-uncovered-1-v8.snapshot.json new file mode 100644 index 000000000000..1eed60fca928 --- /dev/null +++ b/test/coverage-test/test/__snapshots__/custom-file-uncovered-1-v8.snapshot.json @@ -0,0 +1,103 @@ +{ + "path": "/fixtures/src/uncovered.custom-1", + "all": true, + "statementMap": { + "0": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 20 + } + }, + "1": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 2, + "column": 0 + } + }, + "2": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 26 + } + } + }, + "s": { + "0": 0, + "1": 0, + "2": 0 + }, + "branchMap": { + "0": { + "type": "branch", + "line": 1, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 26 + } + }, + "locations": [ + { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 26 + } + } + ] + } + }, + "b": { + "0": [ + 0 + ] + }, + "fnMap": { + "0": { + "name": "(empty-report)", + "decl": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 26 + } + }, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 26 + } + }, + "line": 1 + } + }, + "f": { + "0": 0 + } +} \ No newline at end of file diff --git a/test/coverage-test/test/__snapshots__/custom-file-uncovered-2-istanbul.snapshot.json b/test/coverage-test/test/__snapshots__/custom-file-uncovered-2-istanbul.snapshot.json new file mode 100644 index 000000000000..eb4119bb3faa --- /dev/null +++ b/test/coverage-test/test/__snapshots__/custom-file-uncovered-2-istanbul.snapshot.json @@ -0,0 +1,48 @@ +{ + "path": "/fixtures/workspaces/custom-2/src/uncovered.custom-2", + "statementMap": { + "0": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 20 + } + } + }, + "fnMap": { + "0": { + "name": "uncovered", + "decl": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 20 + } + }, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 20 + } + } + } + }, + "branchMap": {}, + "s": { + "0": 0 + }, + "f": { + "0": 0 + }, + "b": {} +} \ No newline at end of file diff --git a/test/coverage-test/test/__snapshots__/custom-file-uncovered-2-v8.snapshot.json b/test/coverage-test/test/__snapshots__/custom-file-uncovered-2-v8.snapshot.json new file mode 100644 index 000000000000..4d6d12368463 --- /dev/null +++ b/test/coverage-test/test/__snapshots__/custom-file-uncovered-2-v8.snapshot.json @@ -0,0 +1,103 @@ +{ + "path": "/fixtures/workspaces/custom-2/src/uncovered.custom-2", + "all": true, + "statementMap": { + "0": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 20 + } + }, + "1": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 2, + "column": 0 + } + }, + "2": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 26 + } + } + }, + "s": { + "0": 0, + "1": 0, + "2": 0 + }, + "branchMap": { + "0": { + "type": "branch", + "line": 1, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 26 + } + }, + "locations": [ + { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 26 + } + } + ] + } + }, + "b": { + "0": [ + 0 + ] + }, + "fnMap": { + "0": { + "name": "(empty-report)", + "decl": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 26 + } + }, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 26 + } + }, + "line": 1 + } + }, + "f": { + "0": 0 + } +} \ No newline at end of file diff --git a/test/coverage-test/test/all.test.ts b/test/coverage-test/test/all.test.ts index 063072e1f3db..97e464ed80af 100644 --- a/test/coverage-test/test/all.test.ts +++ b/test/coverage-test/test/all.test.ts @@ -4,7 +4,7 @@ import { readCoverageMap, runVitest, test } from '../utils' test('{ all: true } includes uncovered files', async () => { await runVitest({ include: ['fixtures/test/**'], - exclude: ['**/virtual-files-**'], + exclude: ['**/virtual-files-**', '**/custom-1-syntax**'], coverage: { include: ['fixtures/src/**'], all: true, @@ -25,7 +25,7 @@ test('{ all: true } includes uncovered files', async () => { test('{ all: false } excludes uncovered files', async () => { await runVitest({ include: ['fixtures/test/**'], - exclude: ['**/virtual-files-**'], + exclude: ['**/virtual-files-**', '**/custom-1-syntax**'], coverage: { include: ['fixtures/src/**'], all: false, diff --git a/test/coverage-test/test/changed.test.ts b/test/coverage-test/test/changed.test.ts index ada4a686306d..78083beec26c 100644 --- a/test/coverage-test/test/changed.test.ts +++ b/test/coverage-test/test/changed.test.ts @@ -33,6 +33,7 @@ afterAll(() => { test('{ changed: "HEAD" }', async () => { await runVitest({ include: ['fixtures/test/**'], + exclude: ['**/custom-1-syntax**'], changed: 'HEAD', coverage: { include: ['fixtures/src/**'], diff --git a/test/coverage-test/test/workspace.multi-transform.test.ts b/test/coverage-test/test/workspace.multi-transform.test.ts new file mode 100644 index 000000000000..0eb2b63df398 --- /dev/null +++ b/test/coverage-test/test/workspace.multi-transform.test.ts @@ -0,0 +1,43 @@ +import { expect } from 'vitest' +import { isV8Provider, readCoverageMap, runVitest, test } from '../utils' + +test('{ all: true } includes uncovered files that require custom transform', async () => { + await runVitest({ + workspace: 'fixtures/configs/vitest.workspace.multi-transforms.ts', + coverage: { + all: true, + extension: ['.ts', '.custom-1', '.custom-2'], + reporter: ['json', 'html'], + include: ['**/*.custom-1', '**/*.custom-2', '**/math.ts'], + }, + }) + + const coverageMap = await readCoverageMap() + const files = coverageMap.files() + + // All files from workspace should be picked + expect(files).toMatchInlineSnapshot(` + [ + "/fixtures/src/covered.custom-1", + "/fixtures/src/math.ts", + "/fixtures/src/uncovered.custom-1", + "/fixtures/workspaces/custom-2/src/covered.custom-2", + "/fixtures/workspaces/custom-2/src/uncovered.custom-2", + ] + `) + + const covered1 = coverageMap.fileCoverageFor('/fixtures/src/covered.custom-1') + const uncovered1 = coverageMap.fileCoverageFor('/fixtures/src/uncovered.custom-1') + const covered2 = coverageMap.fileCoverageFor('/fixtures/workspaces/custom-2/src/covered.custom-2') + const uncovered2 = coverageMap.fileCoverageFor('/fixtures/workspaces/custom-2/src/uncovered.custom-2') + + // Coverage maps indicate whether source maps are correct. Check html-report if these change + expect(JSON.stringify(covered1, null, 2)).toMatchFileSnapshot(snapshotName('covered-1')) + expect(JSON.stringify(uncovered1, null, 2)).toMatchFileSnapshot(snapshotName('uncovered-1')) + expect(JSON.stringify(covered2, null, 2)).toMatchFileSnapshot(snapshotName('covered-2')) + expect(JSON.stringify(uncovered2, null, 2)).toMatchFileSnapshot(snapshotName('uncovered-2')) +}) + +function snapshotName(label: string) { + return `__snapshots__/custom-file-${label}-${isV8Provider() ? 'v8' : 'istanbul'}.snapshot.json` +}