Skip to content

Commit 27d340a

Browse files
authored
fix(typecheck): fix error test case mapping for @ts-expect-error (#7125)
1 parent 6957182 commit 27d340a

File tree

5 files changed

+62
-7
lines changed

5 files changed

+62
-7
lines changed

packages/utils/src/source-map.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { resolve } from 'pathe'
55
import { isPrimitive, notNullish } from './helpers'
66

77
export {
8+
eachMapping,
9+
type EachMapping,
810
generatedPositionFor,
911
originalPositionFor,
1012
TraceMap,

packages/vitest/src/typecheck/typechecker.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { RawSourceMap } from '@ampproject/remapping'
22
import type { File, Task, TaskResultPack, TaskState } from '@vitest/runner'
33
import type { ParsedStack } from '@vitest/utils'
4+
import type { EachMapping } from '@vitest/utils/source-map'
45
import type { ChildProcess } from 'node:child_process'
56
import type { Vitest } from '../node/core'
67
import type { TestProject } from '../node/project'
@@ -10,7 +11,7 @@ import type { TscErrorInfo } from './types'
1011
import { rm } from 'node:fs/promises'
1112
import { performance } from 'node:perf_hooks'
1213
import { getTasks } from '@vitest/runner/utils'
13-
import { generatedPositionFor, TraceMap } from '@vitest/utils/source-map'
14+
import { eachMapping, generatedPositionFor, TraceMap } from '@vitest/utils/source-map'
1415
import { basename, extname, resolve } from 'pathe'
1516
import { x } from 'tinyexec'
1617
import { collectTests } from './collect'
@@ -161,7 +162,7 @@ export class Typechecker {
161162
}
162163
errors.forEach(({ error, originalError }) => {
163164
const processedPos = traceMap
164-
? generatedPositionFor(traceMap, {
165+
? findGeneratedPosition(traceMap, {
165166
line: originalError.line,
166167
column: originalError.column,
167168
source: basename(path),
@@ -364,3 +365,40 @@ export class Typechecker {
364365
.map<TaskResultPack>(i => [i.id, i.result, { typecheck: true }])
365366
}
366367
}
368+
369+
function findGeneratedPosition(traceMap: TraceMap, { line, column, source }: { line: number; column: number; source: string }) {
370+
const found = generatedPositionFor(traceMap, {
371+
line,
372+
column,
373+
source,
374+
})
375+
if (found.line !== null) {
376+
return found
377+
}
378+
// find the next source token position when the exact error position doesn't exist in source map.
379+
// this can happen, for example, when the type error is in the comment "// @ts-expect-error"
380+
// and comments are stripped away in the generated code.
381+
const mappings: (EachMapping & { originalLine: number })[] = []
382+
eachMapping(traceMap, (m) => {
383+
if (
384+
m.source === source
385+
&& m.originalLine !== null
386+
&& m.originalColumn !== null
387+
&& (line === m.originalLine ? column < m.originalColumn : line < m.originalLine)
388+
) {
389+
mappings.push(m)
390+
}
391+
})
392+
const next = mappings
393+
.sort((a, b) =>
394+
a.originalLine === b.originalLine ? a.originalColumn - b.originalColumn : a.originalLine - b.originalLine,
395+
)
396+
.at(0)
397+
if (next) {
398+
return {
399+
line: next.generatedLine,
400+
column: next.generatedColumn,
401+
}
402+
}
403+
return { line: null, column: null }
404+
}

test/typescript/failing/expect-error.test-d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { expectTypeOf, test } from 'vitest'
22

3+
//
34
test('failing test with expect-error', () => {
45
// @ts-expect-error expect nothing
56
expectTypeOf(1).toEqualTypeOf<number>()

test/typescript/test/__snapshots__/runner.test.ts.snap

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ TypeCheckError: This expression is not callable. Type 'ExpectVoid<number>' has n
2424
exports[`should fail > typecheck files 3`] = `
2525
" FAIL expect-error.test-d.ts > failing test with expect-error
2626
TypeCheckError: Unused '@ts-expect-error' directive.
27-
❯ expect-error.test-d.ts:4:3
28-
2|
29-
3| test('failing test with expect-error', () => {
30-
4| // @ts-expect-error expect nothing
27+
❯ expect-error.test-d.ts:5:3
28+
3| //
29+
4| test('failing test with expect-error', () => {
30+
5| // @ts-expect-error expect nothing
3131
| ^
32-
5| expectTypeOf(1).toEqualTypeOf<number>()"
32+
6| expectTypeOf(1).toEqualTypeOf<number>()"
3333
`;
3434
3535
exports[`should fail > typecheck files 4`] = `
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { defineConfig } from 'vitest/config'
2+
3+
// pnpm -C test/typescript test -- -c vitest.config.fails.ts
4+
export default defineConfig({
5+
test: {
6+
dir: './failing',
7+
typecheck: {
8+
enabled: true,
9+
allowJs: true,
10+
include: ['**/*.test-d.*'],
11+
tsconfig: './tsconfig.fails.json',
12+
},
13+
},
14+
})

0 commit comments

Comments
 (0)