Skip to content

feat(ember-tsc): support Ember 7.1 built-in keywords#1109

Merged
NullVoxPopuli merged 8 commits into
typed-ember:mainfrom
aklkv:feat/ember-7.1-builtin-keywords
May 14, 2026
Merged

feat(ember-tsc): support Ember 7.1 built-in keywords#1109
NullVoxPopuli merged 8 commits into
typed-ember:mainfrom
aklkv:feat/ember-7.1-builtin-keywords

Conversation

@aklkv
Copy link
Copy Markdown
Contributor

@aklkv aklkv commented May 13, 2026

Adds support for the new built-in template keywords introduced in ember-source 7.1.0 (RFCs 562, 470, 997, 998, 999, 1000, 560, 561, 389): on, fn, array, hash, and, or, not, eq, neq, lt, lte, gt, gte, element.

These can be referenced in .gjs/.gts templates without an explicit import from @ember/helper or @ember/modifier, matching ember-source 7.1+ runtime behavior.

Detection is automatic — no consumer opt-in required:

  • The Keywords interface in types/-private/dsl/globals.d.ts probes '@ember/helper' for the EqHelper symbol (introduced in RFC 561, shipped in ember-source 7.1.0). When present, each new keyword resolves to a real HelperLike / ModifierLike / ComponentLike type. When absent, each resolves to 'never', so un-imported usage in a template fails to type-check on older Ember versions.

  • src/environment-ember-template-imports/-private/environment/index.ts: add the 14 keywords to the environment's globals list so the parser recognizes them.

Behavior matrix:

ember-source >= 7.1.0 -> full types; un-imported keywords type-check ember-source < 7.1.0 -> un-imported keywords resolve to 'never'; Glint reports a clear type error.
no @ember/helper -> keywords are enabled (fallback for non-Ember
Glimmer projects, gated by a @ts-ignore on
the probe import).

@aklkv aklkv force-pushed the feat/ember-7.1-builtin-keywords branch 2 times, most recently from 97b349b to d9b940d Compare May 13, 2026 05:14
Comment on lines +64 to +81
// Ember 7.1+ built-in keywords (RFCs 562, 470, 997, 998, 999, 1000, 560, 561, 389).
// Their template-side signatures live in
// `@glint/ember-tsc/globals/ember-7.1` (opt-in), and they only resolve
// at runtime on `ember-source >= 7.1.0`.
'on',
'fn',
'array',
'hash',
'and',
'or',
'not',
'eq',
'neq',
'lt',
'lte',
'gt',
'gte',
'element',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that the strings above (action to yield) are alphabetized. Can we alphabetize the list for Ember 7.1 similarly, and create variables so that it's easier to document and maintain the lists?

The code below is what I mean. (I sorted the RFC IDs, too. I wrote Ember7_1 but see that the name Ember71 is used in types/-private/dsl/globals.d.ts. Feel free to rename Ember7_1 to achieve consistency.)

export default function emberTemplateImportsEnvironment(
  options: Record<string, unknown>,
): GlintEnvironmentConfig {
  let additionalSpecialForms =
    typeof options['additionalSpecialForms'] === 'object'
      ? (options['additionalSpecialForms'] as GlintSpecialFormConfig)
      : {};

  const additionalGlobalSpecialForms = additionalSpecialForms.globals ?? {};

  const additionalGlobals = Array.isArray(options['additionalGlobals'])
    ? options['additionalGlobals']
    : [];

  const globalsForEmber = [
    'action',
    'component',
    'debugger',
    'each',
    'each-in',
    'has-block',
    'has-block-params',
    'helper',
    'if',
    'in-element',
    'let',
    'log',
    'modifier',
    'mount',
    'mut',
    'outlet',
    'unbound',
    'unless',
    'with',
    'yield',
  ];

  /*
    Ember 7.1+ built-in keywords (RFCs 389, 470, 560, 561, 562, 997, 998, 999, 1000).
    Their template-side signatures live in `@glint/ember-tsc/globals/ember-7.1` (opt-in),
    and they only resolve at runtime on `ember-source >= 7.1.0`.
  */
  const globalsForEmber7_1 = [
    'and',
    'array',
    'element',
    'eq',
    'fn',
    'gt',
    'gte',
    'hash',
    'lt',
    'lte',
    'neq',
    'not',
    'on',
    'or',
  ];

  return {
    tags: {
      '@glint/ember-tsc/environment-ember-template-imports/-private/tag': {
        hbs: {
          typesModule: '@glint/ember-tsc/-private/dsl',
          specialForms: {
            globals: {
              if: 'if',
              unless: 'if-not',
              yield: 'yield',
              component: 'bind-invokable',
              modifier: 'bind-invokable',
              helper: 'bind-invokable',
              ...additionalGlobalSpecialForms,
            },
            imports: {
              '@ember/helper': {
                array: 'array-literal',
                hash: 'object-literal',
                ...additionalSpecialForms.imports?.['@ember/helper'],
              },
              ...additionalSpecialForms.imports,
            },
          },
          globals: [
            ...globalsForEmber,
            ...globalsForEmber7_1,
            ...Object.keys(additionalGlobalSpecialForms),
            ...additionalGlobals,
          ],
        },
      },
    },
    extensions: {
      '.gts': {
        kind: 'typed-script',
        preprocess,
        transform,
      },
      '.gjs': {
        kind: 'untyped-script',
        preprocess,
        transform,
      },
    },
  };
}

Comment on lines +228 to +231
// ---- Ember 7.1+ built-in keywords ----
// These are gated on `HasEmber71BuiltIns` above. On `ember-source < 7.1.0`
// each one resolves to `never`, so un-imported usage in a template fails
// to type-check.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion

Similarly to https://github.com/typed-ember/glint/pull/1109/changes#r3231711703, maybe we can create temporary variables and sort keys to ease maintenance?

interface KeywordsForEmber {
  /* ... */
}

interface KeywordsForEmber7_1 {
  /* ... */
}

interface Keywords extends KeywordsForEmber, KeywordsForEmber7_1 {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@aklkv aklkv force-pushed the feat/ember-7.1-builtin-keywords branch 3 times, most recently from 349e11b to 9ad9fe0 Compare May 13, 2026 05:56
Adds support for the new built-in template keywords introduced in
ember-source 7.1.0 (RFCs 562, 470, 997, 998, 999, 1000, 560, 561, 389):
on, fn, array, hash, and, or, not, eq, neq, lt, lte, gt, gte, element.

These can be referenced in .gjs/.gts templates without an explicit import
from @ember/helper or @ember/modifier, matching ember-source 7.1+
runtime behavior.

Detection is automatic — no consumer opt-in required:

- The Keywords interface in types/-private/dsl/globals.d.ts probes
  '@ember/helper' for the EqHelper symbol (introduced in RFC 561,
  shipped in ember-source 7.1.0). When present, each new keyword
  resolves to a real HelperLike / ModifierLike / ComponentLike type.
  When absent, each resolves to 'never', so un-imported usage in a
  template fails to type-check on older Ember versions.

- src/environment-ember-template-imports/-private/environment/index.ts:
  add the 14 keywords to the environment's globals list so the parser
  recognizes them.

Behavior matrix:

  ember-source >= 7.1.0 -> full types; un-imported keywords type-check
  ember-source <  7.1.0 -> un-imported keywords resolve to 'never';
                           Glint reports a clear type error.
  no @ember/helper      -> keywords are enabled (fallback for non-Ember
                           Glimmer projects, gated by a @ts-ignore on
                           the probe import).
@aklkv aklkv force-pushed the feat/ember-7.1-builtin-keywords branch from 9ad9fe0 to dc2aa0a Compare May 13, 2026 08:39
NullVoxPopuli-ai-agent and others added 3 commits May 14, 2026 12:12
… fixture (#2)

The Ember 7.1 detection probe in `globals.d.ts` reads
`typeof import('@ember/helper')` and checks the resulting type for an
`EqHelper` key. But `@ember/helper` exports `EqHelper` only as an
`interface` — `typeof import(...)` surfaces only value exports, so
`keyof` never includes `EqHelper`. The probe therefore always
collapsed to `false`, `Ember71Only<T>` always resolved to `never`, and
every new keyword (`on`, `fn`, `eq`, …) ended up untyped. Switch the
probe to the matching `eq` value export added by RFC 561.

The previous home for the new keyword tests was `ts-template-imports-app`,
which pins `ember-source: ^6.2.0`. With the corrected probe those tests
still resolved to `never` against a 6.x ember-source. Bumping that
fixture to a 7.1 prerelease shifts the resolved peer set for
`@glint/ember-tsc` and reformats unrelated `Modifier`-overload
diagnostic snapshots in `package-test-core`.

Move the keyword tests into a new `ts-gts-7-1-app` fixture pinned to
`ember-source: ~7.1.0-beta.1`. The fixture stays minimal and exercises
the real ember-source 7.1 types end-to-end via the probe, without
disturbing the existing 6.x fixture's peer resolution.

Co-authored-by: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@NullVoxPopuli NullVoxPopuli added the enhancement New feature or request label May 14, 2026
@NullVoxPopuli NullVoxPopuli merged commit 32b7f74 into typed-ember:main May 14, 2026
4 checks passed
@github-actions github-actions Bot mentioned this pull request May 14, 2026
aklkv added a commit to aklkv/eslint-plugin-ember that referenced this pull request May 14, 2026
…lobals

ember-source 7.1.0 makes 14 helpers/modifiers (and, array, element, eq,
fn, gt, gte, hash, lt, lte, neq, not, on, or — RFCs 389, 470, 560, 561,
562, 997, 998, 999, 1000) available in strict-mode templates without an
explicit import.

ESLint's no-undef rule was therefore flagging them as undefined when
consumers turned off no-implicit-this etc. on .gjs/.gts files. Add the
keywords to both the modern flat-config exports (lib/recommended.mjs's
`gjs` and `gts` configs, used via `eslint-plugin-ember/recommended`) and
the legacy-style `./configs/base` entry in lib/config/base.js so they
are recognised as readonly globals on .gjs/.gts.

Detection is gated on the consumer's installed ember-source version via
a shared `getEmber71BuiltInKeywords()` helper (lib/utils/ember71-built-
in-keywords.js) so pre-7.1 projects keep their existing behaviour.
Mirrors Glint's KeywordsForEmber71 gate (typed-ember/glint#1109).
@aklkv aklkv deleted the feat/ember-7.1-builtin-keywords branch May 14, 2026 22:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants