microsoft/TypeScript
Debugging
Most TypeScript compiler bugs are reproduced via a failing test under tests/cases/. This page covers the debugging tools the team uses once a test is in hand.
printf debugging
The fastest path to understanding the checker is console.log. The repo's coding-guideline document recommends:
function checkSomething(n: Node) {
doSomething(n);
+ // @ts-ignore DEBUG CODE ONLY, REMOVE ME WHEN DONE
+ console.log(`Got node with pos = ${n.pos}, kind = ${SyntaxKind[n.kind]}`);
doSomethingElse(n);
}Indexing back into the enum (SyntaxKind[n.kind]) gives a readable name. The same trick works for TypeFlags, SymbolFlags, NodeFlags, etc.
Always prefix debug logs with a comment that says they're temporary and remove them before opening a PR.
Debug.assert* and Debug.fail
The compiler ships rich debug primitives in src/compiler/debug.ts:
Debug.assertNever(x)— exhaustiveness check.Debug.assertIsDefined(x)/Debug.checkDefined(x)— null check with assertion.Debug.fail(message)— unconditional failure.Debug.assert(condition, message?)— guarded assertion.Debug.formatSyntaxKind(kind),Debug.formatTypeFlags(flags),Debug.formatSymbolFlags(flags)— pretty-print enums.
Use these instead of assert from "node:assert" or hand-rolled if (!x) throw …. The local ESLint rule debug-assert enforces this.
Inspector debugging
hereby runtests --tests=mytest -i (or --inspect) launches mocha under node --inspect-brk. Attach Chrome DevTools (chrome://inspect) or VS Code (use the bundled "JavaScript Debug Terminal").
For language-service debugging via fourslash, the same flag works — every fourslash assertion runs in the same Node process.
VS Code launch configurations
The repo includes /.vscode/launch.template.json. Copy to .vscode/launch.json, then choose:
- Run currently open compiler test — runs the
.tstest file in the active editor. - Run currently open fourslash test — runs an LS test.
- Mocha Tests (currently opened test) — for unit tests.
Set breakpoints anywhere in src/. The launch config compiles incrementally so subsequent runs are fast.
Tracing
tsc --generateTrace ./trace-output produces Chrome-trace JSON files describing every checker phase, transformer pass, and emit step. Open chrome://tracing and load trace.json to see a flame graph.
tracing is wired into the source via the helpers in src/compiler/tracing.ts:
tracing?.push(tracing.Phase.Check, 'checkSourceFile', {
fileName: file.fileName,
});
try {
/* expensive work */
} finally {
tracing?.pop();
}Performance regressions are usually first noticed in trace output.
--diagnostics and --extendedDiagnostics
tsc --diagnostics ./tsconfig.json prints per-phase timings and counts:
Files: 54
Lines of Library: 33812
Nodes of Library: 125698
Identifiers: 52371
Symbols: 43126
Types: 39084
Memory used: 187168K--extendedDiagnostics adds per-file resolution counts and cache-hit rates.
Source maps
built/local/ is built with source maps. Stack traces map back to the original .ts files when running the local build. If they don't, check NODE_ENV=development and ensure setBlocking() hasn't been disabled.
Scoping a problem
Several patterns are useful when a bug is hard to localise:
- Bisect via
--target. If the bug only reproduces at--target ES5, look attransformers/es2015.ts,transformers/generators.ts, and helpers. - Bisect via
--module. If only one module mode reproduces, suspectsrc/compiler/transformers/module/andmoduleNameResolver.ts. - Toggle
--strictflags. Narrowing-related bugs often only appear under--strictNullChecks. - Try the LKG compiler.
node ./lib/tsc.jsruns the bootstrap compiler. If the LKG is fine andbuilt/localis broken, the bug is in your in-progress changes.
Common pitfalls
Debug.assertDefinedtriggers in--isolatedModules. Some helpers expect a checker; intranspileModulepaths the checker isn't available. UseDebug.checkDefinedselectively.SourceFile.pathvsfileName. Map keys must usepath(normalised). Many bugs come from accidentally keying onfileName.- Forgetting
setParentRecursive. Synthesised trees without parent pointers will explode in the checker. The factory'supdate*methods preserve parent pointers;create*methods do not (they leaveparentfor the caller to set).
Performance
Common performance investigations:
- A slow checker call usually corresponds to deep recursion in
isTypeAssignableToorinstantiateType. Add atracing.pushto the suspected entry point and view the trace. - Slow auto-import is usually a
node_modulesscan. Profile under--generateTrace; look for theAutoImportProviderphases. - Slow watch-mode rebuilds correlate with cache misses in
resolutionCache.ts. Add--traceResolutionto see what's being looked up.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.