diff --git a/packages/coverage-istanbul/package.json b/packages/coverage-istanbul/package.json index 6cd6b2ea2..ba15ea87b 100644 --- a/packages/coverage-istanbul/package.json +++ b/packages/coverage-istanbul/package.json @@ -45,6 +45,8 @@ }, "dependencies": { "@istanbuljs/schema": "^0.1.3", + "@jridgewell/gen-mapping": "^0.3.13", + "@jridgewell/trace-mapping": "catalog:", "istanbul-lib-coverage": "catalog:", "istanbul-lib-instrument": "^6.0.3", "istanbul-lib-report": "catalog:", diff --git a/packages/coverage-istanbul/src/provider.ts b/packages/coverage-istanbul/src/provider.ts index 614b9cab7..5851c5589 100644 --- a/packages/coverage-istanbul/src/provider.ts +++ b/packages/coverage-istanbul/src/provider.ts @@ -5,6 +5,8 @@ import type { CoverageProvider, ReportContext, ResolvedCoverageOptions, Vite, Vi import { existsSync, promises as fs } from 'node:fs' // @ts-expect-error missing types import { defaults as istanbulDefaults } from '@istanbuljs/schema' +import { addMapping, GenMapping, toEncodedMap } from '@jridgewell/gen-mapping' +import { eachMapping, TraceMap } from '@jridgewell/trace-mapping' import libCoverage from 'istanbul-lib-coverage' import { createInstrumenter } from 'istanbul-lib-instrument' import libReport from 'istanbul-lib-report' @@ -85,6 +87,30 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider { + addMapping(transformMap, { + generated: { line: mapping.generatedLine, column: mapping.generatedColumn }, + original: { line: mapping.generatedLine, column: mapping.generatedColumn }, + content: sourceCode, + name: mapping.name || '', + source: mapping.source || '', + }) + }) + + const encodedMap = toEncodedMap(transformMap) + delete encodedMap.file + delete encodedMap.ignoreList + delete encodedMap.sourceRoot + + this.instrumenter.instrumentSync( + sourceCode, + id, + encodedMap as any, + ) + const map = this.instrumenter.lastSourceMap() as any this.transformedModuleIds.add(id) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f42cd559..7fe2b435e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -553,6 +553,12 @@ importers: '@istanbuljs/schema': specifier: ^0.1.3 version: 0.1.3 + '@jridgewell/gen-mapping': + specifier: ^0.3.13 + version: 0.3.13 + '@jridgewell/trace-mapping': + specifier: 'catalog:' + version: 0.3.31 istanbul-lib-coverage: specifier: 'catalog:' version: 3.2.2 @@ -3097,9 +3103,6 @@ packages: resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} engines: {node: '>=8'} - '@jridgewell/gen-mapping@0.3.12': - resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} - '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -9780,7 +9783,7 @@ snapshots: '@ampproject/remapping@2.3.0': dependencies: - '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 '@antfu/eslint-config@6.2.0(@vue/compiler-sfc@3.5.25)(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)(vitest@packages+vitest)': @@ -9931,7 +9934,7 @@ snapshots: dependencies: '@babel/parser': 7.28.5 '@babel/types': 7.28.4 - '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 @@ -11278,11 +11281,6 @@ snapshots: '@istanbuljs/schema@0.1.3': {} - '@jridgewell/gen-mapping@0.3.12': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 diff --git a/test/coverage-test/fixtures/src/throws-error.ts b/test/coverage-test/fixtures/src/throws-error.ts new file mode 100644 index 000000000..b6840cef6 --- /dev/null +++ b/test/coverage-test/fixtures/src/throws-error.ts @@ -0,0 +1,42 @@ +/** + * Some padding + */ +export function uncovered() { + return "uncoverd"; +} + +/** + * Some padding + */ + +export function throwsError(condition: Boolean) { + /** + * Some padding + */ + + if(condition === false) { + /** + * Some padding + */ + + return; + } + + /** + * Some padding + */ + function throws() { + throw new Error("Expected error") + } + /** + * Some padding + */ + throws() +} + +/** + * Some padding + */ +export function uncovered2() { + return "uncoverd"; +} diff --git a/test/coverage-test/fixtures/test/error-location.test.ts b/test/coverage-test/fixtures/test/error-location.test.ts new file mode 100644 index 000000000..58dc675dc --- /dev/null +++ b/test/coverage-test/fixtures/test/error-location.test.ts @@ -0,0 +1,6 @@ +import { test } from "vitest" +import { throwsError } from "../src/throws-error" + +test("throws error", async () => { + throwsError(true) +}) diff --git a/test/coverage-test/test/source-maps.test.ts b/test/coverage-test/test/source-maps.test.ts new file mode 100644 index 000000000..a25b53d41 --- /dev/null +++ b/test/coverage-test/test/source-maps.test.ts @@ -0,0 +1,30 @@ +import { expect } from 'vitest' +import { isBrowser, runVitest, test } from '../utils' + +test('errors point to correct location', async () => { + const { stderr } = await runVitest({ + include: ['fixtures/test/error-location.test.ts'], + coverage: { reporter: 'json' }, + }, { throwOnError: false }) + + if (isBrowser()) { + expect(stderr).toMatch(` +❯ throws fixtures/src/throws-error.ts:29:11 + 27| */ + 28| function throws() { + 29| throw new Error("Expected error") + | ^ + 30| } + `.trim()) + } + else { + expect(stderr).toMatch(` +❯ throws fixtures/src/throws-error.ts:29:12 + 27| */ + 28| function throws() { + 29| throw new Error("Expected error") + | ^ + 30| } + `.trim()) + } +}) diff --git a/test/coverage-test/vitest.config.ts b/test/coverage-test/vitest.config.ts index 7eb15f7f8..7d01d2aac 100644 --- a/test/coverage-test/vitest.config.ts +++ b/test/coverage-test/vitest.config.ts @@ -87,6 +87,7 @@ export default defineConfig({ '**/in-source.test.ts', '**/query-param-transforms.test.ts', '**/test/cjs-dependency.test.ts', + '**/test/source-maps.test.ts', ], exclude: [FIXTURES], }, @@ -118,6 +119,7 @@ export default defineConfig({ '**/in-source.test.ts', '**/query-param-transforms.test.ts', '**/test/cjs-dependency.test.ts', + '**/test/source-maps.test.ts', ], exclude: [FIXTURES], },