Skip to content

Post-merge-review: Fix template-no-curly-component-invocation: preserve this./@/local names in suggestions, and skip JS scope bindings#2657

Open
johanrd wants to merge 1 commit intoember-cli:masterfrom
johanrd:night_fix/template-no-curly-component-invocation
Open

Post-merge-review: Fix template-no-curly-component-invocation: preserve this./@/local names in suggestions, and skip JS scope bindings#2657
johanrd wants to merge 1 commit intoember-cli:masterfrom
johanrd:night_fix/template-no-curly-component-invocation

Conversation

@johanrd
Copy link
Copy Markdown
Contributor

@johanrd johanrd commented Apr 13, 2026

What's broken on master

Two related false positives:

  1. Autofix PascalCases path prefixes. transformTagName unconditionally PascalCases the curly path when generating the suggested angle-bracket form, corrupting three categories of names:

    {{#this.foo-bar}}{{/this.foo-bar}}   → <This.fooBar>…</This.fooBar>   ❌
    {{#@foo-bar}}{{/@foo-bar}}<@fooBar>…</@fooBar>           ❌
    {{#my-component}}…                    → <MyComponent>…                 ❌ (when my-component is a block param)
  2. GJS/GTS JS scope bindings flagged. The port has no JS scope tracker, so valid imported/const identifiers used in curly form are flagged as needing angle-bracket conversion. The "fix" would break: import fooBar from './foo-bar'; <template>{{fooBar}}</template> would be rewritten to <FooBar /> which references an unbound name.

Fix

  • Pass isLocal to transformTagName and return the name verbatim when it starts with @, starts with this., or is a local (block-param) binding. Matches upstream exactly (curly-component-invocation.js L1–2).
  • Add isJsScopeBinding(node) using sourceCode.getScope(). When the mustache/block path head resolves to a JS binding, skip reporting entirely. Pattern already used by template-no-redundant-fn and template-no-restricted-invocations (isJsScopeVariable). Upstream ember-template-lint uses this.isLocal(path) via its own scope tracker for the same purpose.

Test plan

  • 103/103 tests pass on the branch
  • 5 existing tests had wrong expected output (PascalCase suggestions); updated to correct form. All 5 fail on master.
  • 4 new valid tests with JS scope bindings (import fooBar from '…'; {{fooBar}}, {{fooBar arg=1}}, {{#fooBar}}…{{/fooBar}}, const someHelper = …; {{someHelper}}) all fail on master.

Co-written by Claude.

…s, local names, and JS scope bindings

Three false positives in the port:

1. transformTagName PascalCased this./@/local names, producing wrong
   suggestions (e.g. {{#this.foo-bar}} → <This.fooBar> instead of
   <this.foo-bar>). Matches upstream transformTagName(name, isLocal).

2. In GJS/GTS, explicit JS scope bindings (imports, const, function
   params) used as curly invocations were flagged as needing angle-
   bracket conversion. The conversion would break: converting
   {{fooBar}} to <FooBar> references an unbound name. Added
   isJsScopeBinding() using sourceCode.getScope() (pattern borrowed
   from template-no-restricted-invocations / template-no-redundant-fn).
   Applies to both single-word and named-args mustache forms, and to
   block invocations.

3. Block params already had transformTagName preservation wired up;
   the fix extends that to JS scope bindings too.

Upstream uses this.isLocal(path) via its own scope tracker for the same
purpose; this implementation uses ESLint's sourceCode.getScope().
@johanrd johanrd marked this pull request as ready for review April 13, 2026 10:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant