Skip to content

BUGFIX: template-no-invalid-role — support DPUB/Graphics-ARIA and role-fallback lists#55

Closed
johanrd wants to merge 24 commits intomasterfrom
fix/invalid-role-aria-query
Closed

BUGFIX: template-no-invalid-role — support DPUB/Graphics-ARIA and role-fallback lists#55
johanrd wants to merge 24 commits intomasterfrom
fix/invalid-role-aria-query

Conversation

@johanrd
Copy link
Copy Markdown
Owner

@johanrd johanrd commented Apr 22, 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.

[Mirror of ember-cli#2729 for Copilot review]

Two related fixes in one rewrite. Both were false positives that rejected documented-valid ARIA patterns.

1. DPUB-ARIA and Graphics-ARIA roles

  • Premise: DPUB-ARIA and Graphics-ARIA are W3C Recommendations that extend WAI-ARIA with doc-* (40+ roles) and graphics-* (3 roles) tokens. aria-query indexes them under its roles map.
  • Problem: Our hand-maintained VALID_ROLES (~90 tokens) covered only WAI-ARIA base roles. role="doc-abstract", role="graphics-document", etc., were flagged as invalid.

Fix: derive VALID_ROLES from aria-query's concrete (non-abstract) role keys. Covers ~127 roles automatically.

A small ARIA 1.3 draft allowlist (associationlist, associationlistitemkey, associationlistitemvalue, comment, suggestion) is kept inline because aria-query doesn't yet ship those.

2. Whitespace-separated role fallback lists

  • Premise: ARIA 1.2 §4.1 WAI-ARIA Rolesrole is a list of tokens. The UA picks the first one it recognises; the others are fallbacks.
  • Problem: Our rule treated role="tabpanel row" as one opaque value — VALID_ROLES.has("tabpanel row") is false — and flagged the whole string as invalid.

Fix: split on whitespace, validate each token. The error message now names the first offending token (Invalid ARIA role 'foobar') rather than the whole string.

Before/after

HTML Before After
<div role="doc-abstract"> invalid valid
<svg role="graphics-document"> invalid valid
<div role="tabpanel row"> invalid (whole string) valid
<section role="doc-appendix doc-bibliography"> invalid (whole string) valid
<div role="tabpanel row foobar"> invalid (whole string as role name) invalid (specifically: 'foobar')

Three existing invalid tests updated to reflect the per-token error message; ten new valid tests cover the fixes.

Prior art

Plugin Rule Notes
jsx-a11y aria-role Uses aria-query's roles.keys() directly; .split(' ') the value and checks each token against the set of concrete roles.
vuejs-accessibility aria-role Same pattern as jsx-a11y.
lit-a11y aria-role No whitespace split; passes the full role string to isConcreteAriaRole, so multi-token fallback lists like role="tabpanel row" fail.

@angular-eslint/template's valid-aria rule validates aria-* attribute names and values rather than role tokens, so it has no direct analog for the fallback-list fix.

Audit fixture

Includes translated peer-plugin test fixture at tests/audit/aria-role/peer-parity.js documenting our rule's behavior relative to jsx-a11y / vuejs-accessibility / lit-a11y. Runs as part of the default Vitest suite (included via the tests/**/*.js glob) and serves double-duty as an auditable peer-parity record and as regression coverage pinning current behavior.

johanrd added 8 commits April 21, 2026 07:47
…pport DPUB-/Graphics-ARIA and role-fallback lists

Two related fixes, shared rewrite.

1. Replace the hand-maintained VALID_ROLES (~90 WAI-ARIA 1.2 tokens)
   with a derived list from aria-query (concrete — non-abstract — role
   keys), plus a small ARIA 1.3 draft-role allowlist that aria-query
   doesn't yet ship.

   Effect: DPUB-ARIA roles (doc-abstract, doc-chapter, …) and
   Graphics-ARIA roles (graphics-document, graphics-object,
   graphics-symbol) are no longer flagged as invalid.

2. Split the role value on whitespace before validating. A role
   attribute is a list of tokens per ARIA 1.2 §5.4 (role fallback).
   Each token must individually be valid.

   Effect: role="tabpanel row", role="doc-appendix doc-bibliography",
   and role="graphics-document document" now pass; role="tabpanel
   row foobar" flags the first invalid token ("foobar") instead of
   rejecting the whole string as one opaque role name.

Error message now names the specific offending token. Three existing
invalid tests updated accordingly (previously expected the whole
string; now the specific token).

Ten new valid tests cover DPUB/Graphics and the fallback-list shape.
Closes two gaps identified in PR #28 Phase 3 audit (B10 fixture on
`audit/phase3/no-autofocus`):

