@@ -60,16 +60,31 @@ function isNativeElement(node, sourceCode) {
6060 }
6161 const scope = sourceCode . getScope ( node . parent ) ;
6262 const firstPart = node . parts && node . parts [ 0 ] ;
63- // Compare by identifier name rather than AST node object identity — object
64- // identity isn't guaranteed across parser versions (ember-eslint-parser can
65- // produce distinct node objects for the same token depending on how the
66- // scope manager walks the tree), but the resolved `.name` is stable.
67- if ( firstPart && scope . references . some ( ( ref ) => ref . identifier ?. name === firstPart ?. name ) ) {
63+ // Scope-shadowing detection: treat the tag as a component invocation only
64+ // when its name resolves to an actual BINDING (const / let / import / block-
65+ // param) in the enclosing scope chain — not just any reference. A bare
66+ // reference like `{{div}}` (helper invocation elsewhere in the template)
67+ // creates an entry in `scope.references` without binding a local
68+ // identifier; it must not shadow the native `<div>` tag. Walking upward
69+ // through `scope.upper` catches bindings declared in outer scopes — e.g.
70+ // a module-level `const div = ...` shadowing templates deeper in the file.
71+ if ( firstPart && hasBindingInScopeChain ( scope , firstPart . name ) ) {
6872 return false ;
6973 }
7074 return true ;
7175}
7276
77+ function hasBindingInScopeChain ( scope , name ) {
78+ let current = scope ;
79+ while ( current ) {
80+ if ( current . variables && current . variables . some ( ( v ) => v . name === name ) ) {
81+ return true ;
82+ }
83+ current = current . upper ;
84+ }
85+ return false ;
86+ }
87+
7388/**
7489 * Inverse of {@link isNativeElement}. Returns true when the node should NOT
7590 * be treated as a native HTML element — either because it's a component
0 commit comments