Skip to content

Commit 81de905

Browse files
committed
refactor: align component check with ember-cli#2724 canonical (lists + scope)
Remove the inline PascalCase-regex isComponentInvocation heuristic in favor of the lists + scope pattern from ember-cli#2689 / ember-cli#2724. The new lib/utils/is-native-element.js mirrors ember-cli#2724's util byte-for-byte so the two PRs can land in either order without conflict. evaluateChild / evaluateChildren now thread sourceCode through the recursion so the scope check has access to the enclosing template's bindings. Heuristic approaches were explicitly rejected in ember-cli#2689 because lowercase tags CAN be components when shadowed by scope bindings. Treating custom elements as opaque (the same as components) is a behavior improvement — matches ember-cli#2724's convention.
1 parent 4b9e448 commit 81de905

1 file changed

Lines changed: 10 additions & 24 deletions

File tree

lib/rules/template-anchor-has-content.js

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,4 @@
1-
// Matches a tag string that is a component invocation rather than a plain
2-
// HTML element: PascalCase (`Foo`), argument-invocation (`@foo`), path on
3-
// `this.` (`this.foo`), dotted path (`foo.bar`), or named-block-style
4-
// `foo::bar`. Keep this mirrored with the inline pattern in
5-
// lib/rules/template-no-invalid-interactive.js until a shared utility lands.
6-
function isComponentInvocation(tag) {
7-
if (!tag) {
8-
return false;
9-
}
10-
return (
11-
/^[A-Z]/.test(tag) ||
12-
tag.startsWith('@') ||
13-
tag.startsWith('this.') ||
14-
tag.includes('.') ||
15-
tag.includes('::')
16-
);
17-
}
1+
const { isNativeElement } = require('../utils/is-native-element');
182

193
function isDynamicValue(value) {
204
return value?.type === 'GlimmerMustacheStatement' || value?.type === 'GlimmerConcatStatement';
@@ -73,7 +57,7 @@ function hasAccessibleNameAttribute(node) {
7357
// { accessible: true } — statically contributes a non-empty name.
7458
// { accessible: false } — contributes nothing (empty text, aria-hidden
7559
// subtree, <img> without non-empty alt, …).
76-
function evaluateChild(child) {
60+
function evaluateChild(child, sourceCode) {
7761
if (child.type === 'GlimmerTextNode') {
7862
const text = child.chars.replaceAll('&nbsp;', ' ').trim();
7963
return { dynamic: false, accessible: text.length > 0 };
@@ -97,8 +81,9 @@ function evaluateChild(child) {
9781
return { dynamic: false, accessible: false };
9882
}
9983

100-
// Component invocations are opaque — we can't see inside them.
101-
if (isComponentInvocation(child.tag)) {
84+
// Non-native children (components, custom elements, scope-shadowed tags)
85+
// are opaque — we can't see inside them.
86+
if (!isNativeElement(child, sourceCode)) {
10287
return { dynamic: true, accessible: false };
10388
}
10489

@@ -124,16 +109,16 @@ function evaluateChild(child) {
124109
return { dynamic: false, accessible: true };
125110
}
126111

127-
return evaluateChildren(child.children || []);
112+
return evaluateChildren(child.children || [], sourceCode);
128113
}
129114

130115
return { dynamic: false, accessible: false };
131116
}
132117

133-
function evaluateChildren(children) {
118+
function evaluateChildren(children, sourceCode) {
134119
let dynamic = false;
135120
for (const child of children) {
136-
const result = evaluateChild(child);
121+
const result = evaluateChild(child, sourceCode);
137122
if (result.accessible) {
138123
return { dynamic: false, accessible: true };
139124
}
@@ -162,6 +147,7 @@ module.exports = {
162147
},
163148

164149
create(context) {
150+
const sourceCode = context.sourceCode || context.getSourceCode();
165151
return {
166152
GlimmerElementNode(node) {
167153
if (node.tag !== 'a') {
@@ -179,7 +165,7 @@ module.exports = {
179165
return;
180166
}
181167

182-
const result = evaluateChildren(node.children || []);
168+
const result = evaluateChildren(node.children || [], sourceCode);
183169
if (result.accessible || result.dynamic) {
184170
return;
185171
}

0 commit comments

Comments
 (0)