TypeScript Under the Microscope: What 67 AST Tools Found Inside the Compiler
We benchmarked all 67 prod-code AST tools against microsoft/TypeScript: 264,411 declarations across 31,433 files, AST clone clusters, transitive slicing, and compiler in-memory validation.

On this page · 4 sections
microsoft/TypeScript is the industry standard typed superset of JavaScript, powering modern web and server applications globally. Its repository is one of the largest and most complex developer tool projects in existence, encompassing the core compiler, type checker, language server, parser generators, and tens of thousands of conformance test baselines.
Analyzing a project of this scale requires extraordinary efficiency. Running type checkers, full AST passes, and workspace-wide refactorings locally frequently maxes out laptop CPU cores, throttles battery life, and drains developer flow.
We ran all 67 prod-code AST tools against microsoft/TypeScript (commit 9adc871ff47f): 264,411 declarations across 31,433 source files. The evaluation was offloaded to 32-core LAN cluster nodes with zero local laptop CPU.
AST Clone Clusters Across 31,433 Files
The TypeScript compiler uses code generators to generate AST visitor dispatch tables and node transformation routines.
Running prod-code duplicates --path packages/typescript/src --min-lines 10 --max-groups 5 revealed multiple Type-2 parameterized clone clusters. In packages/typescript/src/ast/factory.generated.ts, an identical 10-line modifier traversal generator loop is repeated across 17 different AST visitor declarations (Clone Group #1480):
$ prod-code duplicates --path packages/typescript/src --min-lines 10 --max-groups 5
[Clone Group #1480] 10 lines | 17 occurrences (Type-2 (Parameterized))
• Occurrence 1: packages/typescript/src/ast/factory.generated.ts:2002-2011
• Occurrence 2: packages/typescript/src/ast/factory.generated.ts:2030-2039
• Occurrence 3: packages/typescript/src/ast/factory.generated.ts:2064-2073
• Occurrence 4: packages/typescript/src/ast/factory.generated.ts:2092-2101
• Occurrence 5: packages/typescript/src/ast/factory.generated.ts:2114-2123
• Occurrence 6: packages/typescript/src/ast/factory.generated.ts:2146-2155
Preview:
│ if (data.modifiers) {
│ for (const n of data.modifiers) {
│ const res = yield n;
│ if (res) return res;
│ …
Recommendation: Fold into a shared function using code_extract_function.
Similarly, clone groups #333 and #444 identified 14 repeated parameter and heritage clause traversal loops across node visitors. Offloading clone detection across the 31,433 files isolated generated factory boilerplate from handwritten language server logic in seconds.
Semantic Search and Transitive Slicing
Locating fundamental compiler entry points in a repository with over 31,000 files using regex grep returns thousands of fixture references, mock declarations, and benchmark suites.
We queried prod-code search for the core compilation method:
$ prod-code search "createSourceFile"
10 hit(s) for `createSourceFile` in 2088 ms (264411 declarations, 31433 files)
1. [function] createSourceFile tsc/testdata/fixtures/compiler/parser.ts:1978
2. [method] API::createSourceFile packages/typescript/src/api/async/api.ts:458
3. [method] createSourceFile tsc/internal/api/session.go:2034
4. [function] createSourceFile tsc/testdata/fixtures/compiler/parser.ts:1344
5. [method] API::function packages/typescript/src/api/sync/api.ts:627
In 2,088 ms, the gateway’s 3-way Reciprocal Rank Fusion (RRF) search ranked API::createSourceFile in packages/typescript/src/api/async/api.ts alongside the Go native compiler bridge (tsc/internal/api/session.go), directly linking client and server entry points.
Understanding the runtime requirements of createSourceFile without reading through the surrounding 3,200 lines of packages/typescript/src/api/async/api.ts is ideal for rapid comprehension:
$ prod-code slice packages/typescript/src/api/async/api.ts --line 458 --depth 1
[method] ensureInitialized packages/typescript/src/api/async/api.ts:375-378 (depth 1, used by createSourceFile)
private async ensureInitialized(): Promise<void> {
if (this.initialized) return;
return this.initializing ??= this.initializeWorker();
}
[method] createSourceFile packages/typescript/src/api/async/api.ts:458-465 (the seed)
async createSourceFile(fileName: string, sourceText: string, options: CreateSourceFileOptions = {}): Promise<RetainedSourceFile> {
await this.ensureInitialized();
const data = await this.client.apiRequestBinary("createSourceFile", { fileName, sourceText, options });
if (!data) {
throw new Error("createSourceFile returned no source file");
}
return this.retainSourceFileResponse(data);
}
[method] retainSourceFileResponse packages/typescript/src/api/async/api.ts:496-515 (depth 1, used by createSourceFile)
[class] RetainedSourceFile packages/typescript/src/api/async/api.ts:816-849 (depth 1, used by createSourceFile)
[method] apiRequestBinary packages/typescript/src/api/async/client.ts:297-302 (depth 1, used by createSourceFile)
[interface] CreateSourceFileOptions packages/typescript/src/api/proto.generated.ts:1711-1713 (depth 1, used by createSourceFile)
From a project spanning 264,411 declarations, slicing delivered the complete self-contained execution context in approximately 120 lines across 3 files.
Structural AST search captured error guards with metavariables across packages:
$ prod-code structural-search --path packages/typescript/src 'throw new Error($$$)'
134 match(es) in 18 file(s) (116 scanned in 721.72ms)
• packages/typescript/src/api/async/api.ts:299:13 throw new Error(`Snapshot ${snapshotId} is inactive`)
• packages/typescript/src/api/async/api.ts:398:13 throw new Error("API has not been initialized")
• packages/typescript/src/api/async/api.ts:462:13 throw new Error("createSourceFile returned no source file")
• packages/typescript/src/api/async/api.ts:1124:28 throw new Error("ModuleResolver is disposed")
• packages/typescript/src/api/async/api.ts:1257:28 throw new Error("Project object registry is disposed")
Refactoring Verification and Compiler Refusal
We evaluated automated boolean inversion on hasProperty in packages/typescript/src/internal/utils.ts:27:
export function hasProperty(map: MapLike<any>, key: string): boolean {
return hasOwnProperty.call(map, key);
}
Running prod-code invert-boolean packages/typescript/src/internal/utils.ts --line 27 --to missingProperty triggered reference classification and halted with a compiler safety refusal:
$ prod-code invert-boolean packages/typescript/src/internal/utils.ts --line 27 --to missingProperty
not rewritten (10 reference(s) that are not a call — a function used as a value keeps its old meaning under its new name; nothing is written while any remains):
tsc/testdata/tests/cases/compiler/unspecializedConstraints.ts:124:14 `hasProperty` used as a value
tsc/testdata/fixtures/compiler/utilities.ts:223:5 `hasProperty` used as a value
tsc/testdata/fixtures/compiler/program.ts:159:5 `hasProperty` used as a value
tsc/testdata/fixtures/compiler/commandLineParser.ts:67:5 `hasProperty` used as a value
tsc/testdata/fixtures/compiler/debug.ts:20:5 `hasProperty` used as a value
the analyzer rejects the result:
Module '"./_namespaces/ts.js"' has no exported member 'hasProperty'. [2305]
nothing was written; pass `apply: true` to make these edits
Because hasProperty is exported and referenced as a first-class value across namespace imports (import { hasProperty } from "./_namespaces/ts.js"), inverting its semantics without updating value consumers would silently introduce compiler regressions. The tool rejected the edit.
We next evaluated parameter bundling on hasProperty:
$ prod-code parameter-object --param map --param key --name HasPropertyArgs \
--path packages/typescript/src/internal/utils.ts hasProperty
`hasProperty` (packages/typescript/src/internal/utils.ts)
- was: (map: MapLike<any>, key: string)
- now: (hasPropertyArgs: HasPropertyArgs)
- 2 call site(s) rewritten, 2 use(s) in the body
the analyzer accepts the result: 0 errors
The tool synthesized export interface HasPropertyArgs, updated the signature, and rewired call sites to { map: member, key: "kind" } with zero errors.
Remote Pre-Flight Validation in Memory
Before writing changes to disk, prod-code validate verifies buffers in server RAM:
$ cat packages/typescript/src/internal/utils.ts | prod-code validate packages/typescript/src/internal/utils.ts
packages/typescript/src/internal/utils.ts: 0 error(s), 0 warning(s)
[prod-code] analysed in 5.32s
When evaluating an injected type error (export const typeErrorTest: number = 'invalid_string';):
$ (cat packages/typescript/src/internal/utils.ts && echo "export const typeErrorTest: number = 'invalid_string';") | prod-code validate packages/typescript/src/internal/utils.ts
packages/typescript/src/internal/utils.ts: 1 error(s), 0 warning(s)
error: Type 'string' is not assignable to type 'number'. [2322] (packages/typescript/src/internal/utils.ts:45:14)
[prod-code] analysed in 5.24s
The remote compiler caught the type violation in memory in 5.24s without altering git status or touching the local filesystem.
When a repository contains tens of thousands of generated files and complex namespace re-exports, never let an agent edit code using unverified regex transforms. Code intelligence must validate candidate AST modifications in server RAM before anything touches disk.
Cite this article
Alexander Panasenko (2026-09-30). TypeScript Under the Microscope: What 67 AST Tools Found Inside the Compiler. https://prod.codes/blog/typescript-under-the-microscope-67-ast-tools/