G3 — value-aware detection. The rule previously flagged `autofocus` purely
on presence, producing false positives for explicit opt-out forms:
  - `autofocus="false"` (GlimmerTextNode chars === "false")
  - `autofocus={{false}}` (BooleanLiteral false)
  - `autofocus={{"false"}}` (StringLiteral "false")
These are now treated as valid. jsx-a11y's `no-autofocus` reads the value
via `getPropValue` and exits early on falsy results, which is the behavior
encoded here. Truthy literals (`="true"`, `="autofocus"`, `={{true}}`,
`={{"true"}}`) and any dynamic expression (`={{this.x}}`) still flag —
the rule cannot prove a dynamic value is safe. Mustache-hash-pair forms
(`{{input autofocus=false}}`, `{{input autofocus="false"}}`) receive the
same value-aware treatment so `autofocus=true` and `autofocus=false` do
not behave inconsistently between syntaxes.

G4 — `<dialog>` exception. The rule now skips reporting when the element
carrying `autofocus` is a `<dialog>` itself OR is nested at any depth
inside a `<dialog>`. Per MDN's `<dialog>` documentation, a dialog is
expected to move focus to its initial control on open, so
`autofocus` on or within a dialog is the recommended pattern rather
than an accessibility defect. This matches
`@angular-eslint/template/no-autofocus`, which exempts the same subtree.

The descendant walk is a full parent-chain traversal via `node.parent`;
the Glimmer AST in this codebase exposes `parent` on every element node,
so no scope narrowing was required.

Behavior unchanged for: bare `<input autofocus>`, truthy string/mustache
values, dynamic mustache values, and any `autofocus` outside a dialog
subtree. 22 rule tests (10 valid, 12 invalid) pass; full suite (9089
tests) green.

Audit fixture `tests/audit/no-autofocus/peer-parity.js` lives on branch
`audit/phase3/no-autofocus` and will need separate updating to reflect
the new parity status for G3 and G4.

Refs: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog
Refs: https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/src/rules/no-autofocus.js
Refs: #28 (G3, G4)
Translates 32 cases from peer-plugin rules:
  - jsx-a11y aria-role
  - vuejs-accessibility aria-role
  - lit-a11y aria-role

Fixture documents parity after this fix:
  - DPUB-ARIA and Graphics-ARIA roles accepted (via aria-query).
  - Space-separated role tokens accepted when all are valid, and the
    invalid-token variant names the specific offending token.

Remaining divergences (case-insensitive comparison, empty-string role
not flagged) are annotated inline.
…kens

`associationlist`, `associationlistitemkey`, and `associationlistitemvalue`
are not present in the current WAI-ARIA 1.3 editor's draft
(https://w3c.github.io/aria/). Earlier commit listed all five as draft
tokens; only `comment` and `suggestion` are actually proposed. Drop the
three phantom tokens and the tests that accepted them as valid.

With this change the rule now correctly flags `role='associationlist'`
and siblings as invalid, matching peer behavior (jsx-a11y, vue-a11y,
lit-a11y all reject them).
…ML boolean semantics

Per HTML Living Standard on boolean attributes, the presence of `autofocus`
indicates TRUE regardless of value — `autofocus="false"` and
`autofocus="autofocus"` are equally truthy. jsx-a11y's `no-autofocus`
treats the literal string `"false"` as an opt-out (via `getPropValue`),
but that's a peer-plugin convention that diverges from HTML semantics;
vue-a11y and lit-a11y are presence-based, consistent with the spec.

Narrow opt-out to the only case that is spec-consistent:
- `autofocus={{false}}` in angle-bracket syntax — renders no attribute.
- `{{input autofocus=false}}` in mustache hash-pair syntax — no attribute.

Revert peer-parity opt-outs for `autofocus="false"`, `autofocus={{"false"}}`,
and `{{input autofocus="false"}}` — these are now flagged per HTML spec
semantics. Moved from valid → invalid in the test suite.

Dialog exemption unchanged — keeps MDN-backed behavior for autofocus on
and within <dialog>.

Follows the spec-first direction established in ember-cli#2717 (aria-hidden),
#19, #33.
…ibute

The G3 exemption's load-bearing premise (Glimmer renders no attribute
when given a mustache-literal false) was asserted in the PR body but
unverified in the rule's own documentation. Verified against Glimmer
VM's attribute-normalization path:

  glimmer-vm/packages/@glimmer/runtime/lib/vm/attributes/dynamic.ts

`normalizeValue` returns null for false/undefined/null; then
`SimpleDynamicAttribute.update()` calls element.removeAttribute(name)
when the value is null. So autofocus={{false}} genuinely omits the
attribute from the rendered DOM — not autofocus="false".

Inlined the citation into the rule's JSDoc so future readers don't
have to trace it.
@johanrd johanrd requested a review from Copilot April 22, 2026 10:41
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 22, 2026

🏎️ Benchmark Comparison

Benchmark Control (p50) Experiment (p50) Δ
🔴 js small 14.41 ms 21.17 ms +47.0%
🟢 js medium 7.14 ms 6.96 ms -2.6%
🟢 js large 2.78 ms 2.59 ms -6.6%
gjs small 1.24 ms 1.24 ms +0.0%
gjs medium 622.37 µs 617.40 µs -0.8%
gjs large 246.63 µs 247.16 µs +0.2%
gts small 1.23 ms 1.25 ms +1.0%
gts medium 616.49 µs 620.75 µs +0.7%
gts large 246.74 µs 249.00 µs +0.9%

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

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

benchmark                   avg (min … max) p75 / p99    (min … top 1%)
------------------------------------------- -------------------------------
js small (control)            16.78 ms/iter  17.61 ms █▅                   
                      (12.42 ms … 30.72 ms)  28.56 ms ██▅ ▂ ▂              
                    (  4.85 mb …  10.48 mb)   7.22 mb ███▇█▄█▁▄▁▁▄▁▁▇▄▁▁▇▁▄

js small (experiment)         20.33 ms/iter  22.66 ms █          █▂▂ ▂     
                      (13.98 ms … 28.05 ms)  27.01 ms █▅▅       ▅███ █ ▅   
                    (  6.13 mb …   8.02 mb)   6.88 mb ███▇▇▁▁▁▁▁████▁█▁█▇▁▇

                             ┌                                            ┐
                             ╷┌──────────┬─┐                              ╷
          js small (control) ├┤          │ ├──────────────────────────────┤
                             ╵└──────────┴─┘                              ╵
                                 ╷    ┌────────────┬──────┐           ╷
       js small (experiment)     ├────┤            │      ├───────────┤
                                 ╵    └────────────┴──────┘           ╵
                             └                                            ┘
                             12.42 ms           20.49 ms           28.56 ms

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

------------------------------------------- -------------------------------
js medium (control)            8.19 ms/iter   8.87 ms ▄█                   
                       (6.59 ms … 15.22 ms)  14.04 ms ██                   
                    (  3.02 mb …   4.23 mb)   3.54 mb ██▅▅▆▅▃▄▂▃▂▆▁▂▁▂▂▂▁▂▂

js medium (experiment)         7.49 ms/iter   7.75 ms ██                   
                       (6.50 ms … 12.32 ms)  11.97 ms ███                  
                    (  2.39 mb …   4.60 mb)   3.54 mb ████▃█▆▄▁▂▂▂▂▁▁▃▁▁▂▁▃

                             ┌                                            ┐
                              ╷┌───────┬───┐                              ╷
         js medium (control)  ├┤       │   ├──────────────────────────────┤
                              ╵└───────┴───┘                              ╵
                             ╷┌────┬─┐                        ╷
      js medium (experiment) ├┤    │ ├────────────────────────┤
                             ╵└────┴─┘                        ╵
                             └                                            ┘
                             6.50 ms           10.27 ms            14.04 ms

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

------------------------------------------- -------------------------------
js large (control)             3.03 ms/iter   2.91 ms  █                   
                        (2.62 ms … 6.55 ms)   5.99 ms  █                   
                    (763.26 kb …   2.50 mb)   1.44 mb ██▅▂▃▂▁▁▂▂▁▁▁▁▁▁▁▁▁▁▁

js large (experiment)          3.07 ms/iter   2.79 ms █                    
                       (2.43 ms … 11.00 ms)   9.04 ms █▇                   
                    (184.25 kb …   3.41 mb)   1.43 mb ██▃▂▂▂▂▁▁▂▂▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                              ╷┌─┬                   ╷
          js large (control)  ├┤ │───────────────────┤
                              ╵└─┴                   ╵
                             ╷┌──┬                                        ╷
       js large (experiment) ├┤  │────────────────────────────────────────┤
                             ╵└──┴                                        ╵
                             └                                            ┘
                             2.43 ms            5.73 ms             9.04 ms

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

------------------------------------------- -------------------------------
gjs small (control)            1.38 ms/iter   1.34 ms █                    
                        (1.21 ms … 6.03 ms)   5.62 ms █                    
                    (584.56 kb …   2.00 mb)   1.06 mb ██▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gjs small (experiment)         1.35 ms/iter   1.27 ms █                    
                        (1.20 ms … 5.66 ms)   4.98 ms █                    
                    (322.38 kb …   1.79 mb)   1.06 mb █▅▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

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

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

------------------------------------------- -------------------------------
gjs medium (control)         669.88 µs/iter 636.11 µs  █                   
                      (590.77 µs … 5.92 ms)   1.34 ms ▂█                   
                    (189.54 kb …   1.12 mb) 541.52 kb ██▄▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gjs medium (experiment)      668.28 µs/iter 634.12 µs █▇                   
                      (586.75 µs … 5.45 ms)   1.78 ms ██                   
                    ( 74.86 kb …   1.25 mb) 541.01 kb ██▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ╷┌─┬                        ╷
        gjs medium (control) ├┤ │────────────────────────┤
                             ╵└─┴                        ╵
                             ╷┌─┬                                         ╷
     gjs medium (experiment) ├┤ │─────────────────────────────────────────┤
                             ╵└─┴                                         ╵
                             └                                            ┘
                             586.75 µs           1.18 ms            1.78 ms

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

------------------------------------------- -------------------------------
gjs large (control)          274.12 µs/iter 262.16 µs  █                   
                      (235.22 µs … 5.71 ms) 391.54 µs ▄█▄                  
                    (216.10 kb … 901.45 kb) 217.68 kb ███▆█▅▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁

gjs large (experiment)       270.70 µs/iter 262.37 µs  █▅                  
                      (235.23 µs … 5.09 ms) 342.15 µs  ██▂                 
                    (215.70 kb … 776.52 kb) 216.49 kb ▆███▄█▇▇▃▁▁▂▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ╷┌─────────┬                                 ╷
         gjs large (control) ├┤         │─────────────────────────────────┤
                             ╵└─────────┴                                 ╵
                             ╷ ┌───────┬                    ╷
      gjs large (experiment) ├─┤       │────────────────────┤
                             ╵ └───────┴                    ╵
                             └                                            ┘
                             235.22 µs         313.38 µs          391.54 µs

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

------------------------------------------- -------------------------------
gts small (control)            1.32 ms/iter   1.26 ms █                    
                        (1.19 ms … 6.62 ms)   4.57 ms █                    
                    (293.84 kb …   1.84 mb)   1.06 mb █▄▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gts small (experiment)         1.34 ms/iter   1.26 ms █                    
                        (1.21 ms … 6.06 ms)   4.82 ms █                    
                    (180.06 kb …   1.88 mb)   1.05 mb █▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌─┬                                       ╷
         gts small (control) │ │───────────────────────────────────────┤
                             └─┴                                       ╵
                             ┌─┬                                          ╷
      gts small (experiment) │ │──────────────────────────────────────────┤
                             └─┴                                          ╵
                             └                                            ┘
                             1.19 ms            3.01 ms             4.82 ms

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

------------------------------------------- -------------------------------
gts medium (control)         664.70 µs/iter 633.63 µs  █                   
                      (586.82 µs … 5.79 ms)   1.14 ms ▃█▅                  
                    (298.78 kb …   1.11 mb) 541.47 kb ███▃▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gts medium (experiment)      663.49 µs/iter 635.93 µs  █                   
                      (588.99 µs … 5.37 ms)   1.63 ms ▄█                   
                    (308.50 kb …   1.13 mb) 540.93 kb ██▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ╷┌─┬                    ╷
        gts medium (control) ├┤ │────────────────────┤
                             ╵└─┴                    ╵
                             ╷┌─┬                                         ╷
     gts medium (experiment) ├┤ │─────────────────────────────────────────┤
                             ╵└─┴                                         ╵
                             └                                            ┘
                             586.82 µs           1.11 ms            1.63 ms

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

------------------------------------------- -------------------------------
gts large (control)          269.62 µs/iter 261.53 µs  █▂                  
                      (234.49 µs … 5.28 ms) 346.91 µs  ██▂ ▃               
                    (216.09 kb … 934.18 kb) 217.26 kb ████▄█▅▆▂▂▂▁▁▁▁▁▁▁▁▁▁

gts large (experiment)       269.53 µs/iter 263.60 µs  █                   
                      (237.32 µs … 5.01 ms) 338.73 µs  ██▂                 
                    ( 16.87 kb … 902.16 kb) 216.60 kb ▆███▄█▆▆▃▂▂▂▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ╷ ┌───────────┬                              ╷
         gts large (control) ├─┤           │──────────────────────────────┤
                             ╵ └───────────┴                              ╵
                              ╷ ┌──────────┬                           ╷
      gts large (experiment)  ├─┤          │───────────────────────────┤
                              ╵ └──────────┴                           ╵
                             └                                            ┘
                             234.49 µs         290.70 µs          346.91 µs

summary
  gts large (experiment)
   1x 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

Note

Copilot was unable to run its full agentic suite in this review.

Fixes false positives in template-no-invalid-role by aligning role validation with aria-query and ARIA’s whitespace-separated fallback-list semantics.

Changes:

  • Derive VALID_ROLES from aria-query concrete (non-abstract) roles, covering DPUB-ARIA doc-* and Graphics-ARIA graphics-*.
  • Validate whitespace-separated role fallback lists token-by-token and improve invalid-role reporting to name the offending token.
  • Add parity/audit fixture tests (not in CI) and update/expand rule tests to cover new behavior.

Reviewed changes

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

File Description
lib/rules/template-no-invalid-role.js Replaces the hand-maintained allowlist with aria-query roles and adds tokenized role validation.
tests/lib/rules/template-no-invalid-role.js Updates expectations for per-token errors and adds new valid cases for doc-*, graphics-*, and fallback lists.
tests/audit/aria-role/peer-parity.js Adds an audit fixture to compare behavior against peer ESLint accessibility plugins.

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

Comment thread lib/rules/template-no-invalid-role.js Outdated
Comment thread lib/rules/template-no-invalid-role.js Outdated
johanrd added 2 commits April 22, 2026 13:56
…Copilot review)

The PR body claimed the ARIA 1.3 draft allowlist covers 5 roles (associationlist,
associationlistitemkey, associationlistitemvalue, comment, suggestion). The
code only listed 2 ('comment', 'suggestion'); the gap was invisible because
no tests exercised any of them. Verified against aria-query 5.3.2:
roles.has() returns false for all 5, so all 5 belong in the inline allowlist
until aria-query catches up.

Also: when reporting presentation/none on a semantic element, include the
offending token in the message data instead of the raw role attribute
string — avoids surfacing e.g. 'presentation listbox' when only
'presentation' is the issue.

Tests: add 5 valid cases in each of the gts and hbs blocks covering all
ARIA 1.3 draft roles.
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 3 out of 3 changed files in this pull request and generated 2 comments.


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

Comment thread lib/rules/template-no-invalid-role.js Outdated
Comment thread tests/audit/aria-role/peer-parity.js Outdated
@johanrd johanrd force-pushed the fix/invalid-role-aria-query branch from 49fafaa to 7e45da3 Compare April 22, 2026 17:09
johanrd added 2 commits April 23, 2026 21:39
…the first recognised role (Copilot review)

Bundle with the Q7/Q10/Q18/Q30 cross-rule first-valid-token pattern. Per
WAI-ARIA §4.1, UAs walk the role-token list for the first role they
recognise; subsequent tokens are author-provided fallbacks that never
take effect. So `role="button presentation"` on a semantic element
resolves to `button` at runtime and must NOT flag. Previously we
flagged on any occurrence of presentation/none anywhere in the list.

Unknown tokens are skipped per the same section, so
`role="xxyxyz presentation"` correctly resolves to presentation and
still flags (covered by a new regression test).
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 3 out of 3 changed files in this pull request and generated 2 comments.


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

Comment thread tests/audit/aria-role/peer-parity.js Outdated
Comment thread tests/audit/aria-role/peer-parity.js Outdated
johanrd added 5 commits April 24, 2026 10:57
…-input helpers (Copilot review)

Previously the GlimmerMustacheStatement visitor fired on ANY mustache
with an autofocus hash pair — but arbitrary custom components taking
'autofocus' as a prop are opaque. We can't statically tell whether the
prop forwards to a native <input autofocus> or is used for something
else. Narrow to the two built-ins that deterministically render a
native input:
  - {{input …}}
  - {{component "input" …}}

Future: when type-aware linting lands (Glint integration or template-
type-check), we can resolve custom components that forward 'autofocus'
to a native <input> and flag those too. For now we stay conservative
to avoid false positives on unrelated helpers.
@johanrd johanrd requested a review from Copilot April 24, 2026 13:38
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 3 out of 3 changed files in this pull request and generated 2 comments.


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

Comment thread lib/rules/template-no-invalid-role.js Outdated
Comment thread tests/audit/aria-role/peer-parity.js Outdated
… message (Copilot review)

Keep the original token alongside the lowercased one; validation stays case-
insensitive (lowercases against VALID_ROLES) but the reported-back token uses
the author's raw casing so the error surfaces their input verbatim rather
than the normalized form.

Also drop a 'line 229 of rule' hard-coded reference from the aria-role
peer-parity fixture — line numbers rot on every refactor.
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 requested a review from Copilot April 24, 2026 18:58
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 and others added 5 commits April 25, 2026 06:44
…t fixture

Upstream maintainers don't want the per-PR `tests/audit/peer-parity`
pattern. Port two cases that pinned distinct behavior:
- `role=""` as VALID — documented divergence; we early-return on
  empty/whitespace role values where jsx-a11y / vue-a11y flag.
- `role="datepicker"` as INVALID — common authoring confusion that
  exercises the unknown-role path with a more likely real-world typo
  than `role="invalid"`.

Other audit cases were already covered by the regular tests.
…ibute

Previously the rule early-returned when the trimmed role value was empty,
silently accepting `<div role="">` and `<div role="   ">`. Both jsx-a11y
and vue-a11y flag these — they're authoring mistakes, not no-ops, since
the attribute is malformed (no recognized role token) and supplies zero
ARIA semantics while still appearing in the DOM.

Replace the early-return with a single 'invalid' report (role rendered
as the empty string in the message), keeping the existing error format
intact. No new messageId; the existing 'invalid' template renders the
empty value clearly.

Tests: move `role=""` from valid to invalid; add `role="   "` for
parity with the trim-then-flag path. Documented as ports of the audit-
fixture divergence cases that previously pinned the under-flag behavior.
…are-and-dialog

fix: template-no-autofocus-attribute — value-aware + <dialog> exception
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
@johanrd johanrd closed this Apr 25, 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.

3 participants