Skip to content

BUGFIX: template-no-unsupported-role-attributes — honor aria-query attribute constraints#9

Closed
johanrd wants to merge 3 commits into
masterfrom
fix/implicit-role-attribute-constraints
Closed

BUGFIX: template-no-unsupported-role-attributes — honor aria-query attribute constraints#9
johanrd wants to merge 3 commits into
masterfrom
fix/implicit-role-attribute-constraints

Conversation

@johanrd
Copy link
Copy Markdown
Owner

@johanrd johanrd commented Apr 21, 2026

  • Premise 1: aria-query's elementRoles map encodes element → implicit-role pairings that depend on attribute state. Each entry lists attributes that must be set, undefined, or match a specific value.
  • Premise 2: Our getImplicitRole helper returned the first entry whose tag name matched, ignoring the attribute constraints on subsequent entries.
  • Conclusion: Several common elements got the wrong implicit role.

Concrete consequences of the old logic:

HTML Implicit role we returned Correct implicit role
<input type="text"> (no list) combobox textbox
<input type="email|tel|url"> (no list) combobox textbox
<input type="password"> button none (unmapped by aria-query; global ARIA attrs apply)

The first two block valid usages involving attributes that textbox supports but combobox does not — notably aria-placeholder (covered by the new tests) and aria-multiline. Both textbox and combobox list aria-required in aria-query 5.3.2, so that attribute is not the concern here. The third row is arbitrarily wrong — there's no reason a password input should ever be treated as a button. <input type="password"> is intentionally left unmapped because aria-query gives it no role; this diverges from HTML-AAM, which maps it to textbox.

Fix: walk every elementRoles entry whose name matches the tag; evaluate each attribute spec (checking constraints: ["set"], constraints: ["undefined"], and required values); pick the entry with the most attribute constraints satisfied.

Eight new valid tests cover the textbox/password paths. One existing test updated: <input type="email" aria-level={{this.level}} /> now maps to textbox in the error message; a sibling case with list="x" covers the combobox path.

Prior art

Plugin Rule Mechanism
jsx-a11y role-supports-aria-props via getImplicitRole + per-element implicit-role files under src/util/implicitRoles/. Dedicated per-element lookup; e.g. a.js checks for href before returning "link".
lit-a11y role-supports-aria-attr Only reacts to an explicit role= attribute; does not compute an implicit role from the element + attribute state, so this false positive does not arise there.

Upstream ember-template-lint@7.9.3 has the same mis-roling.

…ute constraints in implicit-role lookup

The old getImplicitRole picked the first elementRoles entry whose name
matched the tag. For <input> it tried a type-attribute match but
ignored the rest of aria-query's constraint annotations.

Concrete consequences of the old logic:

- <input type="text"> without a `list` attribute returned "combobox"
  (because aria-query's {type=text, list=set}→combobox entry appears
  before {type=text, list=undefined}→textbox). Correct implicit role is
  textbox.
- <input type="email|tel|url"> behaved the same way.
- <input type="password"> isn't mapped by aria-query; the fallback
  branch returned the first input entry — "button".

The new implementation walks every elementRoles key whose tag matches,
checks each attribute spec (honoring `constraints: ["set"]` and
`constraints: ["undefined"]` as well as required values), and picks the
entry with the most attribute constraints satisfied.

Behavioral impact:

- Text-like inputs without a list attribute now correctly resolve to
  textbox and accept aria-required / aria-readonly / aria-placeholder.
- <input type="password"> now resolves to no implicit role, so global
  ARIA attrs don't trip the rule (matching jsx-a11y's treatment).
- One existing test updated: <input type="email"> now maps to textbox
  in the error message; added a sibling case with `list="x"` to cover
  the combobox path.
- Eight new valid tests cover the textbox/password paths.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 21, 2026

🏎️ Benchmark Comparison

Benchmark Control (p50) Experiment (p50) Δ
js small 13.87 ms 13.69 ms -1.3%
js medium 6.79 ms 6.86 ms +0.9%
🟢 js large 2.71 ms 2.61 ms -3.4%
gjs small 1.13 ms 1.13 ms -0.3%
gjs medium 566.20 µs 564.36 µs -0.3%
gjs large 224.10 µs 225.23 µs +0.5%
gts small 1.14 ms 1.14 ms +0.5%
gts medium 570.78 µs 569.13 µs -0.3%
gts large 225.12 µs 223.09 µs -0.9%

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

Full mitata output
clk: ~2.75 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)            17.11 ms/iter  18.42 ms █ ▅                  
                      (11.58 ms … 32.53 ms)  30.68 ms ███                  
                    (  5.66 mb …  10.45 mb)   7.28 mb █████▄██▄▁▁▄▁▁▄▁█▄▁▄▄

js small (experiment)         14.50 ms/iter  15.27 ms  █▄▄█                
                      (11.92 ms … 20.65 ms)  20.63 ms  ████  ▅▅            
                    (  6.70 mb …   8.25 mb)   6.85 mb ▅████████▅█▅▁▁▅▅▁▅▁▁▅

                             ┌                                            ┐
                             ╷┌───────────┬──┐                            ╷
          js small (control) ├┤           │  ├────────────────────────────┤
                             ╵└───────────┴──┘                            ╵
                              ╷ ┌───┬─┐           ╷
       js small (experiment)  ├─┤   │ ├───────────┤
                              ╵ └───┴─┘           ╵
                             └                                            ┘
                             11.58 ms           21.13 ms           30.68 ms

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

------------------------------------------- -------------------------------
js medium (control)            7.59 ms/iter   7.99 ms  █                   
                       (6.27 ms … 14.24 ms)  13.12 ms ▃█                   
                    (  2.77 mb …   4.94 mb)   3.55 mb ███▆▃▄▃▅▃▂▁▃▁▁▁▃▁▂▁▃▂

js medium (experiment)         7.74 ms/iter   8.44 ms  █                   
                       (6.18 ms … 15.14 ms)  14.77 ms ██▄                  
                    (  3.02 mb …   3.96 mb)   3.52 mb ███▆▅▄▅▂▁▆▃▃▁▁▂▂▁▁▂▁▂

                             ┌                                            ┐
                              ╷┌────┬─┐                          ╷
         js medium (control)  ├┤    │ ├──────────────────────────┤
                              ╵└────┴─┘                          ╵
                             ╷┌──────┬───┐                                ╷
      js medium (experiment) ├┤      │   ├────────────────────────────────┤
                             ╵└──────┴───┘                                ╵
                             └                                            ┘
                             6.18 ms           10.47 ms            14.77 ms

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

------------------------------------------- -------------------------------
js large (control)             3.25 ms/iter   3.23 ms  █                   
                       (2.27 ms … 11.53 ms)   8.40 ms  █▂                  
                    (451.30 kb …   3.60 mb)   1.46 mb ▅██▄▂▃▂▂▁▁▂▂▂▁▁▂▁▁▁▁▁

js large (experiment)          2.93 ms/iter   2.77 ms  █                   
                        (2.38 ms … 7.26 ms)   6.33 ms  █                   
                    (279.38 kb …   3.08 mb)   1.43 mb ██▇▂▂▃▂▂▂▂▂▁▁▁▁▂▁▁▁▁▁

                             ┌                                            ┐
                             ╷ ┌────┬                                     ╷
          js large (control) ├─┤    │─────────────────────────────────────┤
                             ╵ └────┴                                     ╵
                              ╷┌──┬                        ╷
       js large (experiment)  ├┤  │────────────────────────┤
                              ╵└──┴                        ╵
                             └                                            ┘
                             2.27 ms            5.34 ms             8.40 ms

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

------------------------------------------- -------------------------------
gjs small (control)            1.26 ms/iter   1.16 ms █                    
                        (1.10 ms … 6.21 ms)   5.72 ms █                    
                    (539.47 kb …   1.62 mb)   1.06 mb █▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gjs small (experiment)         1.25 ms/iter   1.15 ms █                    
                        (1.10 ms … 6.68 ms)   5.50 ms █                    
                    (290.58 kb …   1.60 mb)   1.06 mb █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌─┬                                          ╷
         gjs small (control) │ │──────────────────────────────────────────┤
                             └─┴                                          ╵
                             ┌┬                                         ╷
      gjs small (experiment) ││─────────────────────────────────────────┤
                             └┴                                         ╵
                             └                                            ┘
                             1.10 ms            3.41 ms             5.72 ms

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

------------------------------------------- -------------------------------
gjs medium (control)         618.00 µs/iter 572.90 µs █                    
                      (546.35 µs … 6.31 ms)   1.45 ms █▇                   
                    ( 79.88 kb …   1.56 mb) 541.50 kb ██▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gjs medium (experiment)      614.99 µs/iter 572.48 µs █                    
                      (542.34 µs … 6.07 ms)   1.66 ms █▂                   
                    ( 78.63 kb …   1.70 mb) 541.65 kb ██▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ╷┌─┬                                 ╷
        gjs medium (control) ├┤ │─────────────────────────────────┤
                             ╵└─┴                                 ╵
                             ╷┌─┬                                         ╷
     gjs medium (experiment) ├┤ │─────────────────────────────────────────┤
                             ╵└─┴                                         ╵
                             └                                            ┘
                             542.34 µs           1.10 ms            1.66 ms

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

------------------------------------------- -------------------------------
gjs large (control)          250.73 µs/iter 231.59 µs  █                   
                      (216.25 µs … 5.49 ms) 324.96 µs  █▂                  
                    (215.75 kb … 901.45 kb) 217.53 kb ▃██▇▅▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gjs large (experiment)       249.79 µs/iter 233.12 µs  █▂                  
                      (216.63 µs … 6.52 ms) 309.95 µs  ██                  
                    (159.47 kb … 734.79 kb) 216.59 kb ▃████▅▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ╷ ┌───────────┬                              ╷
         gjs large (control) ├─┤           │──────────────────────────────┤
                             ╵ └───────────┴                              ╵
                             ╷ ┌───────────┬                        ╷
      gjs large (experiment) ├─┤           │────────────────────────┤
                             ╵ └───────────┴                        ╵
                             └                                            ┘
                             216.25 µs         270.61 µs          324.96 µs

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

------------------------------------------- -------------------------------
gts small (control)            1.26 ms/iter   1.15 ms █                    
                        (1.10 ms … 7.79 ms)   6.64 ms █                    
                    (227.66 kb …   1.50 mb)   1.06 mb █▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gts small (experiment)         1.26 ms/iter   1.17 ms █                    
                        (1.11 ms … 7.55 ms)   6.07 ms █                    
                    (292.09 kb …   1.79 mb)   1.05 mb █▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌┬                                           ╷
         gts small (control) ││───────────────────────────────────────────┤
                             └┴                                           ╵
                             ┌┬                                      ╷
      gts small (experiment) ││──────────────────────────────────────┤
                             └┴                                      ╵
                             └                                            ┘
                             1.10 ms            3.87 ms             6.64 ms

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

------------------------------------------- -------------------------------
gts medium (control)         625.69 µs/iter 580.11 µs █▂                   
                      (544.38 µs … 7.18 ms)   1.67 ms ██                   
                    ( 50.95 kb …   1.01 mb) 541.28 kb ██▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gts medium (experiment)      626.82 µs/iter 581.97 µs █                    
                      (541.81 µs … 6.40 ms)   2.10 ms █                    
                    (182.34 kb …   1.68 mb) 541.18 kb ██▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ╷┌┬                              ╷
        gts medium (control) ├┤│──────────────────────────────┤
                             ╵└┴                              ╵
                             ╷┌┬                                          ╷
     gts medium (experiment) ├┤│──────────────────────────────────────────┤
                             ╵└┴                                          ╵
                             └                                            ┘
                             541.81 µs           1.32 ms            2.10 ms

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

------------------------------------------- -------------------------------
gts large (control)          249.23 µs/iter 232.65 µs  █                   
                      (215.86 µs … 6.32 ms) 335.30 µs  █▅                  
                    (180.21 kb … 661.17 kb) 217.07 kb ▄███▅▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gts large (experiment)       244.43 µs/iter 231.53 µs  █                   
                      (215.89 µs … 5.71 ms) 307.91 µs  █▅                  
                    ( 64.53 kb … 989.36 kb) 216.50 kb ▃██▅▇▄▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ╷ ┌──────────┬                               ╷
         gts large (control) ├─┤          │───────────────────────────────┤
                             ╵ └──────────┴                               ╵
                             ╷ ┌────────┬                       ╷
      gts large (experiment) ├─┤        │───────────────────────┤
                             ╵ └────────┴                       ╵
                             └                                            ┘
                             215.86 µs         275.58 µs          335.30 µs

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

…peer cases

Translates 33 cases from peer-plugin rules:
  - jsx-a11y role-supports-aria-props
  - lit-a11y role-supports-aria-attr
  - vuejs-accessibility (no direct equivalent; divergence noted)

The fixture documents where our rule now matches jsx-a11y (notably
<input type="email"> without `list` mapping to "textbox" per the
aria-query attribute constraints) plus the sibling `list=…` case
mapping to "combobox".
@johanrd
Copy link
Copy Markdown
Owner Author

johanrd commented Apr 21, 2026

Moved upstream to ember-cli#2726. 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