Skip to content

feat(html/a11y): add noRedundantRoles rule for HTML#9276

Open
IxxyDev wants to merge 1 commit intobiomejs:mainfrom
IxxyDev:feat/html-no-redundant-roles
Open

feat(html/a11y): add noRedundantRoles rule for HTML#9276
IxxyDev wants to merge 1 commit intobiomejs:mainfrom
IxxyDev:feat/html-no-redundant-roles

Conversation

@IxxyDev
Copy link

@IxxyDev IxxyDev commented Feb 28, 2026

Summary

  • Added HTML support for the noRedundantRoles lint rule.
  • Implemented biome_aria::Element and biome_aria::Attribute traits for HTML elements, enabling ARIA role detection.
  • Biome now detects redundant explicit role attributes on HTML elements (e.g., <button role="button">) and provides an unsafe fix to remove them.

Test plan

  • cargo test -p biome_html_analyze -- no_redundant_roles — 3 tests pass (invalid, valid, HTML-AAM generic role)
  • cargo format passes
  • cargo lint passes
  • cargo run -p xtask_codegen -- analyzer runs without errors

@changeset-bot
Copy link

changeset-bot bot commented Feb 28, 2026

🦋 Changeset detected

Latest commit: 183ad77

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@biomejs/biome Patch
@biomejs/cli-win32-x64 Patch
@biomejs/cli-win32-arm64 Patch
@biomejs/cli-darwin-x64 Patch
@biomejs/cli-darwin-arm64 Patch
@biomejs/cli-linux-x64 Patch
@biomejs/cli-linux-arm64 Patch
@biomejs/cli-linux-x64-musl Patch
@biomejs/cli-linux-arm64-musl Patch
@biomejs/wasm-web Patch
@biomejs/wasm-bundler Patch
@biomejs/wasm-nodejs Patch
@biomejs/backend-jsonrpc Patch

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

@github-actions github-actions bot added A-Linter Area: linter A-Parser Area: parser L-HTML Language: HTML and super languages labels Feb 28, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 28, 2026

Walkthrough

This PR introduces a new HTML lint rule noRedundantRoles that detects when elements carry explicit role attributes duplicating their implicit semantic roles (e.g., <button role="button">). It includes the rule implementation with automated fixes to remove redundant attributes, test fixtures for valid and invalid cases, and integrations with the biome_aria library. Two crates gain the biome_aria dependency, and biome_html_syntax now implements trait abstractions enabling HTML element and attribute interactions.

Possibly related PRs

Suggested labels

A-Linter, A-Parser, L-HTML

Suggested reviewers

  • ematipico
  • dyc3
  • Netail
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding a noRedundantRoles lint rule for HTML.
Description check ✅ Passed The description is directly related to the changeset, detailing the noRedundantRoles feature, trait implementations, and test plan.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 412a08d and 183ad77.

⛔ Files ignored due to path filters (4)
  • Cargo.lock is excluded by !**/*.lock and included by **
  • crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/invalid.html.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/invalidHtmlAamRoleGeneric.html.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/valid.html.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (8)
  • .changeset/some-trams-cut.md
  • crates/biome_html_analyze/Cargo.toml
  • crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs
  • crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/invalid.html
  • crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/invalidHtmlAamRoleGeneric.html
  • crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/valid.html
  • crates/biome_html_syntax/Cargo.toml
  • crates/biome_html_syntax/src/element_ext.rs

Comment on lines +1 to +3
---
"@biomejs/biome": patch
---
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
---
"@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.

Comment on lines +69 to +71
let role_attribute_value = role_attribute.initializer()?.value().ok()?.string_value()?;
let explicit_role = AriaRole::from_roles(role_attribute_value.trim())?;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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 -C2

Repository: 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 -C6

Repository: 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 -80

Repository: 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 html

Repository: 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.

Comment on lines +89 to +90
"Using the role attribute '"{role_attribute}"' on the '"{element_name}"' element is redundant, because it is implied by its semantic."
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Polish the diagnostic copy.

Line 89 says “implied by its semantic”; “implied by its semantics” reads cleaner in user-facing output.

Suggested fix
-                "Using the role attribute '"{role_attribute}"' on the '"{element_name}"' element is redundant, because it is implied by its semantic."
+                "Using the role attribute '"{role_attribute}"' on the '"{element_name}"' element is redundant, because it is implied by its semantics."
📝 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.

Suggested change
"Using the role attribute '"{role_attribute}"' on the '"{element_name}"' element is redundant, because it is implied by its semantic."
},
"Using the role attribute '"{role_attribute}"' on the '"{element_name}"' element is redundant, because it is implied by its semantics."
},
🤖 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
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.

Copy link
Contributor

@dyc3 dyc3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • This needs to be based on next
  • needs to have a minor changeset

version: "next",
name: "noRedundantRoles",
language: "html",
sources: &[RuleSource::EslintJsxA11y("no-redundant-roles").same()],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a rule like this in https://html-eslint.org/ ? if so, add it as a rule source.

@codspeed-hq
Copy link

codspeed-hq bot commented Feb 28, 2026

Merging this PR will not alter performance

✅ 58 untouched benchmarks
⏩ 156 skipped benchmarks1


Comparing IxxyDev:feat/html-no-redundant-roles (183ad77) with main (a2b4494)2

Open in CodSpeed

Footnotes

  1. 156 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (412a08d) during the generation of this report, so a2b4494 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@ematipico
Copy link
Member

...and it needs to restore the original PR template

Copy link
Member

@ematipico ematipico left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Linter Area: linter A-Parser Area: parser L-HTML Language: HTML and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants