diff --git a/.changeset/html-no-redundant-roles.md b/.changeset/html-no-redundant-roles.md new file mode 100644 index 000000000000..6a6152394d00 --- /dev/null +++ b/.changeset/html-no-redundant-roles.md @@ -0,0 +1,10 @@ +--- +"@biomejs/biome": minor +--- + +Added the HTML lint rule [`noRedundantRoles`](https://biomejs.dev/linter/rules/no-redundant-roles/). This rule enforces that explicit `role` attributes are not the same as the implicit/default role of an HTML element. It supports HTML, Vue, Svelte, and Astro files. + +```html + + +``` diff --git a/Cargo.lock b/Cargo.lock index afa837df5006..8f17c654bec3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -809,6 +809,7 @@ version = "0.5.7" dependencies = [ "biome_analyze", "biome_analyze_macros", + "biome_aria", "biome_aria_metadata", "biome_console", "biome_deserialize", @@ -902,6 +903,7 @@ dependencies = [ name = "biome_html_syntax" version = "0.5.7" dependencies = [ + "biome_aria", "biome_html_factory", "biome_html_parser", "biome_rowan", diff --git a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs index d050614940e6..70047e79c93d 100644 --- a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs +++ b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs @@ -249,6 +249,14 @@ pub(crate) fn migrate_eslint_any_rule( .get_or_insert(Default::default()); rule.set_level(rule.level().max(rule_severity.into())); } + "@html-eslint/no-redundant-role" => { + let group = rules.a11y.get_or_insert_with(Default::default); + let rule = group + .unwrap_group_as_mut() + .no_redundant_roles + .get_or_insert(Default::default()); + rule.set_level(rule.level().max(rule_severity.into())); + } "@html-eslint/require-button-type" => { let group = rules.a11y.get_or_insert_with(Default::default); let rule = group diff --git a/crates/biome_html_analyze/Cargo.toml b/crates/biome_html_analyze/Cargo.toml index ff6ff38f2886..3f14c17ed70e 100644 --- a/crates/biome_html_analyze/Cargo.toml +++ b/crates/biome_html_analyze/Cargo.toml @@ -17,6 +17,7 @@ name = "html_analyzer" [dependencies] biome_analyze = { workspace = true } biome_analyze_macros = { workspace = true } +biome_aria = { workspace = true } biome_aria_metadata = { workspace = true } biome_console = { workspace = true } biome_deserialize = { workspace = true } diff --git a/crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs b/crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs new file mode 100644 index 000000000000..d56049c6598f --- /dev/null +++ b/crates/biome_html_analyze/src/lint/a11y/no_redundant_roles.rs @@ -0,0 +1,130 @@ +use biome_analyze::{ + Ast, FixKind, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, +}; +use biome_aria::AriaRoles; +use biome_aria_metadata::AriaRole; +use biome_console::markup; +use biome_diagnostics::Severity; +use biome_html_syntax::{AnyHtmlElement, HtmlAttribute, HtmlFileSource}; +use biome_rowan::{AstNode, BatchMutationExt, Text}; +use biome_rule_options::no_redundant_roles::NoRedundantRolesOptions; + +use crate::HtmlRuleAction; + +declare_lint_rule! { + /// Enforce explicit `role` property is not the same as implicit/default role property on an element. + /// + /// :::note + /// In `.html` files, all elements are treated as native HTML elements. + /// + /// In component-based frameworks (Vue, Svelte, Astro), only native HTML element names are checked. + /// PascalCase names like ` + /// ``` + /// + /// ```html,expect_diagnostic + ///

title

+ /// ``` + /// + /// ### Valid + /// + /// ```html + ///
+ /// ``` + /// + /// ```html + /// + /// ``` + /// + pub NoRedundantRoles { + version: "next", + name: "noRedundantRoles", + language: "html", + sources: &[RuleSource::EslintJsxA11y("no-redundant-roles").same(), RuleSource::HtmlEslint("no-redundant-role").same()], + recommended: true, + severity: Severity::Error, + fix_kind: FixKind::Unsafe, + } +} + +impl Rule for NoRedundantRoles { + type Query = Ast; + type State = RuleState; + type Signals = Option; + type Options = NoRedundantRolesOptions; + + fn run(ctx: &RuleContext) -> Self::Signals { + let node = ctx.query(); + + let source_type = ctx.source_type::(); + if !source_type.is_html() { + let element_name = node.name()?; + let name_text = element_name.text(); + if name_text.chars().next().is_some_and(|c| c.is_uppercase()) + || name_text.contains('-') + { + return None; + } + } + + let role_attribute = node.find_attribute_by_name("role")?; + let role_attribute_value = role_attribute.initializer()?.value().ok()?.string_value()?; + let trimmed = role_attribute_value.trim(); + let explicit_role = AriaRole::from_roles(trimmed)?; + + if AriaRoles.get_implicit_role(node)? == explicit_role { + let has_multiple_roles = trimmed.split_ascii_whitespace().nth(1).is_some(); + return Some(RuleState { + redundant_attribute: role_attribute, + role_attribute_value, + has_multiple_roles, + }); + } + None + } + + fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { + let element_name = ctx.query().name()?; + let element_name = element_name.text(); + let role_attribute = state.role_attribute_value.to_string(); + Some(RuleDiagnostic::new( + rule_category!(), + state.redundant_attribute.range(), + markup! { + "Using the role attribute '"{role_attribute}"' on the '"{element_name}"' element is redundant, because it is implied by its semantics." + }, + )) + } + + fn action(ctx: &RuleContext, state: &Self::State) -> Option { + if state.has_multiple_roles { + return None; + } + let mut mutation = ctx.root().begin(); + mutation.remove_node(state.redundant_attribute.clone()); + Some(HtmlRuleAction::new( + ctx.metadata().action_category(ctx.category(), ctx.group()), + ctx.metadata().applicability(), + markup! { "Remove the ""role"" attribute." }.to_owned(), + mutation, + )) + } +} + +pub struct RuleState { + redundant_attribute: HtmlAttribute, + role_attribute_value: Text, + has_multiple_roles: bool, +} diff --git a/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/astro/invalid.astro b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/astro/invalid.astro new file mode 100644 index 000000000000..3db964c0006f --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/astro/invalid.astro @@ -0,0 +1,9 @@ +--- +// Astro frontmatter +--- + + +
+ +

title

+ diff --git a/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/astro/invalid.astro.snap b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/astro/invalid.astro.snap new file mode 100644 index 000000000000..b2ae67c13bb2 --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/astro/invalid.astro.snap @@ -0,0 +1,92 @@ +--- +source: crates/biome_html_analyze/tests/spec_tests.rs +expression: invalid.astro +--- +# Input +```html +--- +// Astro frontmatter +--- + + +
+ +

title

+ + +``` + +# Diagnostics +``` +invalid.astro:6:10 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'article' on the 'article' element is redundant, because it is implied by its semantics. + + 5 │ + > 6 │
+ │ ^^^^^^^^^^^^^^ + 7 │ + 8 │

title

+ + i Unsafe fix: Remove the role attribute. + + 6 │ + │ -------------- + +``` + +``` +invalid.astro:7:9 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'button' on the 'button' element is redundant, because it is implied by its semantics. + + 5 │ + 6 │
+ > 7 │ + │ ^^^^^^^^^^^^^ + 8 │

title

+ 9 │ + + i Unsafe fix: Remove the role attribute. + + 7 │ + │ ------------- + +``` + +``` +invalid.astro:8:5 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'heading' on the 'h1' element is redundant, because it is implied by its semantics. + + 6 │
+ 7 │ + > 8 │

title

+ │ ^^^^^^^^^^^^^^ + 9 │ + 10 │ + + i Unsafe fix: Remove the role attribute. + + 8 │ title + │ --------------- + +``` + +``` +invalid.astro:9:6 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'navigation' on the 'nav' element is redundant, because it is implied by its semantics. + + 7 │ + 8 │

title

+ > 9 │ + │ ^^^^^^^^^^^^^^^^^ + 10 │ + + i Unsafe fix: Remove the role attribute. + + 9 │ + │ ----------------- + +``` diff --git a/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/astro/valid.astro b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/astro/valid.astro new file mode 100644 index 000000000000..e1281a87c93d --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/astro/valid.astro @@ -0,0 +1,21 @@ +--- +// Astro frontmatter +--- + + + + +
+ +
+
+ + + + + + + +
+ +
diff --git a/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/astro/valid.astro.snap b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/astro/valid.astro.snap new file mode 100644 index 000000000000..f9abc349c046 --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/astro/valid.astro.snap @@ -0,0 +1,29 @@ +--- +source: crates/biome_html_analyze/tests/spec_tests.rs +expression: valid.astro +--- +# Input +```html +--- +// Astro frontmatter +--- + + + + +
+ +
+
+ + + + + + + +
+ +
+ +``` diff --git a/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/invalid.html b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/invalid.html new file mode 100644 index 000000000000..435644b8f810 --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/invalid.html @@ -0,0 +1,27 @@ +
+ +

title

+ + +
+
+
+bar + + +
    +
      + + +
    • + + + + + + +
      + + + + diff --git a/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/invalid.html.snap b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/invalid.html.snap new file mode 100644 index 000000000000..19cb028d57b6 --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/invalid.html.snap @@ -0,0 +1,537 @@ +--- +source: crates/biome_html_analyze/tests/spec_tests.rs +expression: invalid.html +--- +# Input +```html +
      + +

      title

      + + +
      +
      +
      +bar + + +
        +
          + + +
        • + + + + + + +
          + + + + + +``` + +# Diagnostics +``` +invalid.html:1:10 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'article' on the 'article' element is redundant, because it is implied by its semantics. + + > 1 │
          + │ ^^^^^^^^^^^^^^ + 2 │ + 3 │

          title

          + + i Unsafe fix: Remove the role attribute. + + 1 │ + │ -------------- + +``` + +``` +invalid.html:2:9 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'button' on the 'button' element is redundant, because it is implied by its semantics. + + 1 │
          + > 2 │ + │ ^^^^^^^^^^^^^ + 3 │

          title

          + 4 │ + + i Unsafe fix: Remove the role attribute. + + 2 │ + │ ------------- + +``` + +``` +invalid.html:3:5 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'heading' on the 'h1' element is redundant, because it is implied by its semantics. + + 1 │
          + 2 │ + > 3 │

          title

          + │ ^^^^^^^^^^^^^^ + 4 │ + 5 │ + + i Unsafe fix: Remove the role attribute. + + 3 │ title + │ --------------- + +``` + +``` +invalid.html:4:9 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'dialog' on the 'dialog' element is redundant, because it is implied by its semantics. + + 2 │ + 3 │

          title

          + > 4 │ + │ ^^^^^^^^^^^^^ + 5 │ + 6 │
          + + i Unsafe fix: Remove the role attribute. + + 4 │ + │ ------------- + +``` + +``` +invalid.html:5:24 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'checkbox' on the 'input' element is redundant, because it is implied by its semantics. + + 3 │

          title

          + 4 │ + > 5 │ + │ ^^^^^^^^^^^^^^^ + 6 │
          + 7 │
          + + i Unsafe fix: Remove the role attribute. + + 5 │ + │ ---------------- + +``` + +``` +invalid.html:6:9 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'figure' on the 'figure' element is redundant, because it is implied by its semantics. + + 4 │ + 5 │ + > 6 │
          + │ ^^^^^^^^^^^^^ + 7 │
          + 8 │
          + + i Unsafe fix: Remove the role attribute. + + 6 │ + │ ------------- + +``` + +``` +invalid.html:7:7 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'form' on the 'form' element is redundant, because it is implied by its semantics. + + 5 │ + 6 │
          + > 7 │
          + │ ^^^^^^^^^^^ + 8 │
          + 9 │ bar + + i Unsafe fix: Remove the role attribute. + + 7 │ + │ ----------- + +``` + +``` +invalid.html:8:11 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'group' on the 'fieldset' element is redundant, because it is implied by its semantics. + + 6 │
          + 7 │
          + > 8 │
          + │ ^^^^^^^^^^^^ + 9 │ bar + 10 │ + + i Unsafe fix: Remove the role attribute. + + 8 │ + │ ------------ + +``` + +``` +invalid.html:9:26 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'img' on the 'img' element is redundant, because it is implied by its semantics. + + 7 │
          + 8 │
          + > 9 │ bar + │ ^^^^^^^^^^ + 10 │ + 11 │ + + i Unsafe fix: Remove the role attribute. + + 9 │ + │ ----------- + +``` + +``` +invalid.html:10:13 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'presentation' on the 'img' element is redundant, because it is implied by its semantics. + + 8 │
          + 9 │ bar + > 10 │ + │ ^^^^^^^^^^^^^^^^^^^ + 11 │ + 12 │
            + + i Unsafe fix: Remove the role attribute. + + 10 │ + │ -------------------- + +``` + +``` +invalid.html:11:13 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'link' on the 'a' element is redundant, because it is implied by its semantics. + + 9 │ bar + 10 │ + > 11 │ + │ ^^^^^^^^^^^ + 12 │
              + 13 │
                + + i Unsafe fix: Remove the role attribute. + + 11 │ + │ ----------- + +``` + +``` +invalid.html:12:5 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'list' on the 'ol' element is redundant, because it is implied by its semantics. + + 10 │ + 11 │ + > 12 │
                  + │ ^^^^^^^^^^^ + 13 │
                    + 14 │ + + i Unsafe fix: Remove the role attribute. + + 12 │ + │ ----------- + +``` + +``` +invalid.html:13:5 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'list' on the 'ul' element is redundant, because it is implied by its semantics. + + 11 │ + 12 │
                      + > 13 │
                        + │ ^^^^^^^^^^^ + 14 │ + 15 │ + + i Unsafe fix: Remove the role attribute. + + 13 │ + │ ----------- + +``` + +``` +invalid.html:14:21 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'combobox' on the 'select' element is redundant, because it is implied by its semantics. + + 12 │
                          + 13 │
                            + > 14 │ + │ ^^^^^^^^^^^^^^^ + 15 │ + 16 │
                          • + + i Unsafe fix: Remove the role attribute. + + 14 │ + │ --------------- + +``` + +``` +invalid.html:15:39 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'listbox' on the 'select' element is redundant, because it is implied by its semantics. + + 13 │
                              + 14 │ + > 15 │ + │ ^^^^^^^^^^^^^^ + 16 │
                            • + 17 │ + + i Unsafe fix: Remove the role attribute. + + 15 │ + │ -------------- + +``` + +``` +invalid.html:16:5 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'listitem' on the 'li' element is redundant, because it is implied by its semantics. + + 14 │ + 15 │ + > 16 │
                            • + │ ^^^^^^^^^^^^^^^ + 17 │ + 18 │ + + i Unsafe fix: Remove the role attribute. + + 16 │ + │ --------------- + +``` + +``` +invalid.html:17:6 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'navigation' on the 'nav' element is redundant, because it is implied by its semantics. + + 15 │ + 16 │
                            • + > 17 │ + │ ^^^^^^^^^^^^^^^^^ + 18 │ + 19 │ + + i Unsafe fix: Remove the role attribute. + + 17 │ + │ ----------------- + +``` + +``` +invalid.html:18:5 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'row' on the 'tr' element is redundant, because it is implied by its semantics. + + 16 │
                            • + 17 │ + > 18 │ + │ ^^^^^^^^^^ + 19 │ + 20 │ + + i Unsafe fix: Remove the role attribute. + + 18 │ + │ ---------- + +``` + +``` +invalid.html:19:8 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'rowgroup' on the 'tbody' element is redundant, because it is implied by its semantics. + + 17 │ + 18 │ + > 19 │ + │ ^^^^^^^^^^^^^^^ + 20 │ + 21 │ + + i Unsafe fix: Remove the role attribute. + + 19 │ + │ --------------- + +``` + +``` +invalid.html:20:8 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'rowgroup' on the 'tfoot' element is redundant, because it is implied by its semantics. + + 18 │ + 19 │ + > 20 │ + │ ^^^^^^^^^^^^^^^ + 21 │ + 22 │ + + i Unsafe fix: Remove the role attribute. + + 20 │ + │ --------------- + +``` + +``` +invalid.html:21:8 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'rowgroup' on the 'thead' element is redundant, because it is implied by its semantics. + + 19 │ + 20 │ + > 21 │ + │ ^^^^^^^^^^^^^^^ + 22 │ + 23 │
                              + + i Unsafe fix: Remove the role attribute. + + 21 │ + │ --------------- + +``` + +``` +invalid.html:22:22 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'searchbox' on the 'input' element is redundant, because it is implied by its semantics. + + 20 │ + 21 │ + > 22 │ + │ ^^^^^^^^^^^^^^^^ + 23 │
                              + 24 │ + + i Unsafe fix: Remove the role attribute. + + 22 │ + │ ----------------- + +``` + +``` +invalid.html:23:8 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'table' on the 'table' element is redundant, because it is implied by its semantics. + + 21 │ + 22 │ + > 23 │
                              + │ ^^^^^^^^^^^^ + 24 │ + 25 │ + + i Unsafe fix: Remove the role attribute. + + 23 │ + │ ------------ + +``` + +``` +invalid.html:24:11 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'textbox' on the 'textarea' element is redundant, because it is implied by its semantics. + + 22 │ + 23 │
                              + > 24 │ + │ ^^^^^^^^^^^^^^ + 25 │ + 26 │ + + i Unsafe fix: Remove the role attribute. + + 24 │ + │ -------------- + +``` + +``` +invalid.html:25:20 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'textbox' on the 'input' element is redundant, because it is implied by its semantics. + + 23 │
                              + 24 │ + > 25 │ + │ ^^^^^^^^^^^^^^ + 26 │ + 27 │ + + i Unsafe fix: Remove the role attribute. + + 25 │ + │ --------------- + +``` + +``` +invalid.html:26:9 lint/a11y/noRedundantRoles ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'button presentation' on the 'button' element is redundant, because it is implied by its semantics. + + 24 │ + 25 │ + > 26 │ + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + 27 │ + 28 │ + + +``` + +``` +invalid.html:27:6 lint/a11y/noRedundantRoles ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'navigation link' on the 'nav' element is redundant, because it is implied by its semantics. + + 25 │ + 26 │ + > 27 │ + │ ^^^^^^^^^^^^^^^^^^^^^^ + 28 │ + + +``` diff --git a/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/invalidHtmlAamRoleGeneric.html b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/invalidHtmlAamRoleGeneric.html new file mode 100644 index 000000000000..77e21097d92f --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/invalidHtmlAamRoleGeneric.html @@ -0,0 +1,11 @@ +x +x +x +
                              x
                              +x +x +
                              x
                              +x +x +x +x diff --git a/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/invalidHtmlAamRoleGeneric.html.snap b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/invalidHtmlAamRoleGeneric.html.snap new file mode 100644 index 000000000000..c89147904c81 --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/invalidHtmlAamRoleGeneric.html.snap @@ -0,0 +1,225 @@ +--- +source: crates/biome_html_analyze/tests/spec_tests.rs +expression: invalidHtmlAamRoleGeneric.html +--- +# Input +```html +x +x +x +
                              x
                              +x +x +
                              x
                              +x +x +x +x + +``` + +# Diagnostics +``` +invalidHtmlAamRoleGeneric.html:1:4 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'generic' on the 'b' element is redundant, because it is implied by its semantics. + + > 1 │ x + │ ^^^^^^^^^^^^^^ + 2 │ x + 3 │ x + + i Unsafe fix: Remove the role attribute. + + 1 │ x + │ -------------- + +``` + +``` +invalidHtmlAamRoleGeneric.html:2:6 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'generic' on the 'bdi' element is redundant, because it is implied by its semantics. + + 1 │ x + > 2 │ x + │ ^^^^^^^^^^^^^^ + 3 │ x + 4 │
                              x
                              + + i Unsafe fix: Remove the role attribute. + + 2 │ x + │ -------------- + +``` + +``` +invalidHtmlAamRoleGeneric.html:3:6 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'generic' on the 'bdo' element is redundant, because it is implied by its semantics. + + 1 │ x + 2 │ x + > 3 │ x + │ ^^^^^^^^^^^^^^ + 4 │
                              x
                              + 5 │ x + + i Unsafe fix: Remove the role attribute. + + 3 │ x + │ -------------- + +``` + +``` +invalidHtmlAamRoleGeneric.html:4:6 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'generic' on the 'div' element is redundant, because it is implied by its semantics. + + 2 │ x + 3 │ x + > 4 │
                              x
                              + │ ^^^^^^^^^^^^^^ + 5 │ x + 6 │ x + + i Unsafe fix: Remove the role attribute. + + 4 │ x + │ -------------- + +``` + +``` +invalidHtmlAamRoleGeneric.html:5:7 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'generic' on the 'span' element is redundant, because it is implied by its semantics. + + 3 │ x + 4 │
                              x
                              + > 5 │ x + │ ^^^^^^^^^^^^^^ + 6 │ x + 7 │
                              x
                              + + i Unsafe fix: Remove the role attribute. + + 5 │ x + │ -------------- + +``` + +``` +invalidHtmlAamRoleGeneric.html:6:4 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'generic' on the 'i' element is redundant, because it is implied by its semantics. + + 4 │
                              x
                              + 5 │ x + > 6 │ x + │ ^^^^^^^^^^^^^^ + 7 │
                              x
                              + 8 │ x + + i Unsafe fix: Remove the role attribute. + + 6 │ x + │ -------------- + +``` + +``` +invalidHtmlAamRoleGeneric.html:7:6 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'generic' on the 'pre' element is redundant, because it is implied by its semantics. + + 5 │ x + 6 │ x + > 7 │
                              x
                              + │ ^^^^^^^^^^^^^^ + 8 │ x + 9 │ x + + i Unsafe fix: Remove the role attribute. + + 7 │ x + │ -------------- + +``` + +``` +invalidHtmlAamRoleGeneric.html:8:4 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'generic' on the 'q' element is redundant, because it is implied by its semantics. + + 6 │ x + 7 │
                              x
                              + > 8 │ x + │ ^^^^^^^^^^^^^^ + 9 │ x + 10 │ x + + i Unsafe fix: Remove the role attribute. + + 8 │ x + │ -------------- + +``` + +``` +invalidHtmlAamRoleGeneric.html:9:7 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'generic' on the 'samp' element is redundant, because it is implied by its semantics. + + 7 │
                              x
                              + 8 │ x + > 9 │ x + │ ^^^^^^^^^^^^^^ + 10 │ x + 11 │ x + + i Unsafe fix: Remove the role attribute. + + 9 │ x + │ -------------- + +``` + +``` +invalidHtmlAamRoleGeneric.html:10:8 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'generic' on the 'small' element is redundant, because it is implied by its semantics. + + 8 │ x + 9 │ x + > 10 │ x + │ ^^^^^^^^^^^^^^ + 11 │ x + 12 │ + + i Unsafe fix: Remove the role attribute. + + 10 │ x + │ -------------- + +``` + +``` +invalidHtmlAamRoleGeneric.html:11:4 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'generic' on the 'u' element is redundant, because it is implied by its semantics. + + 9 │ x + 10 │ x + > 11 │ x + │ ^^^^^^^^^^^^^^ + 12 │ + + i Unsafe fix: Remove the role attribute. + + 11 │ x + │ -------------- + +``` diff --git a/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/svelte/invalid.svelte b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/svelte/invalid.svelte new file mode 100644 index 000000000000..006a0347cae9 --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/svelte/invalid.svelte @@ -0,0 +1,5 @@ + +
                              + +

                              title

                              + diff --git a/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/svelte/invalid.svelte.snap b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/svelte/invalid.svelte.snap new file mode 100644 index 000000000000..6d10d46b3db0 --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/svelte/invalid.svelte.snap @@ -0,0 +1,88 @@ +--- +source: crates/biome_html_analyze/tests/spec_tests.rs +expression: invalid.svelte +--- +# Input +```html + +
                              + +

                              title

                              + + +``` + +# Diagnostics +``` +invalid.svelte:2:10 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'article' on the 'article' element is redundant, because it is implied by its semantics. + + 1 │ + > 2 │
                              + │ ^^^^^^^^^^^^^^ + 3 │ + 4 │

                              title

                              + + i Unsafe fix: Remove the role attribute. + + 2 │ + │ -------------- + +``` + +``` +invalid.svelte:3:9 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'button' on the 'button' element is redundant, because it is implied by its semantics. + + 1 │ + 2 │
                              + > 3 │ + │ ^^^^^^^^^^^^^ + 4 │

                              title

                              + 5 │ + + i Unsafe fix: Remove the role attribute. + + 3 │ + │ ------------- + +``` + +``` +invalid.svelte:4:5 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'heading' on the 'h1' element is redundant, because it is implied by its semantics. + + 2 │
                              + 3 │ + > 4 │

                              title

                              + │ ^^^^^^^^^^^^^^ + 5 │ + 6 │ + + i Unsafe fix: Remove the role attribute. + + 4 │ title + │ --------------- + +``` + +``` +invalid.svelte:5:6 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'navigation' on the 'nav' element is redundant, because it is implied by its semantics. + + 3 │ + 4 │

                              title

                              + > 5 │ + │ ^^^^^^^^^^^^^^^^^ + 6 │ + + i Unsafe fix: Remove the role attribute. + + 5 │ + │ ----------------- + +``` diff --git a/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/svelte/valid.svelte b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/svelte/valid.svelte new file mode 100644 index 000000000000..de83f27166b7 --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/svelte/valid.svelte @@ -0,0 +1,17 @@ + + + +
                              + +
                              +
                              + + + + + + + +
                              + +
                              diff --git a/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/svelte/valid.svelte.snap b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/svelte/valid.svelte.snap new file mode 100644 index 000000000000..b1158d12aff8 --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/svelte/valid.svelte.snap @@ -0,0 +1,25 @@ +--- +source: crates/biome_html_analyze/tests/spec_tests.rs +expression: valid.svelte +--- +# Input +```html + + + +
                              + +
                              +
                              + + + + + + + +
                              + +
                              + +``` diff --git a/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/valid.html b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/valid.html new file mode 100644 index 000000000000..b59b5e90a61c --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/valid.html @@ -0,0 +1,6 @@ +
                              + +
                              + + + diff --git a/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/valid.html.snap b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/valid.html.snap new file mode 100644 index 000000000000..b039fe56c13b --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/valid.html.snap @@ -0,0 +1,14 @@ +--- +source: crates/biome_html_analyze/tests/spec_tests.rs +expression: valid.html +--- +# Input +```html +
                              + +
                              + + + + +``` diff --git a/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/vue/invalid.vue b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/vue/invalid.vue new file mode 100644 index 000000000000..9d967cf73ebe --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/vue/invalid.vue @@ -0,0 +1,7 @@ + diff --git a/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/vue/invalid.vue.snap b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/vue/invalid.vue.snap new file mode 100644 index 000000000000..4e09d628b75c --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/vue/invalid.vue.snap @@ -0,0 +1,92 @@ +--- +source: crates/biome_html_analyze/tests/spec_tests.rs +expression: invalid.vue +--- +# Input +```html + + +``` + +# Diagnostics +``` +invalid.vue:3:12 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'article' on the 'article' element is redundant, because it is implied by its semantics. + + 1 │ + + i Unsafe fix: Remove the role attribute. + + 5 │ ··title + │ --------------- + +``` + +``` +invalid.vue:6:8 lint/a11y/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Using the role attribute 'navigation' on the 'nav' element is redundant, because it is implied by its semantics. + + 4 │ + 5 │

                              title

                              + > 6 │ + │ ^^^^^^^^^^^^^^^^^ + 7 │ + 8 │ + + i Unsafe fix: Remove the role attribute. + + 6 │ ·· + │ ----------------- + +``` diff --git a/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/vue/valid.vue b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/vue/valid.vue new file mode 100644 index 000000000000..aac73d5f3d06 --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/vue/valid.vue @@ -0,0 +1,19 @@ + diff --git a/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/vue/valid.vue.snap b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/vue/valid.vue.snap new file mode 100644 index 000000000000..030695b49b8f --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/noRedundantRoles/vue/valid.vue.snap @@ -0,0 +1,27 @@ +--- +source: crates/biome_html_analyze/tests/spec_tests.rs +expression: valid.vue +--- +# Input +```html + + +``` diff --git a/crates/biome_html_syntax/Cargo.toml b/crates/biome_html_syntax/Cargo.toml index 908085cfc493..2f9faeeaec1c 100644 --- a/crates/biome_html_syntax/Cargo.toml +++ b/crates/biome_html_syntax/Cargo.toml @@ -14,6 +14,7 @@ publish = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +biome_aria = { workspace = true } biome_rowan = { workspace = true, features = ["serde"] } biome_string_case = { workspace = true } camino = { workspace = true } diff --git a/crates/biome_html_syntax/src/element_ext.rs b/crates/biome_html_syntax/src/element_ext.rs index 7ea646a5643e..a9c783edb741 100644 --- a/crates/biome_html_syntax/src/element_ext.rs +++ b/crates/biome_html_syntax/src/element_ext.rs @@ -6,6 +6,7 @@ use crate::{ }; use biome_rowan::{AstNodeList, SyntaxResult, TokenText, declare_node_union}; +use biome_string_case::StrOnlyExtension; /// https://html.spec.whatwg.org/#void-elements const VOID_ELEMENTS: &[&str] = &[ @@ -425,6 +426,43 @@ impl AnyHtmlTagElement { } } +impl biome_aria::Element for AnyHtmlElement { + fn name(&self) -> Option> { + // HTML element names are case-insensitive; lowercase for AriaRoles matching + Some(Self::name(self)?.text().to_lowercase_cow().into_owned()) + } + + fn attributes(&self) -> impl Iterator { + Self::attributes(self) + .into_iter() + .flatten() + .filter_map(|attr| match attr { + AnyHtmlAttribute::HtmlAttribute(attr) => Some(attr), + _ => None, + }) + } +} + +impl biome_aria::Attribute for HtmlAttribute { + fn name(&self) -> Option> { + // HTML attribute names are case-insensitive; lowercase for matching + Some( + self.name() + .ok()? + .value_token() + .ok()? + .text_trimmed() + .to_lowercase_cow() + .into_owned(), + ) + } + + fn value(&self) -> Option> { + // Text implements Deref but not AsRef, convert to String + Some(Self::value(self)?.to_string()) + } +} + #[cfg(test)] mod tests { use biome_html_factory::syntax::HtmlElement;