Skip to content

BUGFIX: template-no-empty-headings — recognize boolean aria-hidden#8

Closed
johanrd wants to merge 1 commit intomasterfrom
fix/heading-accept-boolean-aria-hidden
Closed

BUGFIX: template-no-empty-headings — recognize boolean aria-hidden#8
johanrd wants to merge 1 commit intomasterfrom
fix/heading-accept-boolean-aria-hidden

Conversation

@johanrd
Copy link
Copy Markdown
Owner

@johanrd johanrd commented Apr 21, 2026

  • Premise 1: In HTML, boolean attributes are truthy when present, regardless of value — so <h1 aria-hidden />, <h1 aria-hidden="" />, and <h1 aria-hidden="true" /> are all intended to hide the heading from assistive technology.
  • Premise 2: Our isHidden check only matched aria-hidden="true" as a string literal.
  • Conclusion: Headings hidden via boolean, valueless, empty-string, or mustache-boolean aria-hidden were treated as visible — a false positive on <h1 aria-hidden />-style intentionally-hidden empty headings.

Fix: extract isAriaHiddenTruthy(); accept valueless, empty, "true", {{true}}, and {{"true"}} forms.

Known limitation: the comparison is strict === 'true', so ASCII-case variants like aria-hidden="TRUE" / aria-hidden="True" are NOT treated as truthy. If desired, this can be addressed in a follow-up.

Strictly, the ARIA 1.2 spec treats a bare aria-hidden attribute as "undefined" rather than "true". But every major linter treats it as truthy (see Prior art), matching real-world browser/AT behavior.

Four new test cases covering each recognized form.

Prior art

Plugin Rule Notes
jsx-a11y heading-has-content Treats <h1 aria-hidden /> as hidden; valueless boolean attribute counts as truthy.
vuejs-accessibility heading-has-content Same semantics as jsx-a11y.
lit-a11y heading-hidden Complementary rule — flags aria-hidden on a heading as its own a11y problem.

Upstream [email protected] has the same false positive.

Before: isHidden only matched aria-hidden="true" as a string literal.
Boolean / valueless / empty / mustache forms (<h1 aria-hidden />,
<h1 aria-hidden="" />, <h1 aria-hidden={{true}} />) slipped past as
"not hidden", so empty headings in those forms were flagged as empty
even when the author had intentionally hidden them from AT.

Fix: extract isAriaHiddenTruthy(). Recognize:
- valueless attribute (HBS AST has value=null or empty-string TextNode)
- "true" string literal (preserved)
- "" empty string
- {{true}} boolean mustache literal
- {{"true"}} string mustache literal

Per HTML boolean-attribute semantics (and jsx-a11y/vue-a11y convention),
presence of aria-hidden without an explicit "false" value is treated as
truthy. The strict ARIA spec treats bare aria-hidden as "undefined"
rather than "true", but every major linter in the ecosystem (and most
screen readers) treats it as true.

Four new test cases covering each of the recognized forms.
@github-actions
Copy link
Copy Markdown

🏎️ Benchmark Comparison

Benchmark Control (p50) Experiment (p50) Δ
js small 14.40 ms 14.19 ms -1.4%
🔴 js medium 7.27 ms 8.78 ms +20.8%
🟢 js large 2.88 ms 2.71 ms -5.8%
gjs small 1.26 ms 1.24 ms -1.3%
gjs medium 623.56 µs 625.06 µs +0.2%
gjs large 249.19 µs 246.71 µs -1.0%
gts small 1.24 ms 1.24 ms -0.4%
gts medium 630.69 µs 624.91 µs -0.9%
gts large 250.15 µs 248.05 µs -0.8%

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

Full mitata output
clk: ~3.10 GHz
cpu: AMD EPYC 7763 64-Core Processor
runtime: node 24.14.1 (x64-linux)

benchmark                   avg (min … max) p75 / p99    (min … top 1%)
------------------------------------------- -------------------------------
js small (control)            17.16 ms/iter  19.44 ms █ ▂                  
                      (12.52 ms … 31.39 ms)  28.43 ms █▅█ ▂                
                    (  5.23 mb …  10.61 mb)   7.27 mb ███▇█▄▇▁▁▇▁▁▁▄▄▄▄▁▁▇▄

js small (experiment)         14.86 ms/iter  15.61 ms    █                 
                      (13.15 ms … 20.46 ms)  18.74 ms  ▆▃█ ▃               
                    (  6.75 mb …   8.22 mb)   6.83 mb ▆███▆█▄▆▄▆█▁▁▄▆▁▁▄▁▁▄

                             ┌                                            ┐
                             ╷┌───────────┬──────┐                        ╷
          js small (control) ├┤           │      ├────────────────────────┤
                             ╵└───────────┴──────┘                        ╵
                               ╷┌───┬─┐        ╷
       js small (experiment)   ├┤   │ ├────────┤
                               ╵└───┴─┘        ╵
                             └                                            ┘
                             12.52 ms           20.48 ms           28.43 ms

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

------------------------------------------- -------------------------------
js medium (control)            8.05 ms/iter   8.33 ms  █                   
                       (6.79 ms … 14.90 ms)  14.52 ms ▇█▂                  
                    (  2.62 mb …   4.34 mb)   3.56 mb ███▄▄█▂▁▃▁▂▂▁▁▂▃▁▁▂▁▂

js medium (experiment)         9.72 ms/iter  11.93 ms ▆           █        
                       (6.87 ms … 15.76 ms)  15.62 ms █▆ ▃▆       █        
                    (  2.51 mb …   4.49 mb)   3.57 mb ██▇██▇▃▃▃█▅▇█▁▁▇▃▁▁▁▃

                             ┌                                            ┐
                             ╷┌────┬─┐                              ╷
         js medium (control) ├┤    │ ├──────────────────────────────┤
                             ╵└────┴─┘                              ╵
                             ╷   ┌──────────┬──────────┐                  ╷
      js medium (experiment) ├───┤          │          ├──────────────────┤
                             ╵   └──────────┴──────────┘                  ╵
                             └                                            ┘
                             6.79 ms           11.21 ms            15.62 ms

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

------------------------------------------- -------------------------------
js large (control)             3.11 ms/iter   2.99 ms  █                   
                        (2.69 ms … 6.09 ms)   5.89 ms ▆█                   
                    (173.24 kb …   2.93 mb)   1.43 mb ███▂▃▂▂▂▃▁▂▂▁▂▁▁▁▁▂▁▁

js large (experiment)          3.24 ms/iter   2.98 ms  █                   
                       (2.49 ms … 13.38 ms)   8.82 ms ▆█                   
                    (316.35 kb …   3.26 mb)   1.44 mb ██▄▃▃▁▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                              ╷┌─┬                   ╷
          js large (control)  ├┤ │───────────────────┤
                              ╵└─┴                   ╵
                             ╷┌───┬                                       ╷
       js large (experiment) ├┤   │───────────────────────────────────────┤
                             ╵└───┴                                       ╵
                             └                                            ┘
                             2.49 ms            5.66 ms             8.82 ms

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

------------------------------------------- -------------------------------
gjs small (control)            1.41 ms/iter   1.35 ms █                    
                        (1.21 ms … 6.85 ms)   5.20 ms █▄                   
                    (356.30 kb …   1.62 mb)   1.06 mb ██▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gjs small (experiment)         1.36 ms/iter   1.28 ms █                    
                        (1.20 ms … 6.25 ms)   5.05 ms █                    
                    (351.92 kb …   1.78 mb)   1.06 mb █▆▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌─┬                                          ╷
         gjs small (control) │ │──────────────────────────────────────────┤
                             └─┴                                          ╵
                             ┌─┬                                        ╷
      gjs small (experiment) │ │────────────────────────────────────────┤
                             └─┴                                        ╵
                             └                                            ┘
                             1.20 ms            3.20 ms             5.20 ms

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

