Skip to content

feat: add template-anchor-has-content#35

Closed
johanrd wants to merge 1 commit intomasterfrom
feat/template-anchor-has-content
Closed

feat: add template-anchor-has-content#35
johanrd wants to merge 1 commit intomasterfrom
feat/template-anchor-has-content

Conversation

@johanrd
Copy link
Copy Markdown
Owner

@johanrd johanrd commented Apr 21, 2026

Note

This is part of a series where Claude has audited eslint-plugin-ember against jsx-a11y, vuejs-accessibility, angular-eslint, lit-a11y and html-validate, ember-template-lint, and the HTML and WCAG specs.

Summary

  • Premise 1: Every <a href> rendered to the DOM is exposed as a link to assistive tech. Per ACCNAME 1.2 §4.3.2 Computation steps, the accessible name is computed in order from aria-labelledby (step 2B), then aria-label (step 2D), then host-language label sources / descendant text content (steps 2E–2F), then a tooltip attribute such as title (step 2I). For an <a> element with none of these, the result is the empty string. An anchor with none of these has no accessible name; a screen reader announces "link" with nothing else. Authoring guidance: WCAG 2.1 SC 2.4.4 Link Purpose.
  • Premise 2: jsx-a11y/anchor-has-content and vuejs-accessibility/anchor-has-content both flag anchors with no accessible-name source; our plugin doesn't have an equivalent today.
  • Conclusion: Add template-anchor-has-content. Flags <a href> with no text, no accessible-name attribute (aria-label / aria-labelledby / title), and no child that contributes an accessible name. Dynamic cases (mustache-only content) stay accepted — we can't know at lint time whether they resolve to a non-empty name.

Four ecosystem positions on valueless aria-hidden

The question "what does <span aria-hidden> (bare), aria-hidden="" (empty), or aria-hidden={{false}} mean?" has no single authoritative answer:

# Source Interpretation Evidence
1 jsx-a11y Valueless → hidden Side effect of jsx-ast-utils coercing valueless JSX → boolean true. Quirk: string aria-hidden="true" is NOT recognized. Not a deliberate ARIA interpretation.
2 vue-a11y Anything not literal "false" → hidden isHiddenFromScreenReader.ts: non-spec shortcut.
3 axe-core / W3C ACT Rules Valueless/empty → INCOMPLETE axe PR #3635; ACT Rule 6a7281 scopes out empty values as inapplicable.
4 WAI-ARIA 1.2 spec Valueless/empty → default undefined → not hidden §aria-hidden value table.

Design choice for this rule

We lean toward fewer false positives. For this rule, that means treating a child with valueless / empty aria-hidden as NOT hidden — if someone writes <a href="/x"><span aria-hidden>X</span></a>, the child's content likely still contributes a name, so we don't flag the anchor as having no accessible content. Only explicit aria-hidden="true" / {{true}} hides the child subtree from the name computation.

Prior art

Verified each peer in source:

Plugin Rule Behavior
jsx-a11y anchor-has-content Flags <a> without text content or a recognized accessible-name attribute.
vuejs-accessibility anchor-has-content Flags <a> lacking content and aria-label; configurable accessibleChildren / accessibleDirectives let callers widen what counts as a name source.
lit-a11y No anchor-has-content equivalent. Its anchor-is-valid covers href validity (noHref/invalidHref/preferButton aspects), not accessible-name content.
@angular-eslint/template No equivalent rule.

Flags

<a href="/x" />
<a href="/x"><span aria-hidden="true">X</span></a>
<a href="/x"><img aria-hidden="true" alt="Nope" /></a>
<a href="/x"><img /></a>
<a href="/x" aria-label="" />
<a href="/x">   </a>

Allows

<a href="/x">link text</a>
<a href="/x"><span aria-hidden>X</span></a>        {{! valueless — child content counts }}
<a href="/x"><img aria-hidden alt="Nope" /></a>    {{! same }}
<a href="/x" aria-label="Close" />
<a href="/x">{{@label}}</a>
<a href="/x"><img alt="Search" /></a>
<Link href="/x" />

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 21, 2026

🏎️ Benchmark Comparison

Benchmark Control (p50) Experiment (p50) Δ
🟢 js small 14.10 ms 13.22 ms -6.2%
js medium 6.76 ms 6.67 ms -1.3%
js large 2.72 ms 2.69 ms -0.8%
gjs small 1.12 ms 1.11 ms -0.5%
gjs medium 550.92 µs 553.13 µs +0.4%
gjs large 218.13 µs 217.71 µs -0.2%
gts small 1.10 ms 1.09 ms -0.3%
gts medium 554.16 µs 548.50 µs -1.0%
gts large 217.15 µs 217.56 µs +0.2%

🟢 faster · 🔴 slower · 🟠 slightly slower · ⚪ within 2%

Full mitata output
clk: ~2.75 GHz
cpu: AMD EPYC 9V74 80-Core Processor
runtime: node 24.15.0 (x64-linux)

benchmark                   avg (min … max) p75 / p99    (min … top 1%)
------------------------------------------- -------------------------------
js small (control)            16.39 ms/iter  17.62 ms █                    
                      (11.49 ms … 31.72 ms)  30.23 ms █▂▂ ▅                
                    (  5.70 mb …  10.17 mb)   7.27 mb ███▆█▃▃▆▃▁▁▃▃▁▃▁▆▁▃▁▃

js small (experiment)         13.97 ms/iter  15.15 ms    ▅█                
                      (11.81 ms … 18.80 ms)  18.40 ms  ▆▃██ ▃▆  ▃          
                    (  6.33 mb …   8.23 mb)   6.84 mb ████████▄▁█▄▄▁██▁▁▄▁▄

                             ┌                                            ┐
                             ╷┌──────────┬──┐                             ╷
          js small (control) ├┤          │  ├─────────────────────────────┤
                             ╵└──────────┴──┘                             ╵
                              ╷ ┌──┬──┐       ╷
       js small (experiment)  ├─┤  │  ├───────┤
                              ╵ └──┴──┘       ╵
                             └                                            ┘
                             11.49 ms           20.86 ms           30.23 ms

summary
  js small (experiment)
   1.17x faster than js small (control)

------------------------------------------- -------------------------------
js medium (control)            7.52 ms/iter   7.67 ms █▅                   
                       (6.41 ms … 16.71 ms)  15.07 ms ██                   
                    (  2.62 mb …   4.64 mb)   3.53 mb ██▄▆▆▂▃▃▃▃▁▁▁▂▁▁▁▁▁▂▂

js medium (experiment)         7.44 ms/iter   7.84 ms  █                   
                       (6.19 ms … 13.91 ms)  13.00 ms ▇█                   
                    (  2.60 mb …   4.41 mb)   3.54 mb ███▅▆▇▄▂▂▃▂▃▂▁▂▁▁▁▂▂▂

                             ┌                                            ┐
                              ╷┌────┬┐                                    ╷
         js medium (control)  ├┤    │├────────────────────────────────────┤
                              ╵└────┴┘                                    ╵
                             ╷┌────┬─┐                         ╷
      js medium (experiment) ├┤    │ ├─────────────────────────┤
                             ╵└────┴─┘                         ╵
                             └                                            ┘
                             6.19 ms           10.63 ms            15.07 ms

summary
  js medium (experiment)
   1.01x faster than js medium (control)

------------------------------------------- -------------------------------
js large (control)             3.27 ms/iter   3.41 ms  █▆                  
                       (2.22 ms … 12.22 ms)   8.21 ms  ██                  
                    (129.64 kb …   3.19 mb)   1.45 mb ▇██▇▄▅▃▁▂▂▃▂▂▂▁▁▁▁▁▁▁

js large (experiment)          3.18 ms/iter   3.41 ms  █                   
                        (2.38 ms … 6.83 ms)   6.34 ms ▂█▂                  
                    (736.95 kb …   2.27 mb)   1.42 mb ███▅▅▄▅▃▂▂▂▁▁▃▂▂▂▂▁▂▁

                             ┌                                            ┐
                             ╷  ┌────┬┐                                   ╷
          js large (control) ├──┤    │├───────────────────────────────────┤
                             ╵  └────┴┘                                   ╵
                              ╷ ┌───┬─┐                     ╷
       js large (experiment)  ├─┤   │ ├─────────────────────┤
                              ╵ └───┴─┘                     ╵
                             └                                            ┘
                             2.22 ms            5.21 ms             8.21 ms

summary
  js large (experiment)
   1.03x faster than js large (control)

------------------------------------------- -------------------------------
gjs small (control)            1.29 ms/iter   1.18 ms █                    
                        (1.08 ms … 6.69 ms)   5.50 ms █                    
                    (583.16 kb …   1.60 mb)   1.06 mb █▅▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gjs small (experiment)         1.25 ms/iter   1.14 ms █                    
                        (1.08 ms … 5.82 ms)   5.26 ms █                    
                    (220.87 kb …   1.79 mb)   1.06 mb █▃▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌─┬                                          ╷
         gjs small (control) │ │──────────────────────────────────────────┤
                             └─┴                                          ╵
                             ┌─┬                                        ╷
      gjs small (experiment) │ │────────────────────────────────────────┤
                             └─┴                                        ╵
                             └                                            ┘
                             1.08 ms            3.29 ms             5.50 ms

summary
  gjs small (experiment)
   1.04x faster than gjs small (control)

------------------------------------------- -------------------------------
gjs medium (control)         615.26 µs/iter 566.07 µs █                    
                      (530.03 µs … 5.74 ms)   1.48 ms █▆                   
                    ( 94.56 kb …   1.04 mb) 541.99 kb ██▂▃▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gjs medium (experiment)      620.48 µs/iter 560.53 µs █                    
                      (532.40 µs … 5.25 ms)   1.98 ms █                    
                    ( 85.52 kb …   1.15 mb) 540.71 kb █▄▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌──┬                         ╷
        gjs medium (control) │  │─────────────────────────┤
                             └──┴                         ╵
                             ┌──┬                                         ╷
     gjs medium (experiment) │  │─────────────────────────────────────────┤
                             └──┴                                         ╵
                             └                                            ┘
                             530.03 µs           1.25 ms            1.98 ms

summary
  gjs medium (control)
   1.01x faster than gjs medium (experiment)

------------------------------------------- -------------------------------
gjs large (control)          251.04 µs/iter 232.33 µs  █                   
                      (210.16 µs … 5.26 ms) 376.86 µs ██▃                  
                    ( 85.70 kb …   1.06 mb) 217.43 kb ███▅▃▂▂▅▃▂▂▁▁▁▁▁▁▁▁▁▁

gjs large (experiment)       242.70 µs/iter 227.80 µs  █                   
                      (210.76 µs … 4.79 ms) 307.51 µs  █                   
                    ( 16.87 kb … 776.88 kb) 216.55 kb ▆██▅▆▂▂▂▂▁▁▁▂▂▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ╷┌─────────┬                                 ╷
         gjs large (control) ├┤         │─────────────────────────────────┤
                             ╵└─────────┴                                 ╵
                             ╷┌───────┬                ╷
      gjs large (experiment) ├┤       │────────────────┤
                             ╵└───────┴                ╵
                             └                                            ┘
                             210.16 µs         293.51 µs          376.86 µs

summary
  gjs large (experiment)
   1.03x faster than gjs large (control)

------------------------------------------- -------------------------------
gts small (control)            1.21 ms/iter   1.12 ms █                    
                        (1.07 ms … 6.13 ms)   5.11 ms █                    
                    (165.99 kb …   1.79 mb)   1.06 mb █▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gts small (experiment)         1.20 ms/iter   1.11 ms █                    
                        (1.07 ms … 5.61 ms)   5.17 ms █                    
                    (196.13 kb …   1.72 mb)   1.05 mb █▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌─┬                                         ╷
         gts small (control) │ │─────────────────────────────────────────┤
                             └─┴                                         ╵
                             ┌┬                                           ╷
      gts small (experiment) ││───────────────────────────────────────────┤
                             └┴                                           ╵
                             └                                            ┘
                             1.07 ms            3.12 ms             5.17 ms

summary
  gts small (experiment)
   1.01x faster than gts small (control)

------------------------------------------- -------------------------------
gts medium (control)         604.48 µs/iter 564.68 µs  █                   
                      (531.20 µs … 5.24 ms)   1.04 ms  █                   
                    (267.80 kb …   1.65 mb) 542.10 kb ██▅▂▁▁▃▂▁▁▁▁▁▁▁▁▁▁▁▁▁

gts medium (experiment)      603.38 µs/iter 558.25 µs █                    
                      (529.65 µs … 5.40 ms)   1.89 ms █                    
                    ( 39.52 kb …   1.00 mb) 540.26 kb █▅▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ╷┌┬              ╷
        gts medium (control) ├┤│──────────────┤
                             ╵└┴              ╵
                             ┌─┬                                          ╷
     gts medium (experiment) │ │──────────────────────────────────────────┤
                             └─┴                                          ╵
                             └                                            ┘
                             529.65 µs           1.21 ms            1.89 ms

summary
  gts medium (experiment)
   1x faster than gts medium (control)

------------------------------------------- -------------------------------
gts large (control)          244.66 µs/iter 225.94 µs  █                   
                      (210.71 µs … 5.45 ms) 313.20 µs  █                   
                    (182.73 kb …   1.10 mb) 217.22 kb ▇█▇▇▄▂▂▁▁▁▁▂▂▁▁▁▁▁▁▁▁

gts large (experiment)       239.01 µs/iter 223.50 µs  █▄                  
                      (210.10 µs … 5.16 ms) 294.59 µs  ██                  
                    (183.82 kb … 808.45 kb) 216.50 kb ▃██▅▇▆▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ╷ ┌────────────┬                             ╷
         gts large (control) ├─┤            │─────────────────────────────┤
                             ╵ └────────────┴                             ╵
                             ╷ ┌──────────┬                       ╷
      gts large (experiment) ├─┤          │───────────────────────┤
                             ╵ └──────────┴                       ╵
                             └                                            ┘
                             210.10 µs         261.65 µs          313.20 µs

summary
  gts large (experiment)
   1.02x faster than gts large (control)

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new accessibility-focused template rule to the plugin to ensure <a href> elements expose a non-empty accessible name, aligning behavior with similar ecosystem rules while avoiding false positives for dynamic content.

Changes:

  • Introduces template-anchor-has-content rule with recursive accessible-name source detection (text, aria-*, title, <img alt>, aria-hidden handling).
  • Adds isNativeElement utility (HTML/SVG/MathML allowlist + scope-shadowing check) and unit tests.
  • Adds rule documentation and lists the new rule in the README.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
lib/rules/template-anchor-has-content.js Implements the new rule’s logic for detecting unlabeled <a href> anchors.
lib/utils/is-native-element.js Adds shared helper for native-element vs component discrimination (including scope shadowing).
tests/lib/rules/template-anchor-has-content.js Adds rule tests for both .gjs and .hbs parsing modes.
tests/lib/utils/is-native-element-test.js Adds unit tests for list-based native-element detection and tag set sanity checks.
docs/rules/template-anchor-has-content.md Documents rule behavior and examples.
README.md Adds the rule to the accessibility rules table.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/rules/template-anchor-has-content.md
Comment thread docs/rules/template-anchor-has-content.md Outdated
Comment thread lib/rules/template-anchor-has-content.js
Comment thread lib/rules/template-anchor-has-content.js
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/rules/template-anchor-has-content.js
Comment thread lib/rules/template-anchor-has-content.js
Comment thread tests/lib/rules/template-anchor-has-content.js
@johanrd johanrd force-pushed the feat/template-anchor-has-content branch from a071bfc to 6030630 Compare April 22, 2026 17:13
@johanrd johanrd requested a review from Copilot April 24, 2026 08:37
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/utils/is-native-element.js
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/lib/rules/template-anchor-has-content.js Outdated
Comment thread lib/utils/is-native-element.js Outdated
Comment thread lib/rules/template-anchor-has-content.js
Comment thread lib/rules/template-anchor-has-content.js
johanrd added a commit that referenced this pull request Apr 24, 2026
… via shared helper (Copilot review)

Extract a new `getStaticAttrValue` util that resolves literal-valued
mustaches (`{{"foo"}}`, `{{true}}`, `{{-1}}`) and single-part concat
statements (`"{{true}}"`) to their static string value. `isAriaHiddenTruthy`
now delegates to the helper and compares the resolved string to `'true'`
(case-insensitive, whitespace-trimmed).

Behavior change: valueless `<h1 aria-hidden>`, `aria-hidden=""`, and the
mustache-empty-string equivalents (`aria-hidden={{""}}`, `aria-hidden="{{""}}"`,
`aria-hidden={{" "}}`) are no longer treated as hidden. Per WAI-ARIA 1.2
§6.6 value table, those shapes resolve to the default `undefined` — NOT
`true` — so the empty-content check still applies. Drops the previous
"fewer false positives" deviation rationale in favour of spec-literal
consistency with sibling rules (#19, #35, #41) that share the same
aria-hidden resolution.

Byte-identical carrier of lib/utils/static-attr-value.js across all PRs
that land it.
@johanrd johanrd requested a review from Copilot April 24, 2026 17:48
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@johanrd johanrd force-pushed the feat/template-anchor-has-content branch from 211ed97 to 88237d9 Compare April 26, 2026 08:09
@johanrd johanrd requested a review from Copilot April 26, 2026 08:42
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/rules/template-anchor-has-content.js
Comment thread lib/rules/template-anchor-has-content.js
Comment thread tests/lib/rules/template-anchor-has-content.js
@johanrd johanrd force-pushed the feat/template-anchor-has-content branch from 4497502 to 34cb30b Compare April 27, 2026 14:04
@johanrd johanrd force-pushed the feat/template-anchor-has-content branch from 33d4351 to 0172580 Compare April 27, 2026 19:28
@johanrd johanrd requested a review from Copilot April 27, 2026 19:34
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +104 to +110
// HTML boolean `hidden` (§5.4) removes the element from rendering AND
// from the accessibility tree — equivalent to aria-hidden="true" for
// accessible-name purposes. A <span hidden>Backup</span> inside an
// anchor contributes no name at runtime.
if (attrs.some((a) => a.name === 'hidden')) {
return { dynamic: false, accessible: false };
}
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The child hidden handling treats any presence of a hidden attribute as meaning “definitely hidden”, but in Ember templates hidden={{false}} commonly results in the attribute being omitted at runtime (meaning the element is visible and its text should contribute to the anchor’s accessible name). This can cause false positives (flagging an anchor as empty even though it has visible child text). Consider resolving hidden similarly to aria-hidden: only treat it as hidden when it’s statically true (e.g. valueless hidden, or mustache-literal {{true}} / {{"true"}} / other statically-known truthy forms), and treat truly dynamic values as “unknown” (likely best as { dynamic: true } so the anchor isn’t flagged).

Copilot uses AI. Check for mistakes.
Comment on lines +215 to +222
// Skip anchors the author has explicitly hidden — either via the HTML
// `hidden` boolean attribute (element is not rendered at all) or
// `aria-hidden="true"` (element removed from the accessibility tree).
// In both cases, "accessible name of an anchor" is moot.
const hasHidden = attrs.some((a) => a.name === 'hidden');
if (hasHidden) {
return;
}
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This skips reporting whenever a hidden attribute is present, but in templates hidden={{false}} should not skip the rule (the anchor is not hidden at runtime). This creates false negatives where empty anchors can slip through linting. Recommend changing the logic to only skip when hidden is statically true; if hidden is dynamic/unknown, consider the project’s “fewer false positives” stance and either (a) treat dynamic as hidden (skip) consistently, or (b) treat dynamic as unknown and skip reporting (similar to how dynamic content is handled), but avoid treating hidden={{false}} as hidden.

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +27
const resolved = getStaticAttrValue(attr.value);
if (resolved === undefined) {
// Dynamic — can't prove truthy.
return false;
}
return resolved.trim().toLowerCase() === 'true';
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolved.trim() assumes getStaticAttrValue always returns a string when it can resolve a value. If it can return booleans for mustache literals (e.g. {{true}} / {{false}}) or null for valueless attributes, this will throw at runtime. Making this robust (e.g., explicitly handling resolved === true, resolved === false, and only calling .trim() when typeof resolved === 'string') will prevent rule crashes on edge-case AST/value shapes.

Copilot uses AI. Check for mistakes.
Comment on lines +98 to +103
// Anchor itself hidden via HTML `hidden` boolean attribute — element is
// not rendered, so "accessible name of an anchor" is moot.
{
filename: 'test.gjs',
code: '<template><a href="/x" hidden /></template>',
},
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The suite covers statically-present hidden, but it doesn’t cover the important template case hidden={{false}} (visible at runtime) for both (a) the anchor itself and (b) child elements inside the anchor. Adding tests for hidden={{false}} would lock in correct behavior and prevent regressions given the rule currently keys off attribute presence.

Copilot uses AI. Check for mistakes.
@johanrd johanrd closed this Apr 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants