Skip to content

Resolve Element for TOC components (satisfies TOC<S> and : TOC<S> forms)#16

Merged
johanrd merged 8 commits intomainfrom
fix/fp-toc-satisfies-element
May 8, 2026
Merged

Resolve Element for TOC components (satisfies TOC<S> and : TOC<S> forms)#16
johanrd merged 8 commits intomainfrom
fix/fp-toc-satisfies-element

Conversation

@johanrd
Copy link
Copy Markdown
Owner

@johanrd johanrd commented May 7, 2026

Summary

Glint's TOC overload reaches emitComponent(...).element but surfaces the type as unknown (or any) for both the satisfies form

const X = <template>...</template> satisfies TOC<{Element: T}>;

and the type-annotation form

const X: TOC<{Element: T}> = <template>...</template>;

even though T is statically known on the value's declaration. Without resolution the component fell through to transparent blanking, floating its children to the actual parent — e.g. a <li>-yielding TOC inside <ul> would leak its body to <ul> directly and element-permitted-content would FP-fire on legal content like <p> or <blockquote>.

When .element is unknown/any, walk the component reference back to its variable declaration, find the TOC<S> TypeReference (in either decl.type for the annotation form or decl.initializer.type for the SatisfiesExpression form), pull its first type argument, and read Element off it. Map the result through the same elementTypeToTag table the class form uses. Falls through to 'transparent' for genuinely yields-only TOCs (no Element declared).

A small refactor extracted matchElementTypeToTag so both the regular path and the satisfies-recovery path share the union-branch lookup.

Surfaced by

Ecosystem CI on:

  • ember-a11y/ember-a11y-testingViolationsGridItem (TOC declaring Element: HTMLLIElement via satisfies); 4× element-permitted-content on <button>/<input>/<code>/<img> flagged as not permitted under <ul> (because the runtime <li> was missing from the static analysis).
  • hashicorp/design-system — multiple internal list-item TOCs.

Test plan

  • New test covers Form B (satisfies TOC<…>) — fixture mirrors ViolationsGridItem's shape
  • New test covers Form A (: TOC<…> =) — annotation-only declaration
  • npm test — 129/129 passing (was 127, +2 new tests)
  • npm run typecheck:tests — clean
  • npm run build — clean

…<S>` / `: TOC<S>`

Glint's TOC overload reaches `emitComponent(...).element` but surfaces
the type as `unknown` (or `any`) for both the satisfies form
  `const X = <template>...</template> satisfies TOC<{Element: T}>;`
and the type-annotation form
  `const X: TOC<{Element: T}> = <template>...</template>;`
even though `T` is statically known on the value's declaration.
Without resolution the component fell through to transparent blanking,
floating its children to the actual parent — e.g. a `<li>`-yielding
TOC inside `<ul>` would leak its body to `<ul>` directly and
`element-permitted-content` would FP-fire on legal content like `<p>`
or `<blockquote>`.

When `.element` is unknown/any, walk back to the component's variable
declaration, find the `TOC<S>` TypeReference (in either the type
annotation or the SatisfiesExpression's `.type`), pull its first type
argument, and read `Element` off it. Map the result through the same
`elementTypeToTag` table the class form uses. Falls through to
transparent for genuinely yields-only TOCs (no `Element` declared).

Surfaced by ecosystem CI on ember-a11y-testing's `ViolationsGridItem`
and HDS's various internal list-item TOCs. Fixture
`toc-list-item.gts` mirrors the satisfies form ember-a11y-testing
uses; `toc-list-item-consumer.gts` exercises it inside `<ul>`.
@johanrd johanrd added the bug Something isn't working label May 7, 2026
@johanrd johanrd requested a review from Copilot May 7, 2026 11:05
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Improves Glint-based component element resolution for template-only components (TOC) when Glint surfaces emitComponent(...).element as unknown/any for satisfies TOC<…> and : TOC<…> = declarations, preventing incorrect “transparent” fallback that can cause content-model false positives.

Changes:

  • Adds a recovery path that walks from the emitted resolve(Comp) reference back to the variable declaration to read TOC<S>’s type argument and derive S['Element'].
  • Refactors element-type-to-tag matching into a shared helper (matchElementTypeToTag) used by both the normal and recovery paths.
  • Adds cross-file fixtures + two new tests covering both satisfies TOC<…> and : TOC<…> = TOC declaration forms.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
lib/glint.ts Adds TOC Element recovery when .element is unknown/any; extracts shared tag-matching helper.
test/glint.test.ts Adds two integration tests asserting TOC → 'li' resolution for satisfies + annotation forms.
test/glint-fixtures/toc-list-item.gts New TOC fixture using satisfies TOC<{ Element: HTMLLIElement; … }> form.
test/glint-fixtures/toc-list-item-consumer.gts New consumer fixture that imports and invokes the satisfies-form TOC.
test/glint-fixtures/toc-annotated-list-item.gts New TOC fixture using const X: TOC<S> = <template>…</template>; form.
test/glint-fixtures/toc-annotated-list-item-consumer.gts New consumer fixture that imports and invokes the annotation-form TOC.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/glint.ts
Per Copilot review on #16: the inline comment in resolveComponentElement
said Glint 'tends to surface as unknown' for satisfies-TOC forms while
the doc on resolveElementFromSatisfiesTOC said 'ends up as any'. Both
shapes can occur (unknown most often, any in cascading-error files;
the code already handles both via the (Unknown | Any) check). Aligned
the doc comment to match.
johanrd added a commit that referenced this pull request May 7, 2026
Per Copilot review on #18:

1. Removed the reference to resolveElementFromSatisfiesTOC from the
   inline comment — that helper exists on a sibling branch
   (fix/fp-toc-satisfies-element / PR #16) but not on this one. Stale
   on its own.
2. Added a fallback path for when TOC's type-args land on a
   TypeReference rather than aliasTypeArguments (i.e., when TOC is
   declared as `interface TOC<S>` instead of `type TOC<S> = …`). The
   public `checker.getTypeArguments(refType as TypeReference)` covers
   that case. aliasTypeArguments handles the type-alias form.
@johanrd johanrd requested a review from Copilot May 7, 2026 12:44
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment thread lib/glint.ts
Comment thread lib/glint.ts Outdated
Per Copilot review on #16:

1. Renamed resolveElementFromSatisfiesTOC →
   resolveElementFromTOCDeclaration. The helper handles both the
   `satisfies TOC<…>` and `: TOC<…> =` annotation forms; the old
   name only mentioned satisfies and was misleading.
2. Added an isTOCTypeName check before reading Element off the
   type-arg. Without it, an unrelated generic annotation that
   happens to have an `Element` property could be mis-resolved
   when emitComponent's .element is unknown/any. Recognizes both
   the short form `TOC` and the long form `TemplateOnlyComponent`.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Comment thread test/glint.test.ts
Comment thread test/glint.test.ts Outdated
Comment thread test/glint-fixtures/toc-annotated-list-item.gts Outdated
Comment thread lib/glint.ts Outdated
Per Copilot review on #16:

1. Three stale references to resolveElementFromSatisfiesTOC (in
   test/glint.test.ts and test/glint-fixtures/toc-annotated-list-item.gts)
   updated to the renamed resolveElementFromTOCDeclaration.
2. The isTOCTypeName comment said qualified names match the
   "leftmost identifier" but the code uses name.right (rightmost).
   The code was correct — for `Ember.TOC` we want to match `TOC`,
   the actual type name. Comment updated to say rightmost.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment thread lib/glint.ts Outdated
Comment thread lib/glint.ts Outdated
Per Copilot review on #16:

1. `findComponentDeclSourceFile` and `resolveElementFromTOCDeclaration`
   both navigated the same emitComponent → resolve(Comp) AST shape and
   ran the same alias-resolution dance. Extracted both to a shared
   helper, `getComponentSymbolFromEmitCall`. Both callers now stay in
   sync if Glint's emitted shape ever changes.

2. The comment above `matchElementTypeToTag` (line 603) said unions
   "take the first branch's mapping". The code actually picks the first
   *matching* branch (skipping ones with no DOM mapping). Updated to
   match.

No behavioral change. 129/129 pass.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

@johanrd
Copy link
Copy Markdown
Owner Author

johanrd commented May 8, 2026

@copilot resolve the merge conflicts in this pull request

Copilot AI and others added 3 commits May 8, 2026 07:03
Co-authored-by: johanrd <4601554+johanrd@users.noreply.github.com>
This reverts commit be1dd2b.

Co-authored-by: johanrd <4601554+johanrd@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented May 8, 2026

@copilot resolve the merge conflicts in this pull request

Done — I merged origin/main into this branch and resolved the conflict in lib/glint.ts. The conflict resolution is in commit 73ebc9b (with follow-up cleanup in 8394e22).

Copilot finished work on behalf of johanrd May 8, 2026 07:07
@johanrd johanrd merged commit 26f1a3c into main May 8, 2026
2 checks passed
@github-actions github-actions Bot mentioned this pull request May 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants