diff --git a/.prettierignore b/.prettierignore index b9b99d1dc..a1ff31845 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,4 +4,6 @@ lib dist temp target -pnpm-lock.yaml \ No newline at end of file +pnpm-lock.yaml +# Line positions are significant for the sourcemap integration test +packages/integrate/__tests__/sourcemaps/throw-with-interfaces.ts \ No newline at end of file diff --git a/packages/integrate/__tests__/sourcemaps/__snapshots__/sourcemaps.spec.ts.md b/packages/integrate/__tests__/sourcemaps/__snapshots__/sourcemaps.spec.ts.md deleted file mode 100644 index cb5c24d3f..000000000 --- a/packages/integrate/__tests__/sourcemaps/__snapshots__/sourcemaps.spec.ts.md +++ /dev/null @@ -1,19 +0,0 @@ -# Snapshot report for `packages/integrate/__tests__/sourcemaps/sourcemaps.spec.ts` - -The actual snapshot is saved in `sourcemaps.spec.ts.snap`. - -Generated by [AVA](https://avajs.dev). - -## should work with sourcemaps - -> Snapshot 1 - - `Error: ␊ - at /packages/integrate/__tests__/sourcemaps/sourcemaps.spec.ts:18:26␊ - at Test.callFn (file:///node_modules/.pnpm/ava@6.4.1_encoding@0.1.13/node_modules/ava/lib/test.js:525:26)␊ - at Test.run (file:///node_modules/.pnpm/ava@6.4.1_encoding@0.1.13/node_modules/ava/lib/test.js:534:33)␊ - at Runner.runSingle (file:///node_modules/.pnpm/ava@6.4.1_encoding@0.1.13/node_modules/ava/lib/runner.js:280:33)␊ - at Runner.runTest (file:///node_modules/.pnpm/ava@6.4.1_encoding@0.1.13/node_modules/ava/lib/runner.js:362:30)␊ - at async Promise.all (index 0)␊ - at file:///node_modules/.pnpm/ava@6.4.1_encoding@0.1.13/node_modules/ava/lib/runner.js:515:21␊ - at Runner.start (file:///node_modules/.pnpm/ava@6.4.1_encoding@0.1.13/node_modules/ava/lib/runner.js:523:15)` diff --git a/packages/integrate/__tests__/sourcemaps/__snapshots__/sourcemaps.spec.ts.snap b/packages/integrate/__tests__/sourcemaps/__snapshots__/sourcemaps.spec.ts.snap deleted file mode 100644 index de0c48d27..000000000 Binary files a/packages/integrate/__tests__/sourcemaps/__snapshots__/sourcemaps.spec.ts.snap and /dev/null differ diff --git a/packages/integrate/__tests__/sourcemaps/sourcemaps.spec.ts b/packages/integrate/__tests__/sourcemaps/sourcemaps.spec.ts index ef7461545..3f1a5aa9d 100644 --- a/packages/integrate/__tests__/sourcemaps/sourcemaps.spec.ts +++ b/packages/integrate/__tests__/sourcemaps/sourcemaps.spec.ts @@ -1,36 +1,32 @@ +import { readFileSync } from 'fs' import { join } from 'path' import test from 'ava' -// @ts-expect-error -interface _Unused1 {} -// @ts-expect-error -interface _Unused2 {} -// @ts-expect-error -interface _Unused3 {} -// @ts-expect-error -interface _Unused4 {} -// @ts-expect-error -interface _Unused5 {} -// @ts-expect-error -interface _Unused6 {} -// @ts-expect-error -interface _Unused7 {} -// @ts-expect-error -interface _Unused8 {} -// @ts-expect-error -interface _Unused9 {} - -test('should work with sourcemaps', (t) => { +test('should report correct line numbers in stack traces when sourceMap is true', (t) => { if (process.platform === 'win32') { return t.pass('Skip on Windows') } - const projectRoot = join(__dirname, '..', '..', '..', '..') - t.snapshot( - new Error().stack - ?.split('\n') - .map((l) => l.replace(projectRoot, '')) - .filter((n) => !n.includes('node:internal')) - .join('\n'), - ) + + // Read the helper file to find the actual line number of the throw statement. + // This makes the test resilient to reformatting: if the file layout changes, + // the expected line number updates automatically. + const helperPath = join(__dirname, 'throw-with-interfaces.ts') + const source = readFileSync(helperPath, 'utf-8') + const expectedLine = source.split('\n').findIndex((line) => line.includes("throw new Error('sourcemap-test')")) + 1 + t.true(expectedLine > 0, 'Could not find throw statement in throw-with-interfaces.ts') + + // The helper file has 9 TypeScript interfaces before the throw. + // SWC strips them during transpilation, so if source maps are broken + // the reported line number will be lower than the actual source line. + try { + require('./throw-with-interfaces').throwError() + t.fail('Expected throwError() to throw') + } catch (err) { + const stack = (err as Error).stack ?? '' + const match = stack.match(/throw-with-interfaces\.ts:(\d+)/) + t.truthy(match, `Stack trace should reference throw-with-interfaces.ts, got:\n${stack}`) + const actualLine = parseInt(match![1], 10) + t.is(actualLine, expectedLine, `Expected error on line ${expectedLine} but stack trace reported line ${actualLine}`) + } }) diff --git a/packages/integrate/__tests__/sourcemaps/throw-with-interfaces.ts b/packages/integrate/__tests__/sourcemaps/throw-with-interfaces.ts new file mode 100644 index 000000000..9e3c20cbb --- /dev/null +++ b/packages/integrate/__tests__/sourcemaps/throw-with-interfaces.ts @@ -0,0 +1,17 @@ +// @ts-nocheck — unused interfaces are intentional +// These type-only constructs are stripped by SWC during transpilation. +// If source maps are broken, the reported line numbers in stack traces +// will be offset by the number of stripped lines. +interface Unused1 { a: string } +interface Unused2 { b: number } +interface Unused3 { c: boolean } +interface Unused4 { d: string } +interface Unused5 { e: number } +interface Unused6 { f: boolean } +interface Unused7 { g: string } +interface Unused8 { h: number } +interface Unused9 { i: boolean } + +exports.throwError = function throwError() { + throw new Error('sourcemap-test') +} diff --git a/packages/register/__test__/ts-compiler-options-to-swc-config.spec.ts b/packages/register/__test__/ts-compiler-options-to-swc-config.spec.ts index 4bce11e14..a7d3f4c03 100644 --- a/packages/register/__test__/ts-compiler-options-to-swc-config.spec.ts +++ b/packages/register/__test__/ts-compiler-options-to-swc-config.spec.ts @@ -71,6 +71,15 @@ test('should force the jsx config', (t) => { t.like(swcConfig, expected) }) +test('should set sourcemap to true when sourceMap is true and inlineSourceMap is not set', (t) => { + const options: ts.CompilerOptions = { + sourceMap: true, + } + const filename = 'some-file.ts' + const swcConfig = tsCompilerOptionsToSwcConfig(options, filename) + t.like(swcConfig, { sourcemap: true }) +}) + test('should set all values', (t) => { const options: ts.CompilerOptions = { module: ts.ModuleKind.CommonJS, diff --git a/packages/register/read-default-tsconfig.ts b/packages/register/read-default-tsconfig.ts index 94016ffe4..39b8137af 100644 --- a/packages/register/read-default-tsconfig.ts +++ b/packages/register/read-default-tsconfig.ts @@ -133,8 +133,7 @@ export function tsCompilerOptionsToSwcConfig(options: ts.CompilerOptions, filena module: toModule(options.module ?? ts.ModuleKind.ES2015), target: toTsTarget(target), jsx: isJsx, - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - sourcemap: options.sourceMap || enableInlineSourceMap ? 'inline' : Boolean(options.sourceMap), + sourcemap: enableInlineSourceMap ? 'inline' : Boolean(options.sourceMap), experimentalDecorators: options.experimentalDecorators ?? false, emitDecoratorMetadata: options.emitDecoratorMetadata ?? false, useDefineForClassFields: getUseDefineForClassFields(options, target),