Add a TypeScript 7 backend for Glint type extraction - #48
Conversation
Type information now comes from one of two backends behind the same extraction algorithm: - ts6: the project's typescript 5/6 as a library plus Glint's rewriteModule (the existing pipeline, moved to lib/backend/ts6.ts). - tsgo: typescript/unstable/sync, TypeScript 7's synchronous IPC API. The project is opened with runExternalCode so the tsconfig's contentMappers transform .gts files inside the compiler; spanMap maps the template back. No Glint rewrite and no typescript library run in this process. Selected when the tsconfig declares contentMappers and a TypeScript 7 package resolves; HVE_TS_BACKEND forces either. lib/glint.ts and lib/resolver/* see a TsSyntax facade instead of the typescript module. The ts6 backend always adds ember-source/types and @glint/ember-tsc/types to the program so projects that dropped them for the content mapper keep their types. The cache key includes the backend. Both vitest lanes (pnpm test, pnpm test:tsgo) pass. On a 244-template app the backends agree on every string-literal narrowing and component substitution; tsgo resolves eight sites the TS6 program reported as any. Cowritten by Claude
There was a problem hiding this comment.
🔵 Needs a closer look
It introduces a large new backend built on an unstable TypeScript 7 dev API with subtle snapshot-lifetime and span-mapping semantics that cannot be fully verified from the diff and warrant human review.
Pull request overview
This PR adds a second type-extraction backend so the plugin can perform type-aware Ember/Glint linting on TypeScript 7 (tsgo), which ships no in-process library API. It refactors the previously monolithic lib/glint.ts into a shared extraction algorithm over a TypeBackend abstraction (lib/backend/), with the existing TS5/6 + Glint rewriteModule pipeline moved to ts6.ts and a new tsgo.ts backend built on typescript/unstable/sync. A TsSyntax facade replaces direct typeof TS usage across the resolver so the syntactic walks work against either backend.
Changes:
- Introduce
lib/backend/(types.ts,ts6.ts,tsgo.ts,index.ts) with per-tsconfig backend selection driven bycontentMappersdetection, TS7 package resolution, andHVE_TS_BACKEND/HVE_TSGOoverrides. - Refactor
lib/glint.tsand the resolver to consumeTemplateSites and theTsSyntax/CheckerLike/ProgramLikeinterfaces instead of thetypescriptmodule directly; add the backend to the disk-cache key. - Add the
test:tsgosuite (vitest.tsgo.config.ts), thetypescript-7/ember-content-mapperdev deps, example/fixture tsconfigs declaringcontentMappers, and README docs.
File summaries
| File | Description |
|---|---|
| lib/backend/types.ts | New shared interfaces (TsSyntax, TypeBackend, TemplateSite, etc.) |
| lib/backend/index.ts | Backend selection, tsconfig discovery, syntax fallback |
| lib/backend/ts6.ts | Existing pipeline extracted; always adds ember/glint types |
| lib/backend/tsgo.ts | New TS7 IPC backend: snapshots, span mapping, site collection |
| lib/glint.ts | Refactored to iterate backend sites; adds Element: null handling |
| lib/cache.ts | Adds backend to cache entry + read/write signatures |
| lib/resolver/walk.ts, template-source.ts, build-maps.ts | Switch to TsSyntax/syntaxFor |
| lib/cache.ts, run.ts | Backend threaded through cache key and summary line |
| test/*.ts, *fixtures | Updated call signatures, backend-selection test, contentMappers fixtures |
| package.json, pnpm-lock.yaml | New deps, test:tsgo script, optional typescript peer |
| README.md, examples/tsconfig.json, vitest*.config.ts | Docs and dual-backend test config |
Review details
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
- Files reviewed: 21/22 changed files
- Comments generated: 2
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- findInnerTypeAtRange: never climb through an object literal. A literal
or plain path inside a component's named-args object (`{ size: "lg" }`)
belongs to that invocation, not to a wrapper of its own; the fallback
could widen to the whole invocation and return a sibling argument's
type under the literal's key. Regression fixture.
- tsgo parseFile cache: one entry per virtual name, replaced when the
buffer changes, instead of one per (name, contents) forever.
- Backends expose dispose(); closeBackends() releases every compiler
process and snapshot for hosts that outlive one run.
- One content-tag parse per opened file instead of two.
Cowritten by Claude
…thor name Cowritten by Claude
Type-aware linting on TypeScript 7. TS7 ships no in-process API, so the existing pipeline (
typescriptas a library + Glint'srewriteModule) cannot run on it. TS7'stypescript/unstable/syncis a synchronous IPC API, which is what html-validate's synchronous transformer needs.Design
One extraction algorithm (
lib/glint.ts) over two backends (lib/backend/):lib/backend/ts6.ts. It now always addsember-source/types/@glint/ember-tsc/typesto its program, so projects that dropped them fromcompilerOptions.typesfor the content mapper keep their types.lib/backend/tsgo.ts. Opens the project withrunExternalCode, so the tsconfig'scontentMappers(ember-content-mapper) transform.gtsinside the compiler;sourceFile.spanMapmaps template ranges to the virtual text. No Glint rewrite, notypescriptlibrary in-process. The resolver's syntactic parses use the same API through anfsoverlay.TsSyntaxfacade (predicates, enum tables,parseFile, union/literal helpers) replacestypeof TSinlib/resolver/*; boundary casts are confined totsgo.ts.contentMappersand a TS7 package resolves (typescript7.x,@typescript/native,typescript-7, orHVE_TSGO=<name>);HVE_TS_BACKEND=tsgo|ts6forces. Node 22.12+ forrequire()of the ESM API. Cache entries carry the backend.Verification
pnpm test(ts6) andpnpm test:tsgo(same suite,vitest.tsgo.config.ts): 289 passed, 1 expected fail each.vitest.config.tsexcludesecosystem/**.dist/: 23 string-literal unions and 24 literals identical as sets,componentAttrMap272/272,componentTagMap617 identical + 8 where tsgo resolves a real tag the TS6 program reported asany, 0 missing.Behaviour differences
values[0]may be a different (equally valid) member than under TS6.tsc;examples/got its own tsconfig.Elementisnullunder tsgo (unknownunder TS6); both now resolve to transparent.Cowritten by Claude