Skip to content

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

Draft
johanrd wants to merge 8 commits intoember-cli:masterfrom
johanrd:fix/role-required-aria-case-and-space-split
Draft

BUGFIX: template-require-mandatory-role-attributes — lowercase role + split whitespace role lists#2728
johanrd wants to merge 8 commits intoember-cli:masterfrom
johanrd:fix/role-required-aria-case-and-space-split

Conversation

@johanrd
Copy link
Copy Markdown
Contributor

@johanrd johanrd commented Apr 21, 2026

Depends on #2725 — merge #2725 first. Without it, the <input type="checkbox" role="switch"> exemption from #2725's axobject-query-driven isSemanticRoleElement helper is missing, and this PR's existing test fixtures (plus the audit-parity file) would flag those shapes as missing aria-checked. #2725 + #2728 together produce the peer-aligned behavior; alone, #2728 shifts some tests out of sync until #2725 lands.

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

1. Case-insensitive role comparison

  • Premise: ARIA role token comparison inherits case-insensitivity from HTML's enumerated-attribute rules — role="COMBOBOX" is the same token as role="combobox". (WAI-ARIA 1.2 §4.1 explicitly defers case-sensitivity to the host language: "Case-sensitivity of the comparison inherits from the case-sensitivity of the host language.")
  • 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: Per WAI-ARIA 1.2 §4.1, a role attribute may list multiple tokens and "User agents MUST use the first token in the sequence of tokens in the role attribute value that matches the name of any non-abstract WAI-ARIA role."
  • 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's first-token semantics. This diverges from jsx-a11y and vue-a11y, which validate every recognised token — our approach is closer to the UA behavior the spec prescribes. Noted as a deliberate divergence.

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

Verified each peer in source:

Plugin Rule Verified behavior
jsx-a11y role-has-required-aria-props String(roleAttrValue).toLowerCase().split(' ').filter(recognised).forEach(validate). Validates EVERY recognised token.
vuejs-accessibility role-has-required-aria-props roleValue.toLowerCase().split(" ").forEach(role => { if (isAriaRoleDefinitionKey(role)) validate(role) }). Same pattern.
@angular-eslint/template role-has-required-aria Raw role string passed directly to roles.get(role). No .toLowerCase(), no .split().
lit-a11y role-has-required-aria-attrs Raw role string passed to isAriaRole / roles.get. No splitting, no lowercasing.

johanrd added 5 commits April 21, 2026 07:44
…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).
…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 added a commit to johanrd/eslint-plugin-ember that referenced this pull request Apr 21, 2026
…d exemption to {{input}} helper

Two changes:

1. `getInputType` now recognises the classic Ember `{{input type=...}}`
   helper as an input host, in addition to the native `<input>` element.
   The `GlimmerMustacheStatement` handler is updated to pass `node` into
   `getMissingRequiredAttributes`, matching the element-node handler.

2. Reverts the defensive `role.toLowerCase()` in the whitelist-key build
   from the previous commit — it was dead code. The caller
   (`getMissingRequiredAttributes`) short-circuits via aria-query's
   case-sensitive `roles.get(role)`, so `role` is guaranteed lowercase
   by the time `isNativelyChecked` runs. Rule-wide role case-
   insensitivity is not in scope for this PR; that is ember-cli#2728.

Tests adjusted accordingly: drop mixed-case-role valid tests (they
passed for the wrong reason — via the case-sensitivity short-circuit in
aria-query, not via the whitelist). Add coverage for `{{input}}`
helper on both gts and hbs parsers, including off-whitelist pairings
that must still flag.
johanrd added 3 commits April 22, 2026 14:21
… review)

- Add HBS equivalents of the GJS case-insensitive and role-fallback tests
  so both parser paths exercise the same behaviours.
- Sort missingRequiredAttributes alphabetically so report order is stable
  regardless of aria-query's requiredProps iteration order.
…quiredAttributeErrorMessage

The helper was unused after the messageId migration (the rule now uses
aria-query-derived data + `messages.missingRequired` with a parameterized
template). No references remained in the rule or its test file.
@johanrd johanrd force-pushed the fix/role-required-aria-case-and-space-split branch from 88ab040 to 3d69b55 Compare April 22, 2026 17:10
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