hbs-parser: register block-param scope for as |x|#189
Merged
NullVoxPopuli merged 2 commits intoember-tooling:mainfrom Apr 16, 2026
Merged
hbs-parser: register block-param scope for as |x|#189NullVoxPopuli merged 2 commits intoember-tooling:mainfrom
as |x|#189NullVoxPopuli merged 2 commits intoember-tooling:mainfrom
Conversation
The HBS parser previously analyzed an empty stub Program, so block
params from `<Foo as |x|>` and `{{#let ... as |x|}}` were never
registered in any scope. Downstream rules that walk scope to resolve
block-param references (or check whether an angle-bracket tag is a
local variable, e.g. template-no-block-params-for-html-elements in
eslint-plugin-ember) had no scope to consult and silently fell back to
heuristics on .hbs files.
Wires the existing `registerGlimmerScopes` helper into the HBS path
with a new `blockParamsOnly` mode, leaving free identifiers
(`{{path}}`, `<Tag>`) unregistered so they remain runtime-defined and
do not surface as no-undef errors. The global scope is rebound from a
stub Program to the real Program — analyzing the real Program directly
hits an esrecurse cycle on the Glimmer subtree's parent back-links.
Also adds an early-exit to `registerBlockParams` when there are no
block params to declare, which avoids creating empty BlockScopes that
slow `findParentScope`/`findVarInParentScopes` lookups (benefits the
gjs/gts path too).
johanrd
added a commit
to johanrd/eslint-plugin-ember
that referenced
this pull request
Apr 15, 2026
…svg-tags + scope Replaces the inverse component-detection heuristic with a whitelist of HTML and SVG tag names plus a scope check. With ember-tooling/ember-eslint-parser#189 populating block-param scope in HBS, lists + scope now works in both modes. Custom elements and unknown lowercase tags remain accepted false negatives.
5 tasks
johanrd
added a commit
to johanrd/eslint-plugin-ember
that referenced
this pull request
Apr 15, 2026
…params Replaces the manual block-param stack with sourceCode.getScope(node.parent) + a walk up the scope chain. GTS templates work today (36/37 tests pass). BLOCKED BY UPSTREAM: ember-tooling/ember-eslint-parser#189. The HBS parser currently builds an empty scope manager, so Glimmer block params aren't registered for .hbs files. The failing test `{{#let ... as |plaintext|}}<plaintext />` documents this gap and will start passing once PR 189 is merged and consumed here. Uses getScope(node.parent) rather than getScope(node) so an element's own `as |x|` params (attached to that element's block scope) don't shadow its own tag — e.g. `<marquee as |marquee|>` must still flag the outer <marquee>.
johanrd
added a commit
to johanrd/eslint-plugin-ember
that referenced
this pull request
Apr 15, 2026
…params Replaces the manual block-param stack with sourceCode.getScope(node.parent) + a walk up the scope chain. GTS templates work today (36/37 tests pass). BLOCKED BY UPSTREAM: ember-tooling/ember-eslint-parser#189. The HBS parser currently builds an empty scope manager, so Glimmer block params aren't registered for .hbs files. The failing test `{{#let ... as |plaintext|}}<plaintext />` documents this gap and will start passing once PR 189 is merged and consumed here. Uses getScope(node.parent) rather than getScope(node) so an element's own `as |x|` params (attached to that element's block scope) don't shadow its own tag — e.g. `<marquee as |marquee|>` must still flag the outer <marquee>.
6 tasks
| * runtime-defined and must not surface as no-undef errors. | ||
| */ | ||
| export function registerGlimmerScopes(result) { | ||
| export function registerGlimmerScopes(result, { blockParamsOnly = false } = {}) { |
Member
There was a problem hiding this comment.
I think it might be better to undo the changes to this function and instead export a registerHBSScopes function
…Scopes Addresses review: ember-tooling#189 (comment)
NullVoxPopuli
approved these changes
Apr 16, 2026
Contributor
Author
|
@NullVoxPopuli thanks for the quick review! Seems like npm publish failed in ci: https://github.com/ember-tooling/ember-eslint-parser/actions/runs/24485077427/job/71557984384 |
Merged
johanrd
added a commit
to johanrd/eslint-plugin-ember
that referenced
this pull request
Apr 19, 2026
ember-tooling/ember-eslint-parser#189 populates block-param scope for <foo as |x|> in HBS mode, so the rule's scope check in lib/rules/template-no-block-params-for-html-elements.js no longer relies on an empty scope manager in .hbs files. No observable test change on master (all tests here use <template>...</template> / GJS mode where scope already worked), but makes the scope-based detection correct in HBS too.
johanrd
added a commit
to johanrd/eslint-plugin-ember
that referenced
this pull request
Apr 19, 2026
Unblocks the HBS valid-case test
{{#let (component 'whatever-here') as |plaintext|}}
<plaintext />
{{/let}}
which relies on ember-tooling/ember-eslint-parser#189 populating block-
param scope in the hbs-parser. All 37 tests (GTS + HBS) pass.
This was referenced Apr 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Registers block-param scope in the HBS parser so
<Foo as |x|>and{{#let ... as |x|}}create proper variable bindings in the scope manager. Previously the HBS parser analyzed an empty stub Program, so no block params landed in any scope.Why
Came up reviewing ember-cli/eslint-plugin-ember#2689 (
template-no-block-params-for-html-elements). The lint rule's scope check is effectively dead code on.hbsfiles because the HBS parser never populates scope with template-defined variables — only the html-tag allowlist heuristic carries that case. Same gap likely affects any other rule that consults scope for HBS templates.Approach
registerHBSScopesintransforms.jsthat traverses the Glimmer AST and declares only block-param variables. Free identifiers ({{path}},<Tag>) are intentionally left unregistered so HBS keeps its "all template locals are runtime-defined" policy and no-undef stays quiet.registerGlimmerScopesis unchanged.hbs-parser.js, rebinds the analyzed global scope from a stub Program onto the real Program. Analyzing the real Program directly causes infinite recursion in esrecurse — the Glimmer subtree carries parent back-links.registerBlockParamswhen there are no block params, avoiding empty BlockScopes that would pollutefindParentScope/findVarInParentScopeslookups (also benefits the gjs/gts path).Test plan
<Foo as |x|>{{x}}</Foo>— block scope withxdeclared{{#let foo as |router|}}{{router}}{{/let}}— block scope withrouter<Foo as |a b c|>— multiple params each registered<Foo>hi</Foo>— no block scopes createdpnpm lintclean