diff --git a/packages/coverage-v8/rollup.config.js b/packages/coverage-v8/rollup.config.js index eb5897fd6f51..a98a9d425eb0 100644 --- a/packages/coverage-v8/rollup.config.js +++ b/packages/coverage-v8/rollup.config.js @@ -19,6 +19,7 @@ const external = [ ...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {}), 'node:inspector', + 'vite', /^@?vitest(\/|$)/, ] diff --git a/packages/coverage-v8/src/provider.ts b/packages/coverage-v8/src/provider.ts index 517f446618cb..9da3b6c0767d 100644 --- a/packages/coverage-v8/src/provider.ts +++ b/packages/coverage-v8/src/provider.ts @@ -1,6 +1,7 @@ import { existsSync, promises as fs, writeFileSync } from 'node:fs' import type { Profiler } from 'node:inspector' import { fileURLToPath, pathToFileURL } from 'node:url' +import { parseAstAsync } from 'vite' import v8ToIstanbul from 'v8-to-istanbul' import { mergeProcessCovs } from '@bcoe/v8-coverage' import libReport from 'istanbul-lib-report' @@ -260,7 +261,13 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage } const coverages = await Promise.all(chunk.map(async (filename) => { - const { source } = await this.getSources(filename.href, transformResults) + const transformResult = await this.ctx.vitenode.transformRequest(filename.pathname) + + // Ignore empty files, e.g. files that contain only typescript types and no runtime code + if (transformResult && await isFileEmpty(transformResult.code)) + return null + + const { originalSource } = await this.getSources(filename.href, transformResults) const coverage = { url: filename.href, @@ -269,7 +276,7 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage functions: [{ ranges: [{ startOffset: 0, - endOffset: source.length, + endOffset: originalSource.length, count: 0, }], isBlockCoverage: true, @@ -281,7 +288,10 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage return { result: [coverage] } })) - merged = mergeProcessCovs([merged, ...coverages]) + merged = mergeProcessCovs([ + merged, + ...coverages.filter((cov): cov is NonNullable => cov != null), + ]) } return merged @@ -289,7 +299,7 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage private async getSources(url: string, transformResults: TransformResults, functions: Profiler.FunctionCoverage[] = []): Promise<{ source: string - originalSource?: string + originalSource: string sourceMap?: { sourcemap: EncodedSourceMap } }> { const filePath = normalize(fileURLToPath(url)) @@ -302,12 +312,16 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage // If file does not exist construct a dummy source for it. // These can be files that were generated dynamically during the test run and were removed after it. const length = findLongestFunctionLength(functions) - return '.'.repeat(length) + return '/'.repeat(length) }) // These can be uncovered files included by "all: true" or files that are loaded outside vite-node - if (!map) - return { source: code || sourcesContent } + if (!map) { + return { + source: code || sourcesContent, + originalSource: sourcesContent, + } + } return { originalSource: sourcesContent, @@ -411,3 +425,15 @@ function normalizeTransformResults(fetchCache: Map