Implement a TypeScript 7 content mapper for .gts/.gjs files - #1
Conversation
Native tsc (7.1 nightlies, --runExternalCode) can now type-check Ember <template> tags directly: the mapper speaks the JSON-RPC protocol from microsoft/typescript-go#4712, transforms each file with Glint's rewriteModule, flattens the Glimmer mapping tree into non-overlapping span mappings, and translates @glint-expect-error / @glint-ignore / @glint-nocheck into the protocol's native diagnostic directives. Structure follows remcohaszing/mdx-content-mapper. Tests are snapshot fixtures over the transform output plus integration suites type-checked by a real TypeScript 7 nightly: type-tests/ ports 18 expect-error / expectTypeOf-style files from typed-ember/glint, and diagnostics-tests/ locks in exact mapped error positions and unused-directive reporting. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
examples/nvp-app (pnpm dlx ember.nvp) and examples/cli-app (pnpm dlx ember-cli@latest new --typescript), each updated to a TypeScript 7.1 nightly with contentMappers registered in tsconfig.json and lint:types switched from ember-tsc to tsc --noEmit --runExternalCode. @glint/tsserver-plugin is dropped since it only loads into a TS 5/6 tsserver. Both apps type-check clean via pnpm test:examples (also in CI), and examples/README.md covers CLI and VS Code debugging. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Added |
Each example app now exercises the surfaces the mapper must handle: class components with signatures/blocks, template-only components, untyped .gjs with JSDoc signatures, custom modifiers, helper functions, .ts barrels importing .gts/.gjs, 7.1 keywords, trackedArray, and .gts rendering tests. Both apps type-check clean. Editor fix: the LSP surfaced hint-severity TS6133 for Glint's unused `const __glintY__ = emitElement(...)` declarations (the CLI never reports suggestions). TypeScript auto-suppresses declared-but-not-used diagnostics only in unmapped regions, so gap mappings now leave holes around those declaration names. examples/README.md documents Neovim setup: nvim-lspconfig's tsc with init_options.runExternalCode and glimmer-filetype language-id mapping; ember.nvim#4 automates it for contentMappers projects. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ackages test-packages/ holds byte-identical copies of five packages from typed-ember/glint at v1.10.0 (ts-template-imports-app, ts-gts-7-1-app, ts-special-forms-app, ts-special-forms-pre-7-1-app, ts-extensionless-app); only package.json and tsconfig.json are adapted. Their glint-expect-error and ts-expect-error directives, and the deliberate absence of directives elsewhere, are the assertions: test/typecheck.js compares each package's tsc --runExternalCode output against expected/<name>.txt. Three packages type-check clean; the recorded deviations (extensionless imports, .gjs declaration-file shadowing, per-process ember-source probing, reported handlebars parse errors) are documented in test-packages/README.md. Mapper fix motivated by the corpus: Glint's directives also suppress transform errors (a special-form arity error under @glint-expect-error), but TypeScript's diagnostic directives only cover its own bind/check diagnostics, so mapper diagnostics inside directive areas are filtered in the transform. When emission aborts before producing mapping nodes, the expect-error area is recovered from the placeholder comment plus Glint's next-line area-of-effect rule. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Two updates from debugging the examples in an editor: Neovim works now. The red squiggles came from the classic pipeline: ember.nvim attaches The LSP round also caught a mapper bug: hint-severity Also expanded both apps with the usage surfaces the mapper has to handle: class components with signatures and yielded blocks, template-only components, untyped |
There was a problem hiding this comment.
we should probably remove prettier/prettierignore, eslint, and stylelint from the test projects (keep them focused on tests)
There was a problem hiding this comment.
Removed in 12d5850: prettier, eslint, and stylelint configs, deps, and scripts are gone from both example apps; each keeps only lint:types.
There was a problem hiding this comment.
template lint should also not be in these test projects
There was a problem hiding this comment.
Removed along with the rest of the lint tooling in 12d5850.
|
Replaced the cherry-picked Results:
The corpus also drove a mapper fix: Glint's directives suppress transform errors too (e.g. And it surfaced an upstream bug: |
The registry was unreachable when test-packages/ landed (TLS validity window ahead of this machine's clock), so the lockfile missed the new importers and --frozen-lockfile installs failed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| }); | ||
| }); | ||
|
|
||
| describe('@volar/typescript proxyCreateProgram patches', () => { |
There was a problem hiding this comment.
why are we testing old volar / ts < 7 stuff?
patching ts isn't possible with ts7, so the comments above in this file confuse me
There was a problem hiding this comment.
Right — those tests (plus the *-fixture dirs and extra tsconfigs) are Glint's own vitest harness for its CLI (watch mode, build mode, the tsc source patches), not part of the expect-error corpus. Removed from the copy in 12d5850; test-packages/README.md notes why.
| @@ -0,0 +1,19 @@ | |||
| import '@glint/ember-tsc/types'; | |||
|
|
|||
| // On ember-source < 7.1, `and`/`or`/`not`/`eq`/`neq` are NOT built-in template | |||
There was a problem hiding this comment.
does this mean that consumers with ember-source < 7.1 will need to have this declare module in their app? previously, this was not needed.
why is it needed now, and can we delete this?
There was a problem hiding this comment.
No new requirement — that file is byte-identical to upstream Glint's (it ships in glint's own test-packages). It exists because this app opts into additionalSpecialForms operators on ember-source < 7.1: the transform emits a discarded Globals. reference for hover/goto on the keyword, and the declaration satisfies it. Consumers who don't use additionalSpecialForms never need it, same as under ember-tsc today. Deleting it here would diverge from the direct-copy rule (and break the package the same way it would break upstream).
- CI: lint and each test suite run as four parallel jobs (lint, test-unit, test-typecheck, test-examples). - Example apps: prettier, eslint, stylelint, and template-lint configs, deps, and scripts removed; the apps keep only lint:types. - test-packages: ts-extensionless-app's __tests__ and *-fixture directories removed; they drive Glint's own CLI harness (watch/build mode, tsc source patches), which does not apply to TypeScript 7. - lib/constants.js documents that the enum values are TypeScript's, copied from typescript-go#4712. - The package.json resolution workarounds reference typed-ember/glint#1222, which adds the export upstream. - README rewritten. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
typed-ember/glint#1222 shipped in 1.10.1, so the manifest resolves with require('@glint/ember-tsc/package.json') and the ember-source probe anchors its createRequire on the same export. The floor moves to ~1.10.1 everywhere so the export is guaranteed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| @@ -0,0 +1,61 @@ | |||
| # test-packages | |||
There was a problem hiding this comment.
rewrite this doc using your writing skills (load them all)
| "packageManager": "pnpm@11.33.0" | ||
| "dependencies": { | ||
| "@glint/ember-tsc": "~1.10.1", | ||
| "typescript": "~5.9.3", |
There was a problem hiding this comment.
why are we using typescript 5.9.3? this whole effort is for TS 7
There was a problem hiding this comment.
TypeScript 7 does all the type-checking of your project. The 5.9 dependency is the TypeScript JavaScript API that Glint's rewriteModule uses internally to parse the script parts of a .gts file before replacing the templates. The TS 7 npm package is the native Go compiler with no JavaScript API, and @glint/ember-tsc peers on >=5.6, so the mapper carries its own TS 5 purely as a parser. It never checks your code, and it can go once Glint's transform runs against a TS 7 API. Documented in the README ("Why does the mapper depend on TypeScript 5?") in eadbf0d.
There was a problem hiding this comment.
We may need no refactor glint so we don't need ts5 here
There was a problem hiding this comment.
Agreed, and it can be done without touching the existing callers. I analyzed what rewriteModule actually uses the TS AST for and filed typed-ember/glint#1224 with the details: template placement and class-member detection are already in content-tag's parse output, and the import analysis (lexical imports beating globals, glint#1113) only needs an import scanner like es-module-lexer, not a full TS parser. The issue proposes either a parallel entry point or a pluggable script-analysis seam, so the tsserver-plugin/Volar paths keep passing their TSLib unchanged and embedders like this mapper opt into the parser-free path. Once that lands, the typescript@5.9 dependency here disappears.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The newest marketplace build of the Native Preview extension (0.20260708.2) predates content mapper support (merged 2026-08-19): it does not send the runExternalCode opt-in, so VS Code cannot type-check gts through the mapper yet. The README now separates what works today (TS 7 server for plain ts/js via useTsgo, Glint extension disabled per workspace) from the steps that apply once a newer build ships. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Content mapper support works in VS Code with an extension built from typescript-go main: the client bundle is plain TypeScript and the native server comes from the published platform package, so no Go toolchain is needed. Verified against the 2026-08-25 nightly (which includes protocol round 2): all five test-packages produce output identical to the recorded expectations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Native
tsccan now type-check<template>tags: with this package registered undercontentMappersintsconfig.json, a TypeScript 7.1 nightly run with--runExternalCodetype-checks.gts/.gjsfiles directly and reports diagnostics at their original template positions. Noember-tsc, no Volar in the CLI path.{ "compilerOptions": { "types": ["ember-source/types", "@glint/ember-tsc/types"] }, "contentMappers": [ { "package": "ember-content-mapper", "extensions": [".gts", ".gjs"] } ] }Example output (from
diagnostics-tests/):How it works
lib/server.jsspeaks the JSON-RPC protocol from Content mappers microsoft/typescript-go#4712 over stdio (initialize/openProject/transform/closeProject), structured after remcohaszing/mdx-content-mapper.transformruns the published Glint transform (rewriteModulefrom@glint/ember-tsc@1.10), so the emitted TypeScript and its semantics are exactly what Glint's language server produces today.lib/util/mappings.jsflattens Glint's Glimmer mapping tree into the protocol's span mappings. Two impedance mismatches handled there:CodeInformationswitches becomeSpanMapFeaturebitmasks (e.g. Glint's twoelementTypestag-name lookups keep their hover-only / navigation-only split).module.directivesfor gts; the semantics are baked into per-nodeCodeInformation. The mapper recovers them (verification: falsenodes,verification.shouldReportclosures, placeholder comment nodes) and emits the protocol's native diagnostic directives:@glint-expect-errorbecomes an Expect region (TypeScript itself reports unused directives asglint2578),@glint-ignore/@glint-nocheckbecome Ignore regions, and Glint's Volar-era// @ts-expect-errorplaceholders and auto-import anchor are Ignore-covered so they can't leak TS2578/TS2688.openProjectdeclaresdynamicConfigand fingerprints the installedember-source(it decides whether the 7.1 built-in keywords are globals), watching itspackage.json.additionalGlobals/additionalSpecialFormspass throughcontentMappers[].optionswith option-path diagnostics for invalid values.Tests
fixtures/+test/fixtures.js: snapshot tests of transform output, span mappings, and directives per fixture (mdx-content-mapper's harness pattern; verbatim mappings are asserted byte-identical, virtual spans asserted disjoint).type-tests/: 18 files ported from typed-ember/glint (ts-gts-7-1-app,@glint/type-test's fccts suite) coveringexpectTypeOf/to.*assertions,@glint-expect-errorusage, 7.1 keyword narrowing, overload resolution, and curried generics.pnpm test:typecheckruns a realtypescript@7.1.0-devnightly over them with--runExternalCodeand requires zero diagnostics.diagnostics-tests/: intentionally broken projects whose exact tsc output (positions mapped into.gtssources, unused-directive reporting, content-tag parse errors) is locked in as expected output. Parse errors live in their own project because tsc skips the semantic phase when syntactic diagnostics exist.One ported file needed
{{!-- --}}comment form instead of{{! ... }}for decorative comments containing nested mustaches (array-keyword-preserve-literals.test.gts); published Glint 1.10 rejects the short form there.Known limitations
protocolVersionfield and the original-span overlap restriction, at which point the demotion pass can regain exact ranges for boilerplate-anchored diagnostics.🤖 Generated with Claude Code