Skip to content

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

Draft
johanrd wants to merge 7 commits intoember-cli:masterfrom
johanrd:fix/implicit-role-attribute-constraints
Draft

BUGFIX: template-no-unsupported-role-attributes — honor aria-query attribute constraints#2726
johanrd wants to merge 7 commits intoember-cli:masterfrom
johanrd:fix/implicit-role-attribute-constraints

Conversation

@johanrd
Copy link
Copy Markdown
Contributor

@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">(nolist`)
<input type="password"> button none (unmapped by aria-query, matches HTML-AAM)

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"> being left unmapped is consistent with HTML-AAM, which assigns it "No corresponding role."

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 Verified behavior
jsx-a11y role-supports-aria-props Computes implicit role via per-element lookup tables under src/util/implicitRoles/ (e.g. a.js checks for href before returning "link"); then validates that every aria-* attribute is in the role's supported-props list from aria-query.
vuejs-accessibility no equivalent rule vue-a11y's aria-props rule only validates that attribute names beginning with aria- are known ARIA attributes (via aria.has(lowered)); it does not compute implicit roles or check role↔prop compatibility.
@angular-eslint/template no equivalent rule angular-eslint's valid-aria validates ARIA attribute names and literal values against aria-query's definitions; it does not resolve implicit roles from element + attributes, so the role-supports-props check does not apply. role-has-required-aria is the inverse check (role → required props), not role → supported props.
lit-a11y role-supports-aria-attr Only reacts to an explicit role= attribute (Object.keys(element.attribs).includes('role')); does not compute an implicit role from tag + attributes, so the implicit-role false positive does not arise.

Audit fixture

Translated peer-plugin test fixture at tests/audit/role-supports-aria-props/peer-parity.js documenting our rule's behavior relative to jsx-a11y and lit-a11y. Not wired into the default test run.

johanrd added 3 commits April 21, 2026 07:36
…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.
…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 added 2 commits April 21, 2026 17:50
…euristic

The "pick entry with most attribute constraints satisfied" resolution is a
heuristic, not a spec-documented approach. aria-query's elementRoles is an
unordered Map; aria-query does not publish a resolution order for
multi-match cases. Name the inference, cite the motivating bug cases, and
note that if aria-query ever publishes resolution semantics we should
switch to those.
johanrd added 2 commits April 22, 2026 13:56
- getStaticAttrValue() now trims chars so callers don't handle whitespace.
- Lowercase the node value when comparing against aria-query's attrSpec.value
  (HTML enumerated attribute values are ASCII case-insensitive per HTML
  common-microsyntaxes §2.3.3).
- Remove unused createUnsupportedAttributeErrorMessage() dead code; the rule
  uses messageId/data exclusively.
@johanrd johanrd force-pushed the fix/implicit-role-attribute-constraints branch from 5ad45c5 to 3de05b9 Compare April 22, 2026 17:11
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