------------------------------------------- -------------------------------
gjs medium (control)         675.16 µs/iter 640.57 µs █                    
                      (590.57 µs … 5.77 ms)   2.92 ms █                    
                    (383.38 kb …   1.09 mb) 542.01 kb █▅▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gjs medium (experiment)      665.96 µs/iter 639.58 µs  █                   
                      (589.71 µs … 5.14 ms)   1.54 ms ▂█                   
                    ( 18.58 kb …   1.65 mb) 540.83 kb ██▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌─┬                                          ╷
        gjs medium (control) │ │──────────────────────────────────────────┤
                             └─┴                                          ╵
                             ┌┬                ╷
     gjs medium (experiment) ││────────────────┤
                             └┴                ╵
                             └                                            ┘
                             589.71 µs           1.75 ms            2.92 ms

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

------------------------------------------- -------------------------------
gjs large (control)          281.28 µs/iter 265.38 µs █▇                   
                      (236.77 µs … 5.51 ms) 620.30 µs ██▄                  
                    ( 17.09 kb … 966.14 kb) 217.41 kb ███▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gjs large (experiment)       270.35 µs/iter 264.07 µs  █                   
                      (235.88 µs … 4.95 ms) 392.47 µs ▂█▂                  
                    (179.86 kb … 801.77 kb) 216.82 kb ███▄█▅▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ╷┌───┬                                       ╷
         gjs large (control) ├┤   │───────────────────────────────────────┤
                             ╵└───┴                                       ╵
                             ╷┌──┬             ╷
      gjs large (experiment) ├┤  │─────────────┤
                             ╵└──┴             ╵
                             └                                            ┘
                             235.88 µs         428.09 µs          620.30 µs

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

------------------------------------------- -------------------------------
gts small (control)            1.35 ms/iter   1.27 ms █                    
                        (1.20 ms … 6.77 ms)   5.43 ms █                    
                    (102.45 kb …   2.03 mb)   1.06 mb █▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gts small (experiment)         1.34 ms/iter   1.27 ms █                    
                        (1.20 ms … 6.61 ms)   5.60 ms █                    
                    ( 84.77 kb …   1.72 mb)   1.05 mb █▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌─┬                                        ╷
         gts small (control) │ │────────────────────────────────────────┤
                             └─┴                                        ╵
                             ┌┬                                           ╷
      gts small (experiment) ││───────────────────────────────────────────┤
                             └┴                                           ╵
                             └                                            ┘
                             1.20 ms            3.40 ms             5.60 ms

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

------------------------------------------- -------------------------------
gts medium (control)         683.61 µs/iter 646.24 µs █                    
                      (597.62 µs … 5.59 ms)   3.17 ms █                    
                    ( 89.90 kb …   1.26 mb) 542.53 kb █▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gts medium (experiment)      681.05 µs/iter 638.13 µs ▂█                   
                      (593.28 µs … 5.28 ms)   1.70 ms ██                   
                    (357.09 kb …   1.23 mb) 541.43 kb ██▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌─┬                                          ╷
        gts medium (control) │ │──────────────────────────────────────────┤
                             └─┴                                          ╵
                             ┌─┬                ╷
     gts medium (experiment) │ │────────────────┤
                             └─┴                ╵
                             └                                            ┘
                             593.28 µs           1.88 ms            3.17 ms

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

------------------------------------------- -------------------------------
gts large (control)          277.45 µs/iter 266.28 µs  █▂                  
                      (239.01 µs … 5.44 ms) 364.10 µs ▂██                  
                    ( 17.28 kb … 739.43 kb) 216.99 kb ███▄▇█▆▂▂▂▁▁▁▁▁▁▁▁▁▁▁

gts large (experiment)       272.61 µs/iter 265.34 µs  █                   
                      (236.48 µs … 5.08 ms) 371.72 µs  █▆                  
                    (137.99 kb … 965.79 kb) 216.77 kb ███▅▇▇▅▂▂▂▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                              ╷┌───────────┬                           ╷
         gts large (control)  ├┤           │───────────────────────────┤
                              ╵└───────────┴                           ╵
                             ╷ ┌─────────┬                                ╷
      gts large (experiment) ├─┤         │────────────────────────────────┤
                             ╵ └─────────┴                                ╵
                             └                                            ┘
                             236.48 µs         304.10 µs          371.72 µs

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

@johanrd
Copy link
Copy Markdown
Owner Author

johanrd commented Apr 21, 2026

Moved upstream to ember-cli#2717. See that PR for ongoing review.

@johanrd johanrd closed this Apr 21, 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.

1 participant