@glint/ember-tsc 1.8.1 → 1.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/lib/config/environment.d.ts +7 -6
  2. package/lib/config/environment.d.ts.map +1 -1
  3. package/lib/config/environment.js +8 -23
  4. package/lib/config/environment.js.map +1 -1
  5. package/lib/config/types.d.cts +8 -7
  6. package/lib/config/types.d.cts.map +1 -1
  7. package/lib/environment-ember-template-imports/-private/environment/index.d.ts.map +1 -1
  8. package/lib/environment-ember-template-imports/-private/environment/index.js +33 -28
  9. package/lib/environment-ember-template-imports/-private/environment/index.js.map +1 -1
  10. package/lib/environment-ember-template-imports/-private/environment/preprocess.d.ts.map +1 -1
  11. package/lib/environment-ember-template-imports/-private/environment/preprocess.js +18 -4
  12. package/lib/environment-ember-template-imports/-private/environment/preprocess.js.map +1 -1
  13. package/lib/environment-ember-template-imports/-private/environment/transform.d.ts.map +1 -1
  14. package/lib/environment-ember-template-imports/-private/environment/transform.js +39 -34
  15. package/lib/environment-ember-template-imports/-private/environment/transform.js.map +1 -1
  16. package/lib/transform/template/inlining/tagged-strings.js +16 -10
  17. package/lib/transform/template/inlining/tagged-strings.js.map +1 -1
  18. package/lib/transform/template/rewrite-module.js +6 -6
  19. package/lib/transform/template/rewrite-module.js.map +1 -1
  20. package/lib/transform/template/template-to-typescript.d.ts.map +1 -1
  21. package/lib/transform/template/template-to-typescript.js +9 -1
  22. package/lib/transform/template/template-to-typescript.js.map +1 -1
  23. package/package.json +5 -1
  24. package/src/config/environment.ts +10 -29
  25. package/src/config/types.cts +8 -8
  26. package/src/environment-ember-template-imports/-private/environment/index.ts +33 -28
  27. package/src/environment-ember-template-imports/-private/environment/preprocess.ts +19 -4
  28. package/src/environment-ember-template-imports/-private/environment/transform.ts +39 -43
  29. package/src/transform/template/inlining/tagged-strings.ts +21 -15
  30. package/src/transform/template/rewrite-module.ts +6 -6
  31. package/src/transform/template/template-to-typescript.ts +11 -1
  32. package/types/-private/dsl/globals.d.ts +5 -10
  33. package/types/-private/intrinsics/array-hash.d.ts +2 -2
  34. package/types/-private/intrinsics/comparison.d.ts +7 -14
  35. package/types/-private/intrinsics/element.d.ts +22 -2
  36. package/types/-private/intrinsics/get.d.ts +8 -4
  37. package/types/-private/intrinsics/truth-helpers.d.ts +26 -9
@@ -2,7 +2,7 @@ import {
2
2
  GlintEmitMetadata,
3
3
  GlintSpecialForm,
4
4
  GlintSpecialFormConfig,
5
- GlintTagConfig,
5
+ GlintTemplateConfig,
6
6
  } from '@glint/ember-tsc/config-types';
7
7
  import type ts from 'typescript';
8
8
  import { GlintEnvironment } from '../../../config/index.js';
@@ -66,10 +66,10 @@ export function calculateTaggedTemplateSpans(
66
66
  suffix: script.contents.slice(templateLocation.contentEnd, templateLocation.end),
67
67
  };
68
68
 
69
- let preamble = [];
70
- if (!info.importedBinding.synthetic) {
71
- preamble.push(`${tag.text};`);
72
- }
69
+ // The `<template>` form's backing import is always compiler-synthesized
70
+ // (see `resolveTagInfo`), so there is never a user-written tag binding to
71
+ // reference in a preamble.
72
+ let preamble: Array<string> = [];
73
73
 
74
74
  let specialForms = collectSpecialForms(importedBindings, info.tagConfig.specialForms ?? {});
75
75
 
