BUGFIX: template-no-redundant-role — case-insensitive match + <select>→combobox#2727
Closed
johanrd wants to merge 11 commits intoember-cli:masterfrom
Closed
BUGFIX: template-no-redundant-role — case-insensitive match + <select>→combobox#2727johanrd wants to merge 11 commits intoember-cli:masterfrom
johanrd wants to merge 11 commits intoember-cli:masterfrom
Conversation
…x mapping Two changes. 1. Compare role tokens case-insensitively. ARIA role values are ASCII-case-insensitive (HTML-AAM inherits HTML's attribute-comparison semantics). Before: <body role="DOCUMENT"> was not flagged because "DOCUMENT" didn't match "document" in ROLE_TO_ELEMENTS. 2. Add 'combobox' → ['select'] mapping. <select> without `multiple` or `size > 1` has an implicit role of "combobox" per HTML-AAM §4.1. Before: <select role="combobox"> was silently accepted as a redundant role. Two new invalid tests cover the fixes.
Per HTML-AAM, <select>'s implicit role is "combobox" only when neither `multiple` nor `size > 1` is present; otherwise it is "listbox". The previous commit added `combobox: ['select']` unconditionally, which caused false positives for <select role="combobox" multiple> and <select role="combobox" size="5"> (where combobox disagrees with the implicit listbox role and therefore is not redundant). Add a selectHasComboboxImplicitRole helper mirroring jsx-a11y's src/util/implicitRoles/select.js, and short-circuit the redundancy check for <select role="combobox"> when the implicit role is actually listbox. Update the code comment on the `combobox: ['select']` entry to reflect this (and drop the inaccurate "§4.1" reference — the <select> mapping lives in the HTML-AAM main conformance table, section 4, without a numbered subsection). Tests: - valid: <select role="combobox" multiple></select> - valid: <select role="combobox" size="5"></select> - invalid: <select role="combobox" size="1"></select> (size=1 → combobox) - invalid: <select role="COMBOBOX"></select> (case + implicit)
…ases
Translates 35 cases from peer-plugin rules:
- jsx-a11y no-redundant-roles
- vuejs-accessibility no-redundant-roles
- lit-a11y no-redundant-role
Fixture documents parity after this fix:
- Case-insensitive role comparison (body role="DOCUMENT" now flagged).
- Default <select role="combobox"> flagged; <select multiple>/size>1 still
valid (implicit listbox, not combobox).
Remaining divergences (ul/ol role="list", <a role="link"> without href)
are annotated inline. Not wired into the default vitest run.
This was referenced Apr 21, 2026
- Normalize role via trim() + first-token split before lookup so values
like 'COMBOBOX' or 'combobox listbox' are handled consistently
(ARIA role attribute is a space-separated fallback list; only the
first supported token is effective).
- getSelectImplicitRole() now returns 'combobox' | 'listbox' | 'unknown'.
A dynamic <select size={{...}}> previously fell through as 'combobox'
and produced false positives on role="combobox"; we now bail on
'unknown' instead of flagging.
df79aaf to
00c823d
Compare
…d role per ARIA §4.1 (Copilot review) Bundle with Q7/Q10/Q18/Q30 cross-rule pattern. Use aria-query's roles set as the "recognised" oracle (not the local ROLE_TO_ELEMENTS table, which would over-walk to roles it has mappings for). `role="xxyxyz button"` on <button> → first recognised is button → flag redundancy (autofix drops the whole role attr; implicit button role is preserved natively). `role="tab button"` on <button> → first recognised is tab → no implicit mapping for tab → nothing to flag.
…o select listbox)
…elect> implicit role (Copilot review)
Ember omits bound boolean attributes at runtime when the value is falsy, so
`<select multiple={{this.isMulti}}>` can resolve to either the combobox or
listbox implicit role. Previously treated any presence as listbox, which
could misclassify redundancy. Return 'unknown' for the dynamic case so the
caller skips flagging — matching the existing 'unknown' handling for dynamic
`size`.
…ox (Copilot review)
Per HTML boolean-attr semantics, a missing-value attribute's value is the
empty string — Number('') is 0; 0 is not > 1, so the implicit role of a
<select size> (no value) stays combobox. Previously we bailed out to
'unknown' for any sizeAttr.value that wasn't a GlimmerTextNode, conflating
valueless with dynamic. Split the two: valueless → combobox (static),
dynamic (mustache / concat) → unknown (skip).
Add an invalid test: <select role='combobox' size> flags as redundant
combobox.
…dit fixture Upstream maintainers don't want the per-PR `tests/audit/peer-parity` pattern. Port two basic non-landmark redundancy cases that were only in the audit fixture into the regular suite: - `<button role="button">` (autofix drops role) - `<img role="img">` (autofix drops role) Other audit cases were already covered by the regular tests.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two fixes in one PR (shared rule, no semantic overlap).
1. Case-insensitive role comparison
<body role="DOCUMENT">slipped through.Fix: lowercase the role value before the
ROLE_TO_ELEMENTS/LANDMARK_ROLESlookups.2.
<select>maps to combobox (conditional)<select>has an implicit role of combobox only when neithermultiplenorsize > 1is set; otherwise the implicit role is listbox. Both mappings are documented in the spec.ROLE_TO_ELEMENTSonly listedlistbox: ['select'].<select role="combobox">was silently accepted.Fix: add
combobox: ['select'], and gate the combobox redundancy check on themultiple/sizeattributes so<select role="combobox" multiple>and<select role="combobox" size="5">are not falsely flagged. The implicit-role check mirrors jsx-a11y'ssrc/util/implicitRoles/select.js.Tests cover both fixes, including the uppercase + conditional-combobox interaction.
Prior art
Verified each peer in source:
no-redundant-rolesgetExplicitRole(lowercases viarole.toLowerCase()atsrc/util/getExplicitRole.js:18–23);src/util/implicitRoles/select.js:9–17honorsmultipleandsize > 1.no-redundant-rolesimplicitRoleSet.includes(explicitRole)); no lowercasing in the rule or its utils.<body role="DOCUMENT">is not flagged there either.valid-aria,role-has-required-aria, and related ARIA-validity rules, but no redundant-role check. Role-vs-implicit-role redundancy is not audited.no-redundant-rolegetExplicitRole-style pattern as jsx-a11y (case-insensitive via its ownlib/utils/getExplicitRole.js:11–12).Audit fixture
Translated peer-plugin test fixture at
tests/audit/no-redundant-roles/peer-parity.js. Not wired into the default test run.