From 5a546c569972204ee01f2cf861dc12d5746610fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20R=C3=B8ed?= Date: Mon, 13 Apr 2026 15:03:55 +0200 Subject: [PATCH 1/2] 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 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 click here. Per upstream, this case can't be reliably analyzed and should not be flagged. --- lib/rules/template-no-invalid-link-text.js | 22 +++++++++---------- .../rules/template-no-invalid-link-text.js | 18 +++++---------- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/lib/rules/template-no-invalid-link-text.js b/lib/rules/template-no-invalid-link-text.js index a4b02c9097..0ed64dfc2e 100644 --- a/lib/rules/template-no-invalid-link-text.js +++ b/lib/rules/template-no-invalid-link-text.js @@ -145,19 +145,19 @@ module.exports = { return; } - // Check text content - let fullText = ''; - let hasDynamic = false; - for (const child of children || []) { - const result = getTextContentResult(child); - fullText += result.text; - if (result.hasDynamic) { - hasDynamic = true; - } + // Match upstream: if the link contains any non-TextNode child (component, + // mustache, sub/block expression), the content is dynamic/opaque — don't flag. + // See upstream no-invalid-link-text.js L53-56. + const childList = children || []; + const allTextNodes = childList.every((child) => child.type === 'GlimmerTextNode'); + if (!allTextNodes) { + return; } - if (hasDynamic) { - return; // can't validate dynamic content + // Concatenate text content (only TextNode children at this point). + let fullText = ''; + for (const child of childList) { + fullText += child.chars.replaceAll(' ', ' '); } const normalized = fullText.trim().toLowerCase().replaceAll(/\s+/g, ' '); diff --git a/tests/lib/rules/template-no-invalid-link-text.js b/tests/lib/rules/template-no-invalid-link-text.js index 4aa0fdca28..016a70e47d 100644 --- a/tests/lib/rules/template-no-invalid-link-text.js +++ b/tests/lib/rules/template-no-invalid-link-text.js @@ -8,6 +8,11 @@ const ruleTester = new RuleTester({ ruleTester.run('template-no-invalid-link-text', rule, { valid: [ + // Link with component child — content is opaque, can't validate. + // Mirrors upstream no-invalid-link-text.js L53-56 ("do not flag when link contains additional dynamic (non-text) children"). + { filename: 'test.gjs', code: '' }, + { filename: 'test.gjs', code: '' }, + { filename: 'test.gjs', code: '' }, { filename: 'test.gjs', @@ -149,13 +154,6 @@ ruleTester.run('template-no-invalid-link-text', rule, { output: null, errors: [{ messageId: 'invalidText' }], }, - { - // Nested element content - filename: 'test.gjs', - code: '', - output: null, - errors: [{ messageId: 'invalidText' }], - }, { // aria-label with disallowed text overrides content check filename: 'test.gjs', @@ -277,12 +275,6 @@ hbsRuleTester.run('template-no-invalid-link-text (hbs)', rule, { output: null, errors: [{ messageId: 'invalidText' }], }, - { - // nested element content — text is in a child element - code: 'click here', - output: null, - errors: [{ messageId: 'invalidText' }], - }, { code: 'click here', output: null, From c56aaddccfd9ca5e2a09f88e63e9bf1a69f3c9ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20R=C3=B8ed?= Date: Mon, 13 Apr 2026 20:34:24 +0200 Subject: [PATCH 2/2] =?UTF-8?q?Remove=20editorial=20'Match=20upstream'=20c?= =?UTF-8?q?omment=20=E2=80=94=20belongs=20in=20PR=20description,=20not=20c?= =?UTF-8?q?ode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/rules/template-no-invalid-link-text.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/rules/template-no-invalid-link-text.js b/lib/rules/template-no-invalid-link-text.js index 0ed64dfc2e..b90bfaa3cd 100644 --- a/lib/rules/template-no-invalid-link-text.js +++ b/lib/rules/template-no-invalid-link-text.js @@ -145,9 +145,7 @@ module.exports = { return; } - // Match upstream: if the link contains any non-TextNode child (component, - // mustache, sub/block expression), the content is dynamic/opaque — don't flag. - // See upstream no-invalid-link-text.js L53-56. + // If the link contains any non-TextNode child, content is dynamic/opaque — don't flag. const childList = children || []; const allTextNodes = childList.every((child) => child.type === 'GlimmerTextNode'); if (!allTextNodes) {