diff --git a/lib/rules/template-no-empty-headings.js b/lib/rules/template-no-empty-headings.js index edb71caae6..ea4cb2790f 100644 --- a/lib/rules/template-no-empty-headings.js +++ b/lib/rules/template-no-empty-headings.js @@ -1,5 +1,29 @@ const HEADINGS = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']); +function isAriaHiddenTruthy(attr) { + if (!attr) { + return false; + } + const value = attr.value; + // Valueless or empty-string attribute —
. Per HTML boolean + // attribute semantics (and jsx-a11y/vue-a11y convention), presence = truthy. + if (!value || (value.type === 'GlimmerTextNode' && value.chars === '')) { + return true; + } + if (value.type === 'GlimmerTextNode') { + return value.chars === 'true'; + } + if (value.type === 'GlimmerMustacheStatement' && value.path) { + if (value.path.type === 'GlimmerBooleanLiteral') { + return value.path.value === true; + } + if (value.path.type === 'GlimmerStringLiteral') { + return value.path.value === 'true'; + } + } + return false; +} + function isHidden(node) { if (!node.attributes) { return false; @@ -7,11 +31,7 @@ function isHidden(node) { if (node.attributes.some((a) => a.name === 'hidden')) { return true; } - const ariaHidden = node.attributes.find((a) => a.name === 'aria-hidden'); - if (ariaHidden?.value?.type === 'GlimmerTextNode' && ariaHidden.value.chars === 'true') { - return true; - } - return false; + return isAriaHiddenTruthy(node.attributes.find((a) => a.name === 'aria-hidden')); } function isComponent(node) { diff --git a/tests/lib/rules/template-no-empty-headings.js b/tests/lib/rules/template-no-empty-headings.js index cce6b806da..375d5ee84d 100644 --- a/tests/lib/rules/template-no-empty-headings.js +++ b/tests/lib/rules/template-no-empty-headings.js @@ -43,6 +43,14 @@ ruleTester.run('template-no-empty-headings', rule, { '