feat(html/a11y): add noRedundantRoles rule for HTML#9276
Conversation
🦋 Changeset detectedLatest commit: 89399c0 The changes in this PR will be included in the next version bump. This PR includes changesets to release 14 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds a new HTML accessibility lint rule Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.changeset/some-trams-cut.md:
- Around line 1-3: The changeset currently marks the package update as a patch;
change the entry for "@biomejs/biome": patch to use a minor release instead
(i.e. "@biomejs/biome": minor) so the new HTML support is recorded as a
feature-level changeset; update the YAML header value accordingly in the
.changeset file.
In `@crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs`:
- Around line 89-90: Update the diagnostic message in no_redundant_roles.rs so
the user-facing sentence reads "implied by its semantics" instead of "implied by
its semantic"; locate the message string (the literal containing "Using the role
attribute '{role_attribute}' on the '{element_name}' element is redundant,
because it is implied by its semantic.") and replace "semantic" with "semantics"
in that string so the diagnostic copy is grammatically correct.
- Around line 69-71: The fixer currently reads role_attribute_value and calls
AriaRole::from_roles on the whole string but then removes the entire role
attribute if the first parsed role is redundant; instead split the
role_attribute_value by whitespace into tokens, map/parse each token via
AriaRole::from_roles (or the appropriate single-token parse), determine which
tokens are redundant for the element (reuse the existing redundancy check
logic), and then: if all tokens are redundant remove the role attribute,
otherwise reconstruct the role attribute value from only the non-redundant
tokens and update the attribute (preserving remaining fallback roles). Update
the logic around role_attribute.initializer()?value().value().string_value()?
and AriaRole::from_roles usage to operate on each token rather than assuming a
single role.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (4)
Cargo.lockis excluded by!**/*.lockand included by**crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/invalid.html.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/invalidHtmlAamRoleGeneric.html.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/valid.html.snapis excluded by!**/*.snapand included by**
📒 Files selected for processing (8)
.changeset/some-trams-cut.mdcrates/biome_html_analyze/Cargo.tomlcrates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rscrates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/invalid.htmlcrates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/invalidHtmlAamRoleGeneric.htmlcrates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/valid.htmlcrates/biome_html_syntax/Cargo.tomlcrates/biome_html_syntax/src/element_ext.rs
.changeset/some-trams-cut.md
Outdated
| --- | ||
| "@biomejs/biome": patch | ||
| --- |
There was a problem hiding this comment.
Use minor for this feature changeset.
Given this introduces new HTML support, patch looks under-scoped here.
Suggested fix
---
-"@biomejs/biome": patch
+"@biomejs/biome": minor
---As per coding guidelines: “Use change types in changesets: patch for bug fixes and non-breaking changes (targets main branch), minor for new features and non-breaking additions (targets next branch), major for breaking changes (targets next branch)”.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| --- | |
| "@biomejs/biome": patch | |
| --- | |
| --- | |
| "@biomejs/biome": minor | |
| --- |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.changeset/some-trams-cut.md around lines 1 - 3, The changeset currently
marks the package update as a patch; change the entry for "@biomejs/biome":
patch to use a minor release instead (i.e. "@biomejs/biome": minor) so the new
HTML support is recorded as a feature-level changeset; update the YAML header
value accordingly in the .changeset file.
| let role_attribute_value = role_attribute.initializer()?.value().ok()?.string_value()?; | ||
| let explicit_role = AriaRole::from_roles(role_attribute_value.trim())?; | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Locate AriaRole::from_roles implementation =="
rg -nP 'fn\s+from_roles\s*\(' --type rust -C3
echo
echo "== Inspect no_redundant_roles usage of parsed role + fixer =="
rg -nP 'from_roles|get_implicit_role|remove_node' crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs -C4
echo
echo "== Check fixture coverage for multi-token or case-variant role values =="
rg -nP 'role="[^"]+\s+[^"]+"|role="[A-Z][^"]*"' crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles -C2Repository: biomejs/biome
Length of output: 1834
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== List fixture files for noRedundantRoles =="
find crates/biome_html_analyze/tests/specs/a11y -name "*noRedundantRoles*" -type f | head -20
echo
echo "== Search fixture content broadly for role attributes =="
find crates/biome_html_analyze/tests/specs/a11y -name "*oRedundantRoles*" -type f -exec cat {} \; | grep -i 'role=' | head -20
echo
echo "== Check AriaRole documentation and parse method =="
rg -nP 'pub fn from_roles|pub fn parse|impl.*Parse' crates/biome_aria_metadata/src/lib.rs -C6Repository: biomejs/biome
Length of output: 160
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Find test files broadly =="
find crates/biome_html_analyze/tests -type f -name "*.html" -o -name "*.json" | grep -i "redundant\|role" | head -20
echo
echo "== Check the full no_redundant_roles.rs file structure =="
wc -l crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs
head -n 120 crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs
echo
echo "== Check AriaRole full implementation =="
wc -l crates/biome_aria_metadata/src/lib.rs
rg -nP 'pub fn from_roles|pub fn|impl.*FromStr' crates/biome_aria_metadata/src/lib.rs -A5 -B2 | head -80Repository: biomejs/biome
Length of output: 7446
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Check noRedundantRoles fixtures content =="
cat crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/invalid.html
echo ""
echo "---"
echo ""
cat crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/valid.html
echo ""
echo "== Search all fixture files for space-separated role values =="
rg 'role="[^"]*\s+[^"]*"' crates/biome_html_analyze/tests/specs/a11y/ -t htmlRepository: biomejs/biome
Length of output: 1718
Verify multi-token role handling before shipping.
The fixer removes the entire role attribute (line 96) but only checks the first parsed role (line 70). If a multi-token role like role="button presentation" appears on a <button>, the code detects button as redundant and discards the entire attribute—losing the presentation fallback. The ARIA spec allows space-separated fallback roles, yet test fixtures for noRedundantRoles don't cover this case (though useValidAriaRole fixtures show multi-token roles are used elsewhere in the codebase).
Consider handling token lists properly: either parse all tokens and verify none are redundant, or preserve non-redundant fallback roles.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs` around lines
69 - 71, The fixer currently reads role_attribute_value and calls
AriaRole::from_roles on the whole string but then removes the entire role
attribute if the first parsed role is redundant; instead split the
role_attribute_value by whitespace into tokens, map/parse each token via
AriaRole::from_roles (or the appropriate single-token parse), determine which
tokens are redundant for the element (reuse the existing redundancy check
logic), and then: if all tokens are redundant remove the role attribute,
otherwise reconstruct the role attribute value from only the non-redundant
tokens and update the attribute (preserving remaining fallback roles). Update
the logic around role_attribute.initializer()?value().value().string_value()?
and AriaRole::from_roles usage to operate on each token rather than assuming a
single role.
There was a problem hiding this comment.
I think what the bot is trying to tell is to handle multiple roles.
In fact, your tests focus solely on one role. What about
<button role="presentation button"></button>I think we should add more tests and maybe fix that if there's some mistakes
dyc3
left a comment
There was a problem hiding this comment.
- This needs to be based on
next - needs to have a
minorchangeset
Merging this PR will not alter performance
Comparing Footnotes
|
|
...and it needs to restore the original PR template |
ematipico
left a comment
There was a problem hiding this comment.
The lint rule needs more work and harness:
- it doesn't check for HTML elements VS components
- there aren't tests for Vue/svelte/astro
- components will hit this rule, which is a bug
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.changeset/html-no-redundant-roles.md:
- Line 2: Update the changeset entry that currently reads `"@biomejs/biome":
minor` to use `patch` instead of `minor` because this PR targets main; edit the
string in .changeset/html-no-redundant-roles.md so it becomes `"@biomejs/biome":
patch`, save the file and commit the change.
In `@crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs`:
- Around line 75-85: The logic in the lint (inside no_redundant_roles.rs where
ctx.source_type::<HtmlFileSource>() is checked) only skips PascalCase component
names; update the early-return condition to also skip kebab-case/custom-element
names by checking the element name text (from node.name() and
element_name.text()) for a hyphen (e.g., name_text.contains('-')) and return
None when present; keep the existing uppercase check and short-circuit so
framework files ignore both PascalCase and hyphenated custom component tags.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
.changeset/html-no-redundant-roles.mdcrates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs
There was a problem hiding this comment.
♻️ Duplicate comments (1)
crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs (1)
93-95:⚠️ Potential issue | 🟠 MajorPrevent destructive auto-fix for multi-token
rolevalues.At Line 93 the rule parses via
AriaRole::from_roles(...), while at Line 119 the fixer always removes the entireroleattribute. For token lists (for example fallback roles), this can drop non-redundant tokens.Please either preserve remaining tokens when fixing, or skip the fix when multiple tokens are present.
💡 Minimal safe patch (avoid data loss until token-preserving rewrite is added)
fn action(ctx: &RuleContext<Self>, state: &Self::State) -> Option<HtmlRuleAction> { + let role_value = state.role_attribute_value.to_string(); + if role_value.split_ascii_whitespace().nth(1).is_some() { + // Multi-token role values may contain fallback roles; avoid destructive removal. + return None; + } + let mut mutation = ctx.root().begin(); mutation.remove_node(state.redundant_attribute.clone()); Some(HtmlRuleAction::new(#!/bin/bash set -euo pipefail # Verify how role parsing is done and where fixer removes the full attribute rg -nP --type=rust '\bpub\s+fn\s+from_roles\s*\(' crates/biome_aria_metadata/src/lib.rs -C4 rg -nP --type=rust '\bfrom_roles\s*\(' crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs -C3 rg -nP --type=rust 'remove_node\s*\(' crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs -C3 # Check whether noRedundantRoles fixtures already cover multi-token role values rg -nP 'role="[^"]+\s+[^"]+"' crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles -g '*.html' -g '*.vue' -g '*.svelte' -g '*.astro'Also applies to: 117-120
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs` around lines 93 - 95, The fixer currently parses role values with AriaRole::from_roles(...) and compares with AriaRoles.get_implicit_role(node) but then unconditionally removes the entire role attribute (via remove_node or the fixer code), which destroys multi-token role lists; change the fixer to first split the role attribute value on whitespace and if it contains more than one token, do not apply the automatic fix (i.e., skip calling remove_node or skip creating a fix) — preserve current behavior for single-token values only; alternatively, if you prefer to preserve tokens now, implement token-aware removal by rebuilding the role attribute without the redundant token(s) and update the attribute node instead of calling remove_node (use the same parsing via AriaRole::from_roles and operate on the role attribute string).
🧹 Nitpick comments (1)
crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs (1)
62-65: MoveRuleStatebelowimpl Ruleto match the project’s rule-file layout.Small maintainability tidy-up: keep helper structs/enums/functions after the
impl Ruleblock in these lint files.Based on learnings: in
crates/biome_analyze/**/*.rsrule files, helper functions/structs/enums should be placed below theimpl Ruleblock (except node unions used byQuery).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs` around lines 62 - 65, The struct RuleState is currently declared before the impl Rule block; move the RuleState declaration so it appears after the impl Rule implementation to follow the project's rule-file layout convention (place helper structs/enums/functions after impl Rule). Locate the RuleState type (fields redundant_attribute: HtmlAttribute and role_attribute_value: Text) and relocate its entire declaration to immediately after the impl Rule block while keeping any references/usages unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs`:
- Around line 93-95: The fixer currently parses role values with
AriaRole::from_roles(...) and compares with AriaRoles.get_implicit_role(node)
but then unconditionally removes the entire role attribute (via remove_node or
the fixer code), which destroys multi-token role lists; change the fixer to
first split the role attribute value on whitespace and if it contains more than
one token, do not apply the automatic fix (i.e., skip calling remove_node or
skip creating a fix) — preserve current behavior for single-token values only;
alternatively, if you prefer to preserve tokens now, implement token-aware
removal by rebuilding the role attribute without the redundant token(s) and
update the attribute node instead of calling remove_node (use the same parsing
via AriaRole::from_roles and operate on the role attribute string).
---
Nitpick comments:
In `@crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs`:
- Around line 62-65: The struct RuleState is currently declared before the impl
Rule block; move the RuleState declaration so it appears after the impl Rule
implementation to follow the project's rule-file layout convention (place helper
structs/enums/functions after impl Rule). Locate the RuleState type (fields
redundant_attribute: HtmlAttribute and role_attribute_value: Text) and relocate
its entire declaration to immediately after the impl Rule block while keeping
any references/usages unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: fff5454b-18f8-446c-b1ed-499c10a24560
⛔ Files ignored due to path filters (8)
crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/astro/invalid.astro.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/astro/valid.astro.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/invalid.html.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/invalidHtmlAamRoleGeneric.html.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/svelte/invalid.svelte.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/svelte/valid.svelte.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/vue/invalid.vue.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/vue/valid.vue.snapis excluded by!**/*.snapand included by**
📒 Files selected for processing (7)
crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rscrates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/astro/invalid.astrocrates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/astro/valid.astrocrates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/svelte/invalid.sveltecrates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/svelte/valid.sveltecrates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/vue/invalid.vuecrates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/vue/valid.vue
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs (1)
62-65: KeepRuleStateinternal and place it belowimpl Rule.This state type is internal to this rule; dropping
puband moving it below theimpl Ruleblock keeps the module surface cleaner and matches local rule-file conventions.Based on learnings: In `crates/biome_analyze/**/*.rs` rule files, all helper functions, structs, and enums must be placed below the `impl Rule` block.Suggested tidy-up
-pub struct RuleState { +struct RuleState { redundant_attribute: HtmlAttribute, role_attribute_value: Text, }// Move `RuleState` below the `impl Rule for NoRedundantRoles` block.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs` around lines 62 - 65, Drop the public visibility from the RuleState struct (change `pub struct RuleState` to `struct RuleState`) and move its declaration so it appears below the `impl Rule for NoRedundantRoles` block; this keeps RuleState internal to the rule and follows the project convention of placing helper types after the `impl Rule` implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs`:
- Around line 62-65: Drop the public visibility from the RuleState struct
(change `pub struct RuleState` to `struct RuleState`) and move its declaration
so it appears below the `impl Rule for NoRedundantRoles` block; this keeps
RuleState internal to the rule and follows the project convention of placing
helper types after the `impl Rule` implementation.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 78b40007-d669-4a80-881c-d9b2944e00c9
📒 Files selected for processing (1)
crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs
|
This still needs to be rebased on |
6e002a5 to
dc35c27
Compare
done |
4c7d959 to
c118d4b
Compare
Add html-eslint no-redundant-role as a rule source and create minor changeset.
e64feec to
d52543f
Compare
|
@IxxyDev can you resolve the conflicts? |
done |
Summary
Added HTML support for the
noRedundantRoleslint rule.biome_aria::Elementandbiome_aria::Attributetraits for HTML elements, enabling ARIA role detection.roleattributes on HTML elements (e.g.,<button role="button">) and provides an unsafe fix to remove them.<Button>) are treated as custom components and skipped, while lowercase native elements are still checked.Test Plan
cargo test -p biome_html_analyze -- no_redundant_roles— 9 tests pass (HTML invalid, HTML valid, HTML-AAM generic role, plus Vue/Svelte/Astro valid and invalid)<Button role="button">(component) is NOT flagged<button role="button">(native) IS still flaggedDocs
Documentation is inline in the rule's rustdoc — added a
:::noteblock explaining that PascalCase names are treated as custom components in framework files and are ignored by this rule.