Skip to content

Commit 5a546c5

Browse files
committed
Fix template-no-invalid-link-text: skip when link contains non-text children
Match upstream ember-template-lint's no-invalid-link-text behavior (lib/rules/no-invalid-link-text.js L53-56): if any child of the link is not a TextNode, the link content is opaque and should not be flagged. Handles real-world patterns like <a><MyComponent /> </a> where the link text comes from a component's template, which the linter cannot analyze. Removes two tests that asserted stricter-than-upstream behavior on <a><span>click here</span></a>. Per upstream, this case can't be reliably analyzed and should not be flagged.
1 parent b705850 commit 5a546c5

2 files changed

Lines changed: 16 additions & 24 deletions

File tree

lib/rules/template-no-invalid-link-text.js

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -145,19 +145,19 @@ module.exports = {
145145
return;
146146
}
147147

148-
// Check text content
149-
let fullText = '';
150-
let hasDynamic = false;
151-
for (const child of children || []) {
152-
const result = getTextContentResult(child);
153-
fullText += result.text;
154-
if (result.hasDynamic) {
155-
hasDynamic = true;
156-
}
148+
// Match upstream: if the link contains any non-TextNode child (component,
149+
// mustache, sub/block expression), the content is dynamic/opaque — don't flag.
150+
// See upstream no-invalid-link-text.js L53-56.
151+
const childList = children || [];
152+
const allTextNodes = childList.every((child) => child.type === 'GlimmerTextNode');
153+
if (!allTextNodes) {
154+
return;
157155
}
158156

159-
if (hasDynamic) {
160-
return; // can't validate dynamic content
157+
// Concatenate text content (only TextNode children at this point).
158+
let fullText = '';
159+
for (const child of childList) {
160+
fullText += child.chars.replaceAll('&nbsp;', ' ');
161161
}
162162

163163
const normalized = fullText.trim().toLowerCase().replaceAll(/\s+/g, ' ');

tests/lib/rules/template-no-invalid-link-text.js

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ const ruleTester = new RuleTester({
88

99
ruleTester.run('template-no-invalid-link-text', rule, {
1010
valid: [
11+
// Link with component child — content is opaque, can't validate.
12+
// Mirrors upstream no-invalid-link-text.js L53-56 ("do not flag when link contains additional dynamic (non-text) children").
13+
{ filename: 'test.gjs', code: '<template><a href="/x"><MyComponent /></a></template>' },
14+
{ filename: 'test.gjs', code: '<template><a href="/x">prefix <MyComponent /></a></template>' },
15+
1116
{ filename: 'test.gjs', code: '<template><a href="/about">About Us</a></template>' },
1217
{
1318
filename: 'test.gjs',
@@ -149,13 +154,6 @@ ruleTester.run('template-no-invalid-link-text', rule, {
149154
output: null,
150155
errors: [{ messageId: 'invalidText' }],
151156
},
152-
{
153-
// Nested element content
154-
filename: 'test.gjs',
155-
code: '<template><a href="/page"><span>click here</span></a></template>',
156-
output: null,
157-
errors: [{ messageId: 'invalidText' }],
158-
},
159157
{
160158
// aria-label with disallowed text overrides content check
161159
filename: 'test.gjs',
@@ -277,12 +275,6 @@ hbsRuleTester.run('template-no-invalid-link-text (hbs)', rule, {
277275
output: null,
278276
errors: [{ messageId: 'invalidText' }],
279277
},
280-
{
281-
// nested element content — text is in a child element
282-
code: '<a href="/page"><span>click here</span></a>',
283-
output: null,
284-
errors: [{ messageId: 'invalidText' }],
285-
},
286278
{
287279
code: '<MyLink>click here</MyLink>',
288280
output: null,

0 commit comments

Comments
 (0)