Reach the TypeScript 7 AST and program through unstable/ast - #8691
Closed
knutwannheden wants to merge 2 commits into
Closed
Reach the TypeScript 7 AST and program through unstable/ast#8691knutwannheden wants to merge 2 commits into
knutwannheden wants to merge 2 commits into
Conversation
TypeScript 7 ships no compiler API: `typescript`'s main entry is a version string, and the AST, checker and program live behind `unstable/*` subpath exports. This adds the two pieces the parser will need there, each covered by a conformance test against the TypeScript 6 tree it has to reproduce. `ts7/token-navigation.ts` rebuilds getChildren/getChildAt/getChildCount/ getFirstToken/getLastToken, which 7 does not expose, from forEachChild, createScanner and the source text. Across the parser's own 26 sources the reconstructed token stream is identical to TypeScript 6's native one -- kinds, order and offsets -- once two equivalences are accounted for: `EndOfFileToken` is renamed `EndOfFile`, and an elision in a binding pattern is a zero-width `BindingElement` rather than an `OmittedExpression`. `ts7/program.ts` replaces createProgram and the CompilerHost. The Go process owns parsing and resolution, so in-memory sources are served over filesystem callbacks and everything else defers to the real filesystem; node_modules and the bundled lib files resolve without a module-resolution hook. The compiler is on `module: node20`, since 7 is ESM-only and this package is CommonJS. `node16` reports TS1479 on every `unstable/*` import; `node20` permits require(esm) and leaves the emitted output CommonJS. TypeScript 6 stays the compiler the parser runs on. The visitor cannot span both, because kind numbers differ and it compares against named members of one enum.
`module: node20` reaches the ESM-only `typescript7` from this CommonJS package via require(esm), which Node supports from 22.12. The published output is unaffected: with the ts7 modules excluded, the emit under node20 is byte-identical to node16, so the floor constrains building and testing rather than consumers of the package. The ts7 modules were reaching dist while `typescript7` is a devDependency, which would leave an unresolvable require in the published package. Only the conformance test loads them, so the build skips them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on Run the JavaScript parser on TypeScript 7 #8690, which is stacked on Upgrade rewrite-javascript to TypeScript 6.0 #8689 — review those first.
TypeScript 7.0 ships no compiler API.
typescript's main entry islib/version.cjs, which exports a version string; the AST, checker and program construction live behindunstable/*subpath exports that the 7.1 iteration plan does not schedule for stabilisation. This adds the two pieces the parser will need from there, each pinned by a conformance test against the TypeScript 6 tree it has to reproduce. The parser still runs on 6.Token navigation
ts7/token-navigation.tsrebuilds the five calls 7 does not expose, fromforEachChild(aNodemethod in 7, not a free function),createScanner, and the source text. Across the parser's own 26 sources the reconstructed token stream is identical to TypeScript 6's native one — kinds, order and offsets — once two equivalences hold:EndOfFileTokenis renamedEndOfFile.const [a, , b] = …) is a zero-widthBindingElementin 7 where 6 emits anOmittedExpression. The equivalence is conditional on zero width; a non-emptyBindingElementmeans the same thing in both.Both are recorded in the test rather than skipped, so a deviation that is not in that table fails.
Four things had to be right for the offsets to line up, and each was found by the test rather than by reading:
posis its full start, sogetTokenFullStart()is the source, notgetTokenStart().|of| "a" | "b"disappears.Symbol. Tagging it with anelementsfield silently captured real nodes —NamedImports,ArrayLiteralExpressionand others already haveelements, so their braces and brackets vanished from the stream.jsDocParsingMode: ParseNone, so JSDoc never enters the tree. In 7, JSDoc is reachable only throughnode.jsDocand never throughforEachChild, and malformed JSDoc raises no syntactic diagnostic — which was the reasonParseNonewas set. The 44visitJSDoc*methods inparser.tsare unreachable under both compilers.Program construction
ts7/program.tsreplacescreateProgramand theCompilerHost, neither of which exists in 7. The Go process owns parsing and module resolution, so a program is described to it: in-memory sources are served over filesystem callbacks, and returningundefineddefers to the real filesystem. An in-memory file importing another in-memory file, and one importing@types/nodefrom realnode_modules, both resolve with full type attribution and no diagnostics.resolveModuleNameLiterals, whichparser.tsoverrides today because its sources are not on disk, has no counterpart to port.Why
module: node20TypeScript 7 is ESM-only and this package is CommonJS. Under
node16everyunstable/*import is TS1479 (cannot be imported with 'require').node20permitsrequire(esm), which Node supports from 22.12.That floor is now declared —
engines.nodeand the module's CLAUDE.md, which said18+. It constrains building and testing rather than consumers: with the ts7 modules excluded from the build, the emit undernode20is byte-identical tonode16, so nothing about the published output changes.The ts7 modules were reaching
distwhiletypescript7is a devDependency, which would have left an unresolvablerequirein the published package. Only the conformance test loads them, sotsconfig.build.jsonskips them.What this deliberately does not do
The parser still runs on TypeScript 6. A visitor cannot span both compilers: kind numbers differ, and it compares
node.kindagainst named members of one enum. Switching is therefore all-at-once, behind a single module that re-exportsSyntaxKindand the type guards. Encouragingly all 109SyntaxKindmembers and all theisXxxguards the parser uses exist in 7, three under consistency renames (isParameter→isParameterDeclaration, and the same forisMethodSignature/isPropertySignature).Node field drift is likewise small: 87 interfaces are field-identical, and the real changes are
ImportAttributes.elements→.attributes,TypeParameterDeclaration.default→.defaultType,Identifier.escapedText→.text, andImportClause.isTypeOnly: boolean→.phaseModifier, an enum carryingimport defer/import source. Only the last needs more than a rename.The checker is not ported here.
Full suite: 2034 passed, 24 skipped, 0 failed.