@@ -16,3 +16,195 @@ To debug the parser script:
1616
17172 . Open VSCode and choose "Debug: Attach to Node process" from the command palette, and
1818 choose the process that looks like the parser-wrapper.
19+
20+ Building and running
21+ --------------------
22+
23+ The wrapper uses the exact ` typescript@7.1.0-dev.20261015.1 ` package for both
24+ compilation/type checking and the native runtime. Its integrity and those of all
25+ platform-specific optional dependencies are recorded in ` package-lock.json ` .
26+
27+ ``` sh
28+ npm ci
29+ npm run build
30+ npm test
31+ node build/main.js --version
32+ ```
33+
34+ Node.js 16.20.0 is the minimum supported runtime, matching the pinned TypeScript
35+ package's engine requirement. The existing Bazel toolchain uses Node.js 18.15.0
36+ and does not need an upgrade. Build scripts invoke TypeScript's
37+ ` lib/tsc.js ` launcher explicitly because these Node.js versions cannot load its
38+ extensionless ESM ` bin/tsc ` entry point. The installed wrapper must include its
39+ ` package.json ` to retain the ESM package boundary.
40+
41+ The existing TSLint rules require the legacy JavaScript compiler API, not the
42+ native TypeScript 7 API. If compatible TSLint tooling is not already installed,
43+ run it separately without replacing the wrapper's build/runtime dependency:
44+
45+ ``` sh
46+ npm exec --yes --package=tslint@6.1.3 --package=typescript@5.9.2 -- tslint --project .
47+ ```
48+
49+ ` SEMMLE_TYPESCRIPT_HOME ` still overrides the runtime dependency. It must point to
50+ the installed TypeScript package directory (containing ` package.json ` ), with the
51+ native ` typescript/sync ` and ` typescript/ast ` entry points and matching executable.
52+ A legacy 5.x module or a directory from which the bundled package merely happens
53+ to be resolvable is not a valid override.
54+
55+ Distribution packaging
56+ ----------------------
57+
58+ The Bazel build produces two archives: ` :typescript ` (` javascript.zip ` ) contains
59+ the compiled ESM wrapper and JavaScript API; ` :native ` (` native.zip ` ) contains the
60+ matching native packages for the target CodeQL distribution:
61+
62+ | Distribution | Native packages |
63+ | --- | --- |
64+ | ` linux64 ` | ` @typescript/typescript-linux-x64 ` |
65+ | ` linux-arm64 ` | ` @typescript/typescript-linux-arm64 ` |
66+ | ` osx64 ` | ` @typescript/typescript-darwin-arm64 ` and ` @typescript/typescript-darwin-x64 ` |
67+ | ` win64 ` | ` @typescript/typescript-win32-x64 ` |
68+
69+ The build installs the selected packages explicitly with ` --force --no-save ` ,
70+ using the pinned versions and integrities in the lockfile. Merely passing
71+ ` --force ` to ` npm ci ` still filters optional dependencies by the build host.
72+ ` package-runtime.mjs ` verifies the native package versions and marks executables
73+ as ` 0755 ` before ZIP creation. The common CodeQL archive contains the wrapper;
74+ the architecture archive installs native packages into
75+ ` tools/typescript-parser-wrapper/node_modules/@typescript ` . Both archives must
76+ be installed, with executable permissions preserved. No separate TypeScript
77+ installation is needed.
78+
79+ Run the installed-package smoke test with:
80+
81+ ``` sh
82+ bazel test //javascript/extractor/lib/typescript:package-test
83+ ```
84+
85+ It checks all distribution selections, mismatched runtime rejection, common/arch
86+ ZIP separation and executable metadata, then installs the merged archives with
87+ the production ` ripunzip ` and parses TS, TSX, and malformed source through the
88+ installed JSON protocol. It runs on the host platform and uses Bazel's Node.js
89+ runtime. The Java extractor test suite also installs both ZIPs and restores
90+ executable permissions using their central-directory metadata.
91+
92+ All four distribution runtime ZIPs have been cross-assembled and inspected.
93+ Installed parsing has been verified with Node.js 18.15.0 on macOS arm64 and on
94+ macOS x64 through Rosetta. Linux x64, Linux arm64, and Windows x64 still require
95+ execution on matching hosts; cross-assembling their ZIPs is not execution coverage.
96+
97+ Native lifecycle and memory
98+ ---------------------------
99+
100+ Each file owns a fresh native API/process. AST projection and JSON serialization
101+ finish before the program is disposed and the API is closed, including on
102+ failure. No native process or compiler cache is retained between files, during
103+ stdout backpressure, or across reset/quit. This deliberately favors bounded
104+ resource lifetime over reusing a compiler process; the measured cost is described
105+ below.
106+
107+ ` SEMMLE_TYPESCRIPT_MEMORY_THRESHOLD ` remains a positive megabyte budget, defaulting
108+ to 1000 when the Java extractor does not supply it. At native-process creation,
109+ the wrapper subtracts Node's current RSS (not just its V8 heap) from that budget
110+ and passes the remaining bytes, with a minimum of one, as the child's
111+ ` GOMEMLIMIT ` . The parent's original ` GOMEMLIMIT ` is restored after spawning.
112+ This is a Go garbage-collector soft limit, not a hard combined RSS ceiling:
113+ Node allocations during extraction, native non-Go allocations, and oversized
114+ individual files can exceed the budget. Closing the native process after every
115+ file prevents native caches and heap capacity from accumulating across files.
116+ Node RSS exceeding the budget produces a warning on stderr.
117+
118+ Native stderr is inherited separately from the JSON stdout protocol. Syntax
119+ diagnostics remain successful AST responses; native, I/O, and protocol failures
120+ are reported on stderr and terminate the wrapper with a nonzero exit status.
121+ Reset, quit, EOF, and failure invalidate queued prefetch work; quit also closes
122+ stdin without requiring the sender to close its pipe.
123+
124+ AST projection and regression tests
125+ -----------------------------------
126+
127+ The wrapper targets the native ` typescript/sync ` and ` typescript/ast ` entry
128+ points, using the same pinned dependency in the production ESM build.
129+ ` npm test ` builds the production wrapper with the pinned compiler and runs
130+ ` test/wrapper.test.mjs ` and ` test/lifecycle.test.mjs ` against its compiled output,
131+ copied with its ESM package boundary into temporary installations. No separate
132+ probe installation or legacy compiler is needed for these tests.
133+
134+ AST nodes are explicitly projected into plain objects before their native
135+ program is disposed. The projection preserves the Java converter's property
136+ names, including optional member tokens, type-parameter defaults, import
137+ attributes, and namespace flags. EOF metadata retains the ` EndOfFileToken `
138+ spelling; the native ` TypeOfExpression ` , ` TypeOfKeyword ` , ` InstanceOfKeyword ` ,
139+ and ` KeyOfKeyword ` names already match the Java converter.
140+
141+ The tests cover AST projection, metadata, UTF-16 source positions, syntax
142+ diagnostics, TSX, repeated parses, native process disposal, the child memory
143+ budget, reset/quit/EOF, cancelled prefetch, runtime overrides, and fatal native
144+ failures. Run them with Node.js 16.20.0 to verify the minimum supported runtime.
145+
146+ Compatibility and regression coverage
147+ ------------------------------------
148+
149+ Native interface ` extends ` and class ` implements ` elements are type references.
150+ The wrapper projects them into the expression-shaped heritage nodes consumed by
151+ the Java converter, preserving generic arguments and qualified names.
152+
153+ The native parser still produces ASTs for legacy ` assert { ... } ` import
154+ assertions, but adds diagnostic 2880. The wrapper excludes only that diagnostic
155+ to preserve previously accepted syntax; genuine syntax diagnostics are unchanged.
156+ Import-attribute keys and values are now explicitly serialized. Their absence
157+ from the old wrapper's JSON was a bug: the Java converter expects ` key ` /` value ` ,
158+ but the old filter exposed neither correctly. The corresponding TRAP expectation
159+ was regenerated; existing QL expectations did not need changes.
160+
161+ Rescanning preserves token text and offsets while retaining the original
162+ pre-rescan token kinds on the wire. In particular, regex and template-tail tokens
163+ keep the classifications expected by existing TRAP tests.
164+
165+ Source-phase imports (` import source wasm from "./module.wasm" ` ) are accepted.
166+ Their declaration, imported path, and variable bindings use the existing import
167+ representation; the schema does not yet distinguish the source phase. The new
168+ ` TypeScript/SourceImports ` QL test covers these bindings.
169+
170+ Validation on October 19, 2026 passed the pinned-compiler build/type check,
171+ existing TSLint rules, parser compatibility and lifecycle tests,
172+ 500 Java tests, 68 TypeScript QL tests, the syntax-error query test, both extraction
173+ diagnostic integration tests, and the installed-package smoke test. The
174+ internal-error integration test deliberately terminates the native child rather
175+ than relying on the old parser's stack overflow.
176+
177+ All 19 production-output regression tests also pass on Node.js 16.20.0, the
178+ minimum supported runtime.
179+
180+ For QL tests, install the newly built extractor and explicitly select it; otherwise
181+ the CLI may silently exercise an older installed extractor:
182+
183+ ``` sh
184+ # From the repository root, with the Java extractor build dependencies available:
185+ bazel run //javascript:install
186+ codeql resolve languages --search-path=javascript/extractor-pack:. --format=json
187+ codeql test run -j4 --search-path=javascript/extractor-pack:. \
188+ javascript/ql/test/library-tests/TypeScript \
189+ javascript/ql/test/query-tests/LanguageFeatures/SyntaxError
190+ ```
191+
192+ Performance comparison
193+ ----------------------
194+
195+ On macOS arm64 with Node.js 18.15.0, a comparison against the pre-migration
196+ wrapper from ` 1971c368e1d ` compiled with TypeScript 5.9.2 gave these medians over three
197+ runs:
198+
199+ | Workload | 5.9.2 time | 7.1 time | 5.9.2 combined RSS | 7.1 combined RSS |
200+ | --- | --- | --- | --- | --- |
201+ | 103 TypeScript fixtures (33,589 bytes) | 0.237 s | 1.349 s | 124.1 MiB | 113.9 MiB |
202+ | Five parses of TypeScript's 588,128-byte declaration file | 0.991 s | 1.116 s | 269.3 MiB | 249.1 MiB |
203+
204+ Timing covers prepare-files, extraction, serialization, and protocol responses
205+ after the metadata handshake, not Java conversion or database import. Combined
206+ RSS is the maximum sampled sum of Node and its native descendants, sampled every
207+ 5 ms; short-lived peaks may be missed. The per-file native process costs about
208+ 5.7x on tiny files and 1.13x on the declaration workload, while measured memory is
209+ slightly lower. The bounded lifecycle is retained deliberately; this migration
210+ does not claim a speedup.
0 commit comments