Skip to content

BUGFIX: template-require-mandatory-role-attributes — lowercase role + split whitespace role lists#13

Closed
johanrd wants to merge 5 commits intomasterfrom
fix/role-required-aria-case-and-space-split
Closed

BUGFIX: template-require-mandatory-role-attributes — lowercase role + split whitespace role lists#13
johanrd wants to merge 5 commits intomasterfrom
fix/role-required-aria-case-and-space-split

Conversation

@johanrd
Copy link
Copy Markdown
Owner

@johanrd johanrd commented Apr 21, 2026

Two related fixes (shared root cause — both deal with role-token normalisation).

1. Case-insensitive role comparison

  • Premise: ARIA 1.2 §4.1 WAI-ARIA Roles and HTML-AAM inherit ASCII-case-insensitive comparison from HTML attribute-value semantics.
  • Problem: <div role="COMBOBOX"> was silently accepted — roles.get("COMBOBOX") returns undefined in aria-query, so the rule skipped the check entirely.

Fix: lowercase the role value before the lookup.

2. Whitespace-separated role fallback lists

  • Premise: ARIA 1.2 §4.1 WAI-ARIA Roles — a role attribute may list multiple tokens to express a fallback; user agents pick the first they recognise.
  • Problem: role="combobox listbox" was treated as one opaque string. aria-query returned undefined, the rule skipped silently, and <div role="combobox listbox"> without aria-expanded + aria-controls wasn't flagged.

Fix: split on whitespace, validate the first recognised role per ARIA §4.1 role-fallback semantics. This diverges from jsx-a11y, which validates every recognised token rather than only the first.

Helpers renamed to plural forms (getStaticRolesFromElement, getStaticRolesFromMustache). getMissingRequiredAttributes now returns { role, missing } so the reporter can cite the actually-checked role in the error message.

Four new tests cover both fixes (valid + invalid of each).

Prior art

Plugin Rule Notes
jsx-a11y role-has-required-aria-props .toLowerCase().split(' ') of the role value; filters to recognised tokens, then validates every one.
vuejs-accessibility role-has-required-aria-props Same pattern as jsx-a11y.
@angular-eslint/template role-has-required-aria No splitting; passes the raw role string directly to aria-query's roles.get.
lit-a11y role-has-required-aria-attrs No splitting; passes the raw role attribute value to roles.get.

Upstream ember-template-lint@7.9.3 has both gaps.

…t whitespace-separated role lists

Two changes, shared root cause (both deal with role-token normalisation).

1. Case-insensitive role matching. Per HTML-AAM, ARIA role tokens
   compare as ASCII-case-insensitive. Before: <div role="COMBOBOX">
   was silently accepted because "COMBOBOX" didn't match "combobox" in
   aria-query. Now: lowercase before lookup.

2. Whitespace-separated role fallback lists. Per ARIA 1.2 §5.4, a role
   attribute may list multiple tokens to express a fallback; a UA picks
   the first one it recognises. Before: role="combobox listbox" was
   treated as one opaque string, aria-query returned undefined, and
   the rule skipped silently. Now: split on whitespace, check against
   the first recognised role's required attributes (matching jsx-a11y).

Helpers renamed to plural forms (getStaticRolesFromElement,
getStaticRolesFromMustache) and getMissingRequiredAttributes now
returns { role, missing } so the reporter can use the actually-checked
role in the error message.

Four new tests cover the two cases (valid + invalid of each).
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 21, 2026

🏎️ Benchmark Comparison

Benchmark Control (p50) Experiment (p50) Δ
js small 13.69 ms 13.51 ms -1.3%
🟢 js medium 6.73 ms 6.58 ms -2.3%
🔴 js large 2.51 ms 2.66 ms +5.8%
gjs small 1.11 ms 1.11 ms -0.3%
gjs medium 553.40 µs 550.53 µs -0.5%
gjs large 218.46 µs 218.29 µs -0.1%
gts small 1.10 ms 1.10 ms -0.5%
gts medium 552.86 µs 553.12 µs +0.0%
gts large 219.11 µs 218.37 µs -0.3%

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

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

benchmark                   avg (min … max) p75 / p99    (min … top 1%)
------------------------------------------- -------------------------------
js small (control)            16.64 ms/iter  18.02 ms █                    
                      (11.54 ms … 33.61 ms)  30.62 ms █ ▇ ▅                
                    (  5.63 mb …  10.13 mb)   7.26 mb ███▃█▁▆▆▁▁▁▃▁▃▃▁▃▃▃▁▃

js small (experiment)         14.22 ms/iter  15.65 ms    █▅                
                      (11.94 ms … 21.05 ms)  18.81 ms ▃▃ ██▆      █        
                    (  6.11 mb …   8.40 mb)   6.84 mb ███████▄▄▁█▄███▁▁▁▁▁▄

                             ┌                                            ┐
                             ╷┌──────────┬──┐                             ╷
          js small (control) ├┤          │  ├─────────────────────────────┤
                             ╵└──────────┴──┘                             ╵
                              ╷ ┌──┬───┐      ╷
       js small (experiment)  ├─┤  │   ├──────┤
                              ╵ └──┴───┘      ╵
                             └                                            ┘
                             11.54 ms           21.08 ms           30.62 ms

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

------------------------------------------- -------------------------------
js medium (control)            7.49 ms/iter   7.97 ms  █                   
                       (6.18 ms … 15.03 ms)  13.20 ms ▅█▅                  
                    (  2.61 mb …   4.62 mb)   3.54 mb ███▅▃▅▄▅▂▁▅▁▂▁▂▁▁▁▁▃▂

js medium (experiment)         7.32 ms/iter   7.62 ms  █                   
                       (6.09 ms … 13.40 ms)  13.30 ms  █                   
                    (  1.91 mb …   4.26 mb)   3.50 mb ██▆▇▄▃▂▄▂▃▂▁▂▂▁▁▂▁▁▁▂

                             ┌                                            ┐
                              ╷ ┌─────┬──┐                               ╷
         js medium (control)  ├─┤     │  ├───────────────────────────────┤
                              ╵ └─────┴──┘                               ╵
                             ╷ ┌─────┬─┐                                  ╷
      js medium (experiment) ├─┤     │ ├──────────────────────────────────┤
                             ╵ └─────┴─┘                                  ╵
                             └                                            ┘
                             6.09 ms            9.69 ms            13.30 ms

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

------------------------------------------- -------------------------------
js large (control)             2.86 ms/iter   2.68 ms  █                   
                       (2.25 ms … 11.39 ms)   6.30 ms ▅█▂                  
                    ( 73.16 kb …   3.15 mb)   1.42 mb ███▄▃▂▂▁▁▂▂▁▁▁▁▁▁▁▂▁▁

js large (experiment)          3.44 ms/iter   3.92 ms █                    
                       (2.42 ms … 13.24 ms)  11.28 ms █                    
                    (381.23 kb …   2.49 mb)   1.43 mb █▆▆▄▆▂▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ╷┌─┬                ╷
          js large (control) ├┤ │────────────────┤
                             ╵└─┴                ╵
                              ┌────┬─┐                                    ╷
       js large (experiment)  │    │ ├────────────────────────────────────┤
                              └────┴─┘                                    ╵
                             └                                            ┘
                             2.25 ms            6.76 ms            11.28 ms

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

------------------------------------------- -------------------------------
gjs small (control)            1.24 ms/iter   1.15 ms █                    
                        (1.09 ms … 6.75 ms)   5.90 ms █                    
                    (196.45 kb …   1.56 mb)   1.06 mb █▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gjs small (experiment)         1.25 ms/iter   1.13 ms █                    
                        (1.08 ms … 6.95 ms)   6.02 ms █                    
                    (196.71 kb …   1.77 mb)   1.06 mb █▁▁▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

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

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

------------------------------------------- -------------------------------
gjs medium (control)         627.99 µs/iter 564.53 µs █                    
                      (533.12 µs … 6.83 ms)   3.72 ms █                    
                    (  7.86 kb …   1.25 mb) 541.55 kb █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gjs medium (experiment)      605.78 µs/iter 558.23 µs █                    
                      (532.30 µs … 6.76 ms)   2.95 ms █                    
                    (211.24 kb …   1.25 mb) 541.67 kb █▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌┬                                           ╷
        gjs medium (control) ││───────────────────────────────────────────┤
                             └┴                                           ╵
                             ┌┬                                ╷
     gjs medium (experiment) ││────────────────────────────────┤
                             └┴                                ╵
                             └                                            ┘
                             532.30 µs           2.12 ms            3.72 ms

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

------------------------------------------- -------------------------------
gjs large (control)          242.44 µs/iter 225.81 µs  █                   
                      (211.68 µs … 5.82 ms) 299.43 µs  █▅                  
                    ( 19.67 kb … 776.80 kb) 216.96 kb ▄██▅▇▄▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gjs large (experiment)       241.32 µs/iter 225.47 µs  █                   
                      (212.10 µs … 5.95 ms) 297.88 µs  █▃                  
                    (  6.77 kb … 672.49 kb) 216.66 kb ▅██▅▇▄▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ╷ ┌─────────────┬                            ╷
         gjs large (control) ├─┤             │────────────────────────────┤
                             ╵ └─────────────┴                            ╵
                             ╷ ┌────────────┬                            ╷
      gjs large (experiment) ├─┤            │────────────────────────────┤
                             ╵ └────────────┴                            ╵
                             └                                            ┘
                             211.68 µs         255.55 µs          299.43 µs

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

------------------------------------------- -------------------------------
gts small (control)            1.21 ms/iter   1.12 ms █                    
                        (1.08 ms … 6.84 ms)   5.73 ms █                    
                    (637.90 kb …   1.57 mb)   1.06 mb █▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gts small (experiment)         1.20 ms/iter   1.11 ms █                    
                        (1.08 ms … 6.87 ms)   6.14 ms █                    
                    (292.00 kb …   1.84 mb)   1.05 mb █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌┬                                       ╷
         gts small (control) ││───────────────────────────────────────┤
                             └┴                                       ╵
                             ┌┬                                           ╷
      gts small (experiment) ││───────────────────────────────────────────┤
                             └┴                                           ╵
                             └                                            ┘
                             1.08 ms            3.61 ms             6.14 ms

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

------------------------------------------- -------------------------------
gts medium (control)         598.91 µs/iter 560.50 µs █                    
                      (532.93 µs … 5.67 ms)   1.56 ms █▂                   
                    ( 87.25 kb …   1.20 mb) 541.28 kb ██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gts medium (experiment)      599.81 µs/iter 560.45 µs ▇█                   
                      (533.38 µs … 5.74 ms)   1.30 ms ██                   
                    (106.49 kb …   1.38 mb) 540.48 kb ██▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ╷┌─┬                                         ╷
        gts medium (control) ├┤ │─────────────────────────────────────────┤
                             ╵└─┴                                         ╵
                             ╷┌─┬                              ╷
     gts medium (experiment) ├┤ │──────────────────────────────┤
                             ╵└─┴                              ╵
                             └                                            ┘
                             532.93 µs           1.05 ms            1.56 ms

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

------------------------------------------- -------------------------------
gts large (control)          240.54 µs/iter 226.20 µs  █▃                  
                      (211.71 µs … 5.63 ms) 286.76 µs  ██                  
                    (216.09 kb … 966.18 kb) 217.04 kb ▃███▇▆▄▂▂▁▁▁▁▁▁▁▁▁▁▁▁

gts large (experiment)       241.32 µs/iter 225.04 µs  █                   
                      (211.65 µs … 6.01 ms) 296.33 µs  █▇                  
                    ( 16.88 kb … 801.86 kb) 216.50 kb ▄██▄▇▅▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ╷ ┌────────────┬                        ╷
         gts large (control) ├─┤            │────────────────────────┤
                             ╵ └────────────┴                        ╵
                             ╷ ┌─────────────┬                            ╷
      gts large (experiment) ├─┤             │────────────────────────────┤
                             ╵ └─────────────┴                            ╵
                             └                                            ┘
                             211.65 µs         253.99 µs          296.33 µs

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

johanrd and others added 4 commits April 21, 2026 08:35
…back semantics

The previous comment said this matched jsx-a11y, but jsx-a11y validates
every recognised role token while we check only the first recognised
role per ARIA role-fallback semantics. Update the comment to reflect
the PR description's stated behavior.
…er cases

Translates 33 cases from peer-plugin rules:
  - jsx-a11y role-has-required-aria-props
  - vuejs-accessibility role-has-required-aria-props
  - @angular-eslint/template role-has-required-aria
  - lit-a11y role-has-required-aria-attrs

Fixture documents parity after this fix:
  - Case-insensitive role comparison (<div role="COMBOBOX"> flagged).
  - Whitespace-separated roles: first recognised token is validated
    (ARIA 1.2 §4.1 role-fallback semantics). Divergence from jsx-a11y,
    which validates every recognised token; documented inline.

The semantic input+role exception (input[type=checkbox] role=switch)
remains flagged here — that fix is on a separate branch.
@johanrd
Copy link
Copy Markdown
Owner Author

johanrd commented Apr 21, 2026

Moved upstream to ember-cli#2728. See that PR.

@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