Match the template block of a semicolon-less TOC in multi-template files - #49
Conversation
`readGts` picks the template block that lies inside the named declaration's range, computed on the file with templates blanked. For `export const X = <template>…</template>` without a trailing semicolon the blanked initializer leaves the statement ending at `=`, so no block was ever inside the range and the lookup failed silently; the consumer fell back to the TypeScript-side result (typically `transparent`). Accept a block that starts inside the range or follows it across whitespace only. Cowritten by Claude
There was a problem hiding this comment.
🟡 Changes recommended
The change is small, focused, and well-tested, but block-matching logic in the resolver is subtle enough that final human review is warranted before approval.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR fixes a resolution gap in readGts (lib/resolver/template-source.ts) for multi-template .gts/.gjs files. When a template-only component is written in expression form without a trailing semicolon (export const Bar = <template>…</template>), blanking the initializer before the TypeScript parse leaves the statement range ending at =, so no template block was ever considered "inside" the declaration's range. The lookup returned null silently, causing the consumer to keep the (usually transparent) TypeScript-side result and lose the real element substitution. The fix relaxes the block-matching condition to also accept a block that starts at or after the range and is separated from it by whitespace only.
Changes:
- Replace the strict "block fully contained in declaration range" check with one that accepts a block starting inside the range, or immediately following it across whitespace only.
- Add a regression fixture (
toc-expression-no-semicolon.gts) with two semicolon-less expression-form TOCs (Bar→<div>,Baz→<span>). - Add a unit test asserting both components resolve to their correct template blocks by name.
File summaries
| File | Description |
|---|---|
lib/resolver/template-source.ts |
Relaxes the template-block match in readGts to handle semicolon-less expression-form TOCs whose blanked range ends at =. |
test/glint-fixtures/toc-expression-no-semicolon.gts |
New fixture with two semicolon-less expression-form template-only components. |
test/resolver/template-source.test.ts |
New regression test verifying Bar/Baz resolve to the correct template by name. |
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 0
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
readGtsresolves a component in a multi-template.gtsby finding the named declaration in a copy of the file with the templates blanked, then picking the<template>block inside that declaration's range. For the expression form without a trailing semicolon —— the blanked initializer leaves
export const Bar =with nothing after it, so the statement's range ends at=and no block is ever "inside" it. The lookup returnednullsilently and the consumer kept whatever the TypeScript side had said (usuallytransparent), losing the<div>substitution. Surfaced on a real app where<HeaderLoadingBar/>(a semicolon-less TOC exported from a multi-template file) never resolved.Fix: accept a block that starts inside the range, or follows it across whitespace only. Regression test with a two-TOC fixture (
Bar→<div>,Baz→<span>), red before / green after; the full suite stays at 289 + 1 expected fail.Independent of #48 (found while comparing its two backends; affects both).
Cowritten by Claude