Skip to content

DEV: Type-check with TypeScript 7 - #43406

Merged
davidtaylorhq merged 16 commits into
mainfrom
typescript-7
Oct 16, 2026
Merged

davidtaylorhq merged 16 commits into
mainfrom
typescript-7

Conversation

@davidtaylorhq

@davidtaylorhq davidtaylorhq commented Sep 8, 2026 •

Copy link
Copy Markdown
Member

This moves type-checking from Glint's ember-tsc to TypeScript 7. TypeScript 7 checks .gts and .gjs files itself, through ember-content-mapper. A full check of core, plugins and themes takes about 15 seconds and 3GB of memory. The Glint build needed an 8GB Node heap and several minutes.

  • typescript is now the TypeScript 7 nightly, and pnpm lint:types runs tsc -b --runExternalCode. ESLint runs on TypeScript 5.9 through lint-configs, and TypeScript 6 stays only for @glint/tsserver-plugin.
  • Bundled plugins and themes use @discourse/types from the workspace and reference the core project. One tsc -b run checks core and every plugin, and a core type change no longer needs a package release first.
  • The published @discourse/types works with both TypeScript 6 and 7, so plugins outside this repository can move over when they are ready.
  • The linting workflow checks that the TypeScript 7 language server resolves core imports from a plugin.
  • The nightly is pinned to 7.1.0-dev.20260930.4. Later nightlies fail in seven places in core because of Declaration emit errors (TS5088) on anonymous cyclic types that 6.0 elided to any microsoft/TypeScript#64656.
  • TypeScript 7 reports a few errors that Glint dropped because they landed on generated code. This fixes them.

For editors, install the TypeScript 7 extension (TypeScriptTeam.native-preview) and Glint 2 (1.4.0 or newer). Both are in the VS Code recommendations and the devcontainer. Editors on VS Code's built-in TypeScript server keep working through @glint/tsserver-plugin.

@davidtaylorhq
davidtaylorhq changed the base branch from main to relative-import-extensions September 11, 2026 17:00
@github-actions github-actions Bot removed the rte label Sep 11, 2026
@davidtaylorhq
davidtaylorhq force-pushed the relative-import-extensions branch 2 times, most recently from e78e7d2 to deb1063 Compare October 15, 2026 10:52
Base automatically changed from relative-import-extensions to main October 15, 2026 11:06
TypeScript 7 checks .gts and .gjs files through a content mapper instead of Glint, and its declaration emit names those files `foo.d.gts.ts` and `foo.d.gjs.ts`. This makes the published types package work with both compilers, so plugins can move over once core does.

- `tsconfig-plugin.json` registers `ember-content-mapper` for `.gts` and `.gjs`. TypeScript 6 ignores the `contentMappers` key.
- The package `exports` fall back to the `.d.gjs.ts` and `.d.gts.ts` declaration names when there is no plain `.d.ts`.
- Generated ambient stubs now export a namespace that a module only exposed as its default export. Without a name for `RSVP.Promise`, declaration emit expanded the type structurally for every plugin file that used a promise from rsvp. TypeScript 6 stops at a depth limit, but tsgo has no such limit and runs until it is out of memory.
- `ember-source/types/index.d.ts` is generated so that a `/// <reference types="ember-source/types" />` resolves inside the package.
TypeScript 7 (tsgo) replaces Glint's `ember-tsc` for type-checking. It type-checks `.gts` and `.gjs` files itself, using `ember-content-mapper` to transform the templates, and its own language server replaces the Glint tsserver plugin in editors.

The whole solution now checks in about 10 seconds and 3GB of memory, where the Glint build needed an 8GB Node heap and several minutes.

- `typescript-7` is an alias for the 7.1 nightly. `typescript` stays at 6.x because `typescript-eslint` does not accept 7 yet, so the scripts call the 7 binary by path.
- Every tsconfig registers the content mapper and adds `*.gjs`/`*.gts` fallbacks to its `paths`, because TypeScript 7 only resolves a mapped file when the specifier has the extension. Relative imports of `.gts` and `.gjs` modules now carry the extension.
- The VS Code recommendations gain the TypeScript (Native Preview) extension, which Glint 2 hands `.gts`/`.gjs` files to. `@glint/tsserver-plugin` stays configured for editors still on the built-in TypeScript 6 server, which needs it to resolve `.gts` imports from `.ts` files. TypeScript 7 does not load tsserver plugins, so it costs nothing there.
- `ajax` and `debounce` take rest parameters instead of reading `arguments`, and `d-otp` writes its callback types in TypeScript syntax, since TypeScript 7 infers neither the `arguments` object nor Closure-style function types.
- `d-close-on-click-outside` declares its signature. Glint dropped the arity error that `d-float-body` gets without it, because the error landed on generated text with no source position.
- The discourse-ai sentiment controller annotates a return type that could not be named from the plugin.
- ESLint ignores `*.d.gjs.ts` and `*.d.gts.ts`, the declaration names TypeScript 7 emits for mapped files.

Plugins and themes keep passing under the published `@discourse/types` until the package is republished with the content mapper configuration and the rsvp stub fix, so this depends on that release.
Bundled plugins and themes now take `@discourse/types` from the pnpm workspace and reference the core project, so `tsc -b` builds core declarations first and checks every plugin against them in the same run. A core type change no longer needs a package release and a version bump before plugins see it, and the language server resolves core imports straight to core source.

- Every plugin and theme depends on `discourse` as `workspace:@discourse/types@*` and lists `frontend/discourse` in `references`.
- The shared plugin config writes each project's build info next to its own `tsconfig.json`, since all of them used to share one path under `declarations`.
- `pnpm install` regenerates the external type stubs through a `prepare` script, so a fresh checkout has them before the first type check.
- The publish workflow keeps publishing `@discourse/types` for plugins outside this repository and no longer opens a bump PR.
- An integrity spec drives the TypeScript language server from a plugin file and checks that "go to definition" on core `.ts`, `.js`, `.gts` and `.gjs` imports lands in core source.
`typescript` now resolves to the TypeScript 7 nightly, so `tsc` is tsgo across the repository and the scripts no longer need the `typescript-7` alias. TypeScript 6 stays only where a tool uses the TypeScript JavaScript API, which TypeScript 7 does not ship.

- ESLint keeps TypeScript 6 through `@discourse/lint-configs`, which depends on it directly. A package extension gives lint-configs its own `@typescript-eslint/parser`. Without it, the parser that `ember-eslint-parser` asks for as a peer resolved `typescript` from the root.
- The `typescript` override now only applies to `@glint/tsserver-plugin`, which still runs inside the TypeScript 6 server in editors. The repository-wide override also rewrote peer ranges, which hid that `eslint-plugin-tsdoc` pins an older `@typescript-eslint/utils` that does not accept TypeScript 6. That plugin now uses the same `@typescript-eslint/utils` as the rest of lint-configs.
- `discourse-i18n` emits its declarations with `tsc` instead of Glint's `ember-tsc`.
- `@discourse/types` has TypeScript 6 as a dev dependency, because `dts-generator.ts` uses the compiler API.
- Glint's `ember-tsc` now gets TypeScript 7 as its peer. The content mapper only uses its template transform, which does not load `typescript`.
`dts-generator.ts` now reads declaration files through the TypeScript 7 API, so `@discourse/types` no longer needs TypeScript 6. The generated stubs are byte-for-byte the same as before.