@@ -164,22 +164,28 @@ function resolveTagInfo(
164
164
  importedBindings: ImportedBindings,
165
165
  tag: ts.Identifier,
166
166
  environment: GlintEnvironment,
167
- ): { importedBinding: ImportedBinding; tagConfig: GlintTagConfig } | undefined {
167
+ ): { importedBinding: ImportedBinding; tagConfig: GlintTemplateConfig } | undefined {
168
168
  let importedBinding = importedBindings[tag.text];
169
169
  if (!importedBinding) {
170
170
  return;
171
171
  }
172
172
 
173
- for (let [importSource, tags] of Object.entries(environment.getConfiguredTemplateTags())) {
174
- for (let [importSpecifier, tagConfig] of Object.entries(tags)) {
175
- if (
176
- importSource === importedBinding.source &&
177
- importSpecifier === importedBinding.specifier
178
- ) {
179
- return { importedBinding, tagConfig };
180
- }
181
- }
173
+ // Only the compiler-generated `<template>` form is processed. Its backing
174
+ // import is synthesized by the gts/gjs transform (see `addTagImport` in
175
+ // `environment-ember-template-imports/-private/environment/transform.ts`) and
176
+ // therefore has no position in the original source. User-authored tagged
177
+ // templates (`hbs`...``) have a real import and are deliberately ignored —
178
+ // Glint no longer supports tagged-string templates.
179
+ if (!importedBinding.synthetic) {
180
+ return;
182
181
  }
182
+
183
+ let tagConfig = environment.getTemplateConfig();
184
+ if (!tagConfig) {
185
+ return;
186
+ }
187
+
188
+ return { importedBinding, tagConfig };
183
189
  }
184
190
 
185
191
  type ImportedBinding = { specifier: string; source: string; synthetic: boolean };
@@ -160,12 +160,12 @@ function calculateCorrelatedSpans(
160
160
  ts.transform(ast, [
161
161
  (context) =>
162
162
  function visit<T extends ts.Node>(node: T): T {
163
- // Here we look for ```hbs``` tagged template expressions, originally introduced
164
- // in the now-removed GlimmerX environment. We can consider getting rid of this, but
165
- // then again there are still some use cases in the wild (e.g. Glimmer Next / GXT)
166
- // where have tagged templates closing over outer scope is desirable:
167
- // https://github.com/lifeart/glimmer-next/tree/master/glint-environment-gxt
168
- // https://discord.com/channels/480462759797063690/717767358743183412/1259061848632721480
163
+ // The gts/gjs preprocessor rewrites every `<template>` into a tagged
164
+ // template expression using a compiler-synthesized tag (see
165
+ // `environment-ember-template-imports/-private/environment/{preprocess,transform}.ts`).
166
+ // We look for those tagged templates here and hand them to
167
+ // `calculateTaggedTemplateSpans`, which only processes the synthesized
168
+ // tag — user-authored `hbs`...`` tagged strings are not supported.
169
169
  if (ts.isTaggedTemplateExpression(node)) {
170
170
  let meta = emitMetadata.get(node);
171
171
  let result = calculateTaggedTemplateSpans(ts, node, meta, script, environment);
@@ -553,7 +553,17 @@ export function templateToTypescript(
553
553
  let isGlobal = globals ? globals.includes(name) : true;
554
554
  let form = specialForms[name];
555
555
 
556
- return { name, form, requiresConsumption: !isGlobal };
556
+ // Operator special forms (`===`, `!==`, `&&`, `||`, `!`) emit only the
557
+ // operator expression — never a reference to the keyword itself. For a
558
+ // documented global keyword (e.g. `eq`/`neq`) that would drop hover
559
+ // docs and go-to-definition, so we still emit a discarded reference to
560
+ // it. Narrowing is preserved: the keyword is emitted as the throwaway
561
+ // first operand of a comma expression whose value is the operator
562
+ // expression (`(noop(eq), (a === b))`).
563
+ let isOperatorForm =
564
+ form === '===' || form === '!==' || form === '&&' || form === '||' || form === '!';
565
+
566
+ return { name, form, requiresConsumption: !isGlobal || isOperatorForm };
557
567
  }
558
568
  }
559
569
 
@@ -4,14 +4,7 @@ import * as VM from '@glint/template/-private/keywords';
4
4
 
5
5
  import { ActionKeyword } from '../intrinsics/action';
6
6
  import { ArrayHelper, HashHelper } from '../intrinsics/array-hash';
7
- import {
8
- EqHelper,
9
- GtHelper,
10
- GteHelper,
11
- LtHelper,
12
- LteHelper,
13
- NeqHelper,
14
- } from '../intrinsics/comparison';
7
+ import { GtHelper, GteHelper, LtHelper, LteHelper } from '../intrinsics/comparison';
15
8
  import { EachKeyword } from '../intrinsics/each';
16
9
  import { EachInKeyword } from '../intrinsics/each-in';
17
10
  import { ElementHelper } from '../intrinsics/element';
@@ -331,7 +324,8 @@ interface KeywordsForEmber71Members {
331
324
  *
332
325
  * @see https://api.emberjs.com/ember/release/functions/@ember%2Fhelper/eq
333
326
  */
334
- eq: EqHelper;
327
+ // `{{eq}}` is emitted as the native `===` operator in `ember-tsc`.
328
+ eq: void;
335
329
 
336
330
  /**
337
331
  * `{{fn}}` is a helper that receives a function and some arguments, and
@@ -459,7 +453,8 @@ interface KeywordsForEmber71Members {
459
453
  *
460
454
  * @see https://api.emberjs.com/ember/release/functions/@ember%2Fhelper/neq
461
455
  */
462
- neq: NeqHelper;
456
+ // `{{neq}}` is emitted as the native `!==` operator in `ember-tsc`.
457
+ neq: void;
463
458
 
464
459
  /**
465
460
  * The `{{not}}` helper returns the logical negation of its argument using
@@ -5,7 +5,7 @@ import { DirectInvokable } from '@glint/template/-private/integration';
5
5
  * Built-in keyword in ember-source >= 7.1 (RFC 1000).
6
6
  */
7
7
  export type ArrayHelper = DirectInvokable<{
8
- <T extends unknown[]>(...args: T): T;
8
+ <const T extends unknown[]>(...args: T): T;
9
9
  }>;
10
10
 
11
11
  /**
@@ -13,5 +13,5 @@ export type ArrayHelper = DirectInvokable<{
13
13
  * Built-in keyword in ember-source >= 7.1 (RFC 999).
14
14
  */
15
15
  export type HashHelper = DirectInvokable<{
16
- <T extends Record<string, unknown>>(args: T): T;
16
+ <const T extends Record<string, unknown>>(args: T): T;
17
17
  }>;
@@ -1,19 +1,12 @@
1
1
  import { DirectInvokable } from '@glint/template/-private/integration';
2
2
 
3
- /**
4
- * `(eq a b)` strict equality of two values. Built-in keyword in
5
- * ember-source >= 7.1 (RFC 561). The result is narrowed when both sides are
6
- * literal types so templates such as `{{#if (eq this.kind "primary")}}` get
7
- * accurate type information.
8
- */
9
- export type EqHelper = DirectInvokable<{
10
- <A, B>(a: A, b: B): [A] extends [B] ? ([B] extends [A] ? true : boolean) : boolean;
11
- }>;
12
-
13
- /** `(neq a b)` — strict inequality. Built-in keyword in ember-source >= 7.1. */
14
- export type NeqHelper = DirectInvokable<{
15
- <A, B>(a: A, b: B): [A] extends [B] ? ([B] extends [A] ? false : boolean) : boolean;
16
- }>;
3
+ // NOTE: `eq`/`neq` (RFC 561) are not represented here. Because their runtime is
4
+ // exactly `left === right` / `left !== right`, they are emitted as the native
5
+ // `===`/`!==` operators (see the `ember-template-imports` environment's
6
+ // `specialForms`) which, unlike a boolean-returning helper type, lets
7
+ // TypeScript narrow discriminated unions in `{{#if (eq foo.kind "a")}}`. The
8
+ // `lt`/`lte`/`gt`/`gte` keywords below have no narrowing benefit and remain
9
+ // ordinary helpers.
17
10
 
18
11
  /** `(lt a b)` — numeric less-than. Built-in keyword in ember-source >= 7.1. */
19
12
  export type LtHelper = DirectInvokable<{
@@ -6,8 +6,28 @@ import { DirectInvokable } from '@glint/template/-private/integration';
6
6
  * name. Built-in keyword in ember-source >= 7.1 (RFC 389).
7
7
  */
8
8
  export type ElementHelper = DirectInvokable<{
9
- (tagName: string): ComponentLike<{
10
- Element: Element;
9
+ // `const K` preserves the literal tag name (e.g. `"video"`) through Glint's
10
+ // `resolve()` inference; without it TypeScript widens the argument to `string`
11
+ // and narrowing collapses to the base `Element`. The `string & {}` member lets
12
+ // a genuinely dynamic (non-literal) tag name still match and fall back to
13
+ // `Element`, covering both HTML and SVG.
14
+ <const K extends keyof HTMLElementTagNameMap | keyof SVGElementTagNameMap | (string & {})>(
15
+ tagName: K,
16
+ ): ComponentLike<{
17
+ // A known HTML or SVG tag name narrows to the corresponding element type.
18
+ // When a name exists in both maps (e.g. `a`, `script`, `style`, `title`)
19
+ // the HTML element wins, matching the default (non-SVG) namespace at
20
+ // runtime. An empty string renders the block without a wrapping element, so
21
+ // there is no element to receive attributes (`null`, which makes applying
22
+ // any attribute a type error). Any other (dynamic) tag name falls back to
23
+ // the base `Element`, covering both HTML and SVG.
24
+ Element: K extends keyof HTMLElementTagNameMap
25
+ ? HTMLElementTagNameMap[K]
26
+ : K extends keyof SVGElementTagNameMap
27
+ ? SVGElementTagNameMap[K]
28
+ : K extends ''
29
+ ? null
30
+ : Element;
11
31
  Args: Record<string, unknown>;
12
32
  Blocks: { default: [] };
13
33
  }>;
@@ -1,12 +1,16 @@
1
1
  import { DirectInvokable } from '@glint/template/-private/integration';
2
2
  import ObjectProxy from '@ember/object/proxy';
3
- import '@ember/object/-private/types';
4
3
 
5
4
  // This hack lets us support both the stable/preview types from `ember-source`
6
5
  // and the classic types still live on DefinitelyTyped. If using the DT types,
7
- // this acts as a declaration merge for a module which is then integrated via
8
- // the rest of the DT types. If using the stable/preview types, this ends up
9
- // being a no-op.
6
+ // the `declare module` below acts as a declaration merge for a module which is
7
+ // then integrated via the rest of the DT types. If using the stable/preview
8
+ // types, the module does not exist and this ends up being a no-op.
9
+ //
10
+ // Note: we intentionally do *not* add a side-effect `import '@ember/object/-private/types'`
11
+ // here. TypeScript 6.0 errors (TS2882) on a side-effect import of a module that
12
+ // cannot be resolved, which is exactly the no-op (ember-source) case above. The
13
+ // `declare module` augmentation still merges into the DT module when it exists.
10
14
  declare const GetSetMarker: unique symbol;
11
15
  declare module '@ember/object/-private/types' {
12
16
  interface ComputedPropertyMarker<Get, Set = Get> {
@@ -1,32 +1,49 @@
1
1
  import { DirectInvokable } from '@glint/template/-private/integration';
2
2
 
3
- import type { FirstFalsy, FirstTruthy, MaybeTruthy, TruthConvert } from './truth-convert';
3
+ import type { Falsy, FirstFalsy, FirstTruthy, MaybeTruthy } from './truth-convert';
4
4
 
5
5
  /**
6
6
  * `(and a b ...)` — short-circuit logical AND. Returns the first falsy
7
7
  * positional argument or the last argument. Available as a built-in keyword
8
8
  * in ember-source >= 7.1 (RFC 560).
9
+ *
10
+ * The runtime requires **at least two** arguments (it throws otherwise), so the
11
+ * signature pins two leading positionals before the rest.
9
12
  */
10
13
  export type AndHelper = DirectInvokable<{
11
- <T extends MaybeTruthy[]>(...args: T): FirstFalsy<T>;
14
+ <A extends MaybeTruthy, B extends MaybeTruthy, Rest extends MaybeTruthy[]>(
15
+ a: A,
16
+ b: B,
17
+ ...rest: Rest
18
+ ): FirstFalsy<[A, B, ...Rest]>;
12
19
  }>;
13
20
 
14
21
  /**
15
22
  * `(or a b ...)` — short-circuit logical OR. Returns the first truthy
16
23
  * positional argument or the last argument. Available as a built-in keyword
17
24
  * in ember-source >= 7.1 (RFC 560).
25
+ *
26
+ * The runtime requires **at least two** arguments (it throws otherwise), so the
27
+ * signature pins two leading positionals before the rest.
18
28
  */
19
29
  export type OrHelper = DirectInvokable<{
20
- <T extends MaybeTruthy[]>(...args: T): FirstTruthy<T>;
30
+ <A extends MaybeTruthy, B extends MaybeTruthy, Rest extends MaybeTruthy[]>(
31
+ a: A,
32
+ b: B,
33
+ ...rest: Rest
34
+ ): FirstTruthy<[A, B, ...Rest]>;
21
35
  }>;
22
36
 
23
37
  /**
24
- * `(not value)` — boolean negation using truthy semantics matching the rest
25
- * of Ember's template-side helpers. Built-in keyword in ember-source >= 7.1
26
- * (RFC 560).
38
+ * `(not value)` — boolean negation using Handlebars truthiness semantics.
39
+ * Built-in keyword in ember-source >= 7.1 (RFC 560).
40
+ *
41
+ * Typed as a type predicate over Ember's `Falsy` type so that using it as a
42
+ * condition narrows the operand: in `{{#if (not value)}}` the operand is
43
+ * narrowed to its falsy members, and in the `{{else}}` branch to its truthy
44
+ * members. (As a predicate its *value* is `boolean` rather than a `true`/
45
+ * `false` literal — narrowing in conditions is the more useful guarantee.)
27
46
  */
28
47
  export type NotHelper = DirectInvokable<{
29
- <T>(
30
- value: T,
31
- ): TruthConvert<T> extends true ? false : TruthConvert<T> extends false ? true : boolean;
48
+ <T>(value: T): value is Extract<T, Falsy>;
32
49
  }>;