fix(typecheck): improve error message when tsc outputs help text (#9214) · vitest-dev/vitest@7b10ab4

@@ -53,7 +53,7 @@ export class Typechecker {

53535454

protected files: string[] = []

555556-

constructor(protected project: TestProject) {}

56+

constructor(protected project: TestProject) { }

57575858

public setFiles(files: string[]): void {

5959

this.files = files

@@ -123,6 +123,22 @@ export class Typechecker {

123123

sourceErrors: TestError[]

124124

time: number

125125

}> {

126+

// Detect if tsc output is help text instead of error output

127+

// This happens when tsconfig.json is missing and tsc can't find any config

128+

if (output.includes('The TypeScript Compiler - Version') || output.includes('COMMON COMMANDS')) {

129+

const { typecheck } = this.project.config

130+

const tsconfigPath = typecheck.tsconfig || 'tsconfig.json'

131+

const msg = `TypeScript compiler returned help text instead of type checking results.\n`

132+

+ `This usually means the tsconfig file was not found.\n\n`

133+

+ `Possible solutions:\n`

134+

+ ` 1. Ensure '${tsconfigPath}' exists in your project root\n`

135+

+ ` 2. If using a custom tsconfig, verify the path in your Vitest config:\n`

136+

+ ` test: { typecheck: { tsconfig: 'path/to/tsconfig.json' } }\n`

137+

+ ` 3. Check that the tsconfig file is valid JSON`

138+139+

throw new Error(msg)

140+

}

141+126142

const typeErrors = await this.parseTscLikeOutput(output)

127143

const testFiles = new Set(this.getFiles())

128144

@@ -319,6 +335,8 @@ export class Typechecker {

319335

return

320336

}

321337338+

let resolved = false

339+322340

child.process.stdout.on('data', (chunk) => {

323341

dataReceived = true

324342

this._output += chunk

@@ -343,13 +361,25 @@ export class Typechecker {

343361

}

344362

})

345363364+

// Also capture stderr for configuration errors like missing tsconfig

365+

child.process.stderr?.on('data', (chunk) => {

366+

this._output += chunk

367+

})

368+346369

const timeout = setTimeout(

347370

() => reject(new Error(`${typecheck.checker} spawn timed out`)),

348371

this.project.config.typecheck.spawnTimeout,

349372

)

350373374+

let winTimeout: NodeJS.Timeout | undefined

375+351376

function onError(cause: Error) {

377+

if (resolved) {

378+

return

379+

}

352380

clearTimeout(timeout)

381+

clearTimeout(winTimeout)

382+

resolved = true

353383

reject(new Error('Spawning typechecker failed - is typescript installed?', { cause }))

354384

}

355385

@@ -361,11 +391,13 @@ export class Typechecker {

361391

// on Windows, the process might be spawned but fail to start

362392

// we wait for a potential error here. if "close" event didn't trigger,

363393

// we resolve the promise

364-

setTimeout(() => {

394+

winTimeout = setTimeout(() => {

395+

resolved = true

365396

resolve({ result: child })

366397

}, 200)

367398

}

368399

else {

400+

resolved = true

369401

resolve({ result: child })

370402

}

371403

})