Harden typecheck diagnostic collection

This commit is contained in:
Brennan Benson
2026-08-28 03:09:31 -07:00
parent 675429f3e6
commit b02f19e324
2 changed files with 65 additions and 8 deletions
@@ -1,9 +1,10 @@
import { spawnSync } from 'node:child_process'
import { realpathSync } from 'node:fs'
import { readFile, writeFile } from 'node:fs/promises'
import { relative, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
const repoRoot = fileURLToPath(new URL('../..', import.meta.url))
const repoRoot = realpathSync(fileURLToPath(new URL('../..', import.meta.url)))
function parseArgs(argv) {
const options = { write: false }
@@ -95,10 +96,12 @@ function collectDiagnostics(projectPath, tscPath) {
if (result.signal) {
throw new Error(`TypeScript exited with signal ${result.signal}`)
}
const output = `${result.stdout ?? ''}${result.stderr ?? ''}`
const diagnostics = parseDiagnostics(output)
const stdout = result.stdout ?? ''
const diagnostics = parseDiagnostics(stdout)
if (result.status !== 0 && diagnostics.length === 0) {
throw new Error(`TypeScript exited ${result.status} without parseable diagnostics:\n${output}`)
throw new Error(
`TypeScript exited ${result.status} without parseable diagnostics:\nstdout:\n${stdout}\nstderr:\n${result.stderr ?? ''}`
)
}
return diagnostics
}
@@ -1,4 +1,4 @@
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { spawnSync } from 'node:child_process'
@@ -32,11 +32,25 @@ function createFixture(source) {
}
}
function runBaseline(paths, extraArgs = []) {
function runBaseline(paths, extraArgs = [], spawnOptions = {}) {
return spawnSync(
process.execPath,
[script, '--project', paths.project, '--baseline', paths.baseline, '--tsc', tsc, ...extraArgs],
{ cwd: repoRoot, encoding: 'utf8' }
[
...(spawnOptions.nodeArgs ?? []),
spawnOptions.script ?? script,
'--project',
paths.project,
'--baseline',
paths.baseline,
'--tsc',
paths.tsc ?? tsc,
...extraArgs
],
{
cwd: repoRoot,
encoding: 'utf8',
env: { ...process.env, ...spawnOptions.env }
}
)
}
@@ -91,6 +105,46 @@ describe('typecheck diagnostic baseline', () => {
expect(runBaseline(paths).status).toBe(0)
})
it('matches recorded diagnostics through a symlinked checkout path', () => {
fixtureDir = mkdtempSync(join(tmpdir(), 'orca-typecheck-symlink-'))
const linkedCheckout = join(fixtureDir, 'checkout')
symlinkSync(repoRoot, linkedCheckout, process.platform === 'win32' ? 'junction' : 'dir')
const result = runBaseline(
{
project: 'config/tsconfig.e2e.json',
baseline: 'config/typecheck-e2e-diagnostics.json'
},
[],
{
nodeArgs: ['--preserve-symlinks-main'],
script: join(linkedCheckout, 'config/scripts/typecheck-diagnostic-baseline.mjs')
}
)
expect(result.status).toBe(0)
expect(result.stdout).toContain('240 known diagnostics, no drift')
})
it('ignores stderr warnings while parsing compiler diagnostics', () => {
const paths = createFixture("const value: number = 'bad'\n")
paths.tsc = join(fixtureDir, 'fake-tsc.mjs')
writeFileSync(
paths.tsc,
[
"if (process.env.INJECT_TSC_WARNING) process.stderr.write('pnpm warning: unsupported engine\\n')",
"process.stdout.write(\"source.ts(1,7): error TS2322: Type 'string' is not assignable to type 'number'.\\n\")",
'process.exitCode = 1'
].join('\n')
)
expect(runBaseline(paths, ['--write']).status).toBe(0)
const result = runBaseline(paths, [], { env: { INJECT_TSC_WARNING: '1' } })
expect(result.status).toBe(0)
expect(result.stdout).toContain('1 known diagnostics, no drift')
})
it('refuses to regenerate a baseline that grows from the PR base', () => {
const paths = createFixture("const first: number = 'bad'\n")
expect(runBaseline(paths, ['--write']).status).toBe(0)