- The generator only ever needs to wrap and rewrite `.d.ts` files. A few packages also ship `.ts` sources, but the declarations emitted from them never reached the output, so that path is gone along with its diagnostics handling.
- `isDeclaredExternalModule` is gone too, since `generate-external-types.js` never read it.
- TypeScript 7 has no JavaScript parser, so the generator starts a tsgo process through `typescript/unstable/sync` and reads each file's syntax tree from it. That API is marked unstable and may change between TypeScript 7 releases.
`typescript` moves to `7.1.0-dev.20260930.4`, `@glint/ember-tsc` to 1.11.6 and `@glint/tsserver-plugin` to 2.7.9. `ember-content-mapper` 0.3.1 and `@glint/template` 1.9.0 are already the latest releases.

- This is the newest nightly without microsoft/TypeScript#64656. From `7.1.0-dev.20261001.1` on, declaration emit fails with TS5088 on an inferred type that refers to itself, where earlier versions wrote `any` at the cycle. Seven places in core hit it.
- In newer nightlies, the sync API's `createProgram` takes the compiler options as its second argument. `dts-generator.ts` now passes an empty object there, which works with both forms.
The backend job deletes `node_modules` before running RSpec, so the language server spec had no `tsc` to start and failed with `EOFError`. The same check is now a `node:test` file in `@discourse/types`, which runs in the linting workflow next to the type check, where `node_modules` is present.

- It opens a plugin file in the TypeScript 7 language server and checks that "go to definition" on core `.ts`, `.js`, `.gts` and `.gjs` imports lands in core source, and that the file has no diagnostics.
- When the server exits early, the failure includes the server's stderr.
- `pnpm --dir=frontend/discourse-types test` runs it locally.
…ainer

`.vscode/extensions.json` recommends TypeScript (Native Preview), but the devcontainer's own extension list did not include it. Editors in the devcontainer stayed on VS Code's built-in TypeScript 6 server instead of TypeScript 7's language server, which Glint 2 hands `.gts` and `.gjs` files to.
The VS Code recommendations, the devcontainer and the types guide named the extension `TypeScript.native-preview`, which does not exist. Its ID is `TypeScriptTeam.native-preview`, and the Marketplace now calls it TypeScript 7, so the guide uses that name.
`block-outlet.gts` passes the outlet info debug component a default block, but its copy of that component's signature declared no blocks. With strict mode on, TypeScript 7 reports this as an implicit `any` at both call sites. Glint's `ember-tsc` drops the error, because it lands on generated code with no source position.
lint-configs 3.8.1 lists `@typescript-eslint/parser` as its own dependency, so ESLint's parser now finds the TypeScript 6 that lint-configs installs, with no help from core. The `packageExtensions` entry that added the parser for it is gone.
The JSDoc typedef described the positional args as `Args.Positional.0` and `[Args.Positional.1]`. TypeScript 7 writes those into the declaration file as properties with no name, which TypeScript 5 and 6 cannot parse. Themes and plugins include every declaration file from `@discourse/types`, so this one file failed their whole type check. A labeled tuple gives the same types and keeps the descriptions in the labels.
lint-configs 3.8.2 lints with TypeScript 5.9, which the `@typescript-eslint/utils` version that `eslint-plugin-tsdoc` pins accepts. The override that moved that plugin onto a newer `@typescript-eslint/utils` is gone.
@davidtaylorhq
davidtaylorhq marked this pull request as ready for review October 16, 2026 12:33
@davidtaylorhq
davidtaylorhq requested a review from a team as a code owner October 16, 2026 12:33
@davidtaylorhq
davidtaylorhq requested a review from cvx October 16, 2026 12:33
`ember-tsc` could not find `@glimmer/manager`, so the blocks decorator declared the two functions it uses in an ambient shim. TypeScript 7 resolves the module from the stable types in `ember-source`, so the shim is unused.
The skill described `pnpm lint:types` as `ember-tsc -b`, and its conversion reference named `ember-tsc` as the check that misses runtime module-resolution errors. Both now name `tsc`. The guidance itself is unchanged.
@davidtaylorhq
davidtaylorhq merged commit 570ffdd into main Oct 16, 2026
23 checks passed
@davidtaylorhq
davidtaylorhq deleted the typescript-7 branch October 16, 2026 13:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment