diff --git a/CHANGELOG.md b/CHANGELOG.md index f7fc16e61fb..92e05f36a17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Linter #### New rules - [`noConfusingArrow`](https://docs.rome.tools/lint/rules/noConfusingArrow/) +- [`noRedundantRoles`](https://docs.rome.tools/lint/rules/noRedundantRoles/) ### Parser ### VSCode ### JavaScript APIs diff --git a/crates/rome_aria/src/roles.rs b/crates/rome_aria/src/roles.rs index 6f627968f3a..8f650a8ef74 100644 --- a/crates/rome_aria/src/roles.rs +++ b/crates/rome_aria/src/roles.rs @@ -1,5 +1,6 @@ use crate::{define_role, is_aria_property_valid}; use rome_aria_metadata::AriaPropertiesEnum; +use std::collections::HashMap; use std::fmt::Debug; use std::slice::Iter; use std::str::FromStr; @@ -60,6 +61,11 @@ pub trait AriaRoleDefinition: Debug { fn is_interactive(&self) -> bool { self.roles().any(|role| *role == "widget") } + + /// Returns a concrete type name. + fn type_name(&self) -> &'static str { + return std::any::type_name::(); + } } define_role! { @@ -531,6 +537,181 @@ define_role! { } } +define_role! { + /// https://www.w3.org/TR/wai-aria-1.2/#generic + GenericRole { + PROPS: [], + ROLES: ["structure"], + CONCEPTS: &[("div", &[]), ("span", &[])], + } +} + +define_role! { + /// https://www.w3.org/TR/wai-aria-1.2/#complementary + ComplementaryRole { + PROPS: [], + ROLES: ["landmark"], + } +} + +define_role! { + /// https://www.w3.org/TR/wai-aria-1.2/#blockquote + BlockQuoteRole { + PROPS: [], + ROLES: ["section"], + CONCEPTS: &[("blockquote", &[])], + } +} + +define_role! { + /// https://www.w3.org/TR/wai-aria-1.2/#caption + CaptionRole { + PROPS: [], + ROLES: ["section"], + CONCEPTS: &[("caption", &[]), ("figcaption", &[])], + } +} + +define_role! { + /// https://www.w3.org/TR/wai-aria-1.2/#code + CodeRole { + PROPS: [], + ROLES: ["section"], + CONCEPTS: &[("code", &[])], + } +} + +define_role! { + /// https://www.w3.org/TR/wai-aria-1.2/#deletion + DeletionRole { + PROPS: [], + ROLES: ["section"], + CONCEPTS: &[("del", &[])], + } +} + +define_role! { + /// https://www.w3.org/TR/wai-aria-1.2/#document + DocumentRole { + PROPS: [], + ROLES: ["structure"], + } +} + +define_role! { + /// https://www.w3.org/TR/wai-aria-1.2/#emphasis + EmphasisRole { + PROPS: [], + ROLES: ["section"], + CONCEPTS: &[("em", &[])], + } +} + +define_role! { + /// https://www.w3.org/TR/wai-aria-1.2/#insertion + InsertionRole { + PROPS: [], + ROLES: ["section"], + CONCEPTS: &[("ins", &[])], + } +} + +define_role! { + /// https://www.w3.org/TR/wai-aria-1.2/#math + MathRole { + PROPS: [], + ROLES: ["section"], + } +} + +define_role! { + /// https://www.w3.org/TR/wai-aria-1.2/#strong + StrongRole { + PROPS: [], + ROLES: ["section"], + CONCEPTS: &[("strong", &[])], + } +} + +define_role! { + /// https://www.w3.org/TR/wai-aria-1.2/#subscript + SubScriptRole { + PROPS: [], + ROLES: ["section"], + CONCEPTS: &[("sub", &[]), ("sup", &[])], + } +} + +define_role! { + /// https://www.w3.org/TR/wai-aria-1.2/#superscript + SuperScriptRole { + PROPS: [], + ROLES: ["section"], + CONCEPTS: &[("sub", &[]), ("sup", &[])], + } +} + +define_role! { + /// https://w3c.github.io/graphics-aria/#graphics-document + GraphicsDocumentRole { + PROPS: [], + ROLES: ["document"], + CONCEPTS: &[("graphics-object", &[]), ("img", &[]), ("article", &[])], + } +} + +define_role! { + /// https://www.w3.org/TR/wai-aria-1.2/#time + TimeRole { + PROPS: [], + ROLES: ["section"], + CONCEPTS: &[("time", &[])], + } +} + +define_role! { + /// https://www.w3.org/TR/wai-aria-1.2/#paragraph + ParagraphRole { + PROPS: [], + ROLES: ["section"], + CONCEPTS: &[("p", &[])], + } +} + +define_role! { + /// https://www.w3.org/TR/wai-aria-1.2/#status + StatusRole { + PROPS: [], + ROLES: ["section"], + } +} + +define_role! { + /// https://www.w3.org/TR/wai-aria-1.2/#meter + MeterRole { + PROPS: [], + ROLES: ["range"], + CONCEPTS: &[("meter", &[])], + } +} + +define_role! { + /// https://www.w3.org/TR/wai-aria-1.2/#presentation + PresentationRole { + PROPS: [], + ROLES: ["structure"], + } +} + +define_role! { + /// https://www.w3.org/TR/wai-aria-1.2/#region + RegionRole { + PROPS: [], + ROLES: ["landmark"], + CONCEPTS: &[("section", &[])], + } +} + impl<'a> AriaRoles { /// These are roles that will contain "concepts". pub(crate) const ROLE_WITH_CONCEPTS: &'a [&'a str] = &[ @@ -636,12 +817,139 @@ impl<'a> AriaRoles { "textbox" => &TextboxRole as &dyn AriaRoleDefinition, "toolbar" => &ToolbarRole as &dyn AriaRoleDefinition, "tree" => &TreeRole as &dyn AriaRoleDefinition, + "region" => &RegionRole as &dyn AriaRoleDefinition, + "presentation" => &PresentationRole as &dyn AriaRoleDefinition, + "document" => &DocumentRole as &dyn AriaRoleDefinition, + _ => return None, + }; + Some(result) + } + + /// Given a element and attributes, it returns the metadata of the element's implicit role. + /// + /// Check: https://www.w3.org/TR/html-aria/#docconformance + pub fn get_implicit_role( + &self, + element: &str, + // To generate `attributes`, you can use `rome_js_analyze::aria_services::AriaServices::extract_defined_attributes` + attributes: &HashMap>, + ) -> Option<&'static dyn AriaRoleDefinition> { + let result = match element { + "article" => &ArticleRole as &dyn AriaRoleDefinition, + "aside" => &ComplementaryRole as &dyn AriaRoleDefinition, + "blockquote" => &BlockQuoteRole as &dyn AriaRoleDefinition, + "button" => &ButtonRole as &dyn AriaRoleDefinition, + "caption" => &CaptionRole as &dyn AriaRoleDefinition, + "code" => &CodeRole as &dyn AriaRoleDefinition, + "datalist" => &ListBoxRole as &dyn AriaRoleDefinition, + "del" => &DeletionRole as &dyn AriaRoleDefinition, + "dfn" => &TermRole as &dyn AriaRoleDefinition, + "dialog" => &DialogRole as &dyn AriaRoleDefinition, + "em" => &EmphasisRole as &dyn AriaRoleDefinition, + "figure" => &FigureRole as &dyn AriaRoleDefinition, + "form" => &FormRole as &dyn AriaRoleDefinition, + "hr" => &SeparatorRole as &dyn AriaRoleDefinition, + "html" => &DocumentRole as &dyn AriaRoleDefinition, + "ins" => &InsertionRole as &dyn AriaRoleDefinition, + "main" => &MainRole as &dyn AriaRoleDefinition, + "math" => &MathRole as &dyn AriaRoleDefinition, + "menu" => &ListRole as &dyn AriaRoleDefinition, + "meter" => &MeterRole as &dyn AriaRoleDefinition, + "nav" => &NavigationRole as &dyn AriaRoleDefinition, + "ul" | "ol" => &ListRole as &dyn AriaRoleDefinition, + "li" => &ListItemRole as &dyn AriaRoleDefinition, + "optgroup" => &GroupRole as &dyn AriaRoleDefinition, + "output" => &StatusRole as &dyn AriaRoleDefinition, + "p" => &ParagraphRole as &dyn AriaRoleDefinition, + "progress" => &ProgressBarRole as &dyn AriaRoleDefinition, + "strong" => &StrongRole as &dyn AriaRoleDefinition, + "sub" => &SubScriptRole as &dyn AriaRoleDefinition, + "sup" => &SuperScriptRole as &dyn AriaRoleDefinition, + "svg" => &GraphicsDocumentRole as &dyn AriaRoleDefinition, + "table" => &TableRole as &dyn AriaRoleDefinition, + "textarea" => &TextboxRole as &dyn AriaRoleDefinition, + "tr" => &RowRole as &dyn AriaRoleDefinition, + "time" => &TimeRole as &dyn AriaRoleDefinition, + "address" | "details" | "fieldset" => &GroupRole as &dyn AriaRoleDefinition, + "h1" | "h2" | "h3" | "h4" | "h5" | "h6" => &HeadingRole as &dyn AriaRoleDefinition, + "tbody" | "tfoot" | "thead" => &RowGroupRole as &dyn AriaRoleDefinition, + "input" => { + let type_values = attributes.get("type")?; + match type_values.first()?.as_str() { + "checkbox" => &CheckboxRole as &dyn AriaRoleDefinition, + "number" => &SpinButtonRole as &dyn AriaRoleDefinition, + "radio" => &RadioRole as &dyn AriaRoleDefinition, + "range" => &SliderRole as &dyn AriaRoleDefinition, + "button" | "image" | "reset" | "submit" => { + &ButtonRole as &dyn AriaRoleDefinition + } + "search" => match attributes.get("list") { + Some(_) => &ComboBoxRole as &dyn AriaRoleDefinition, + _ => &SearchboxRole as &dyn AriaRoleDefinition, + }, + "email" | "tel" | "url" => match attributes.get("list") { + Some(_) => &ComboBoxRole as &dyn AriaRoleDefinition, + _ => &TextboxRole as &dyn AriaRoleDefinition, + }, + "text" => &TextboxRole as &dyn AriaRoleDefinition, + _ => &TextboxRole as &dyn AriaRoleDefinition, + } + } + "a" | "area" => match attributes.get("href") { + Some(_) => &LinkRole as &dyn AriaRoleDefinition, + _ => &GenericRole as &dyn AriaRoleDefinition, + }, + "img" => match attributes.get("alt") { + Some(values) => { + if values.iter().any(|x| !x.is_empty()) { + &ImgRole as &dyn AriaRoleDefinition + } else { + &PresentationRole as &dyn AriaRoleDefinition + } + } + None => &ImgRole as &dyn AriaRoleDefinition, + }, + "section" => { + let has_accessible_name = attributes.get("aria-labelledby").is_some() + || attributes.get("aria-label").is_some() + || attributes.get("title").is_some(); + if has_accessible_name { + &RegionRole as &dyn AriaRoleDefinition + } else { + return None; + } + } + "select" => { + let size = match attributes.get("size") { + Some(size) => size + .first() + .unwrap_or(&"0".to_string()) + .parse::() + .ok()?, + None => 0, + }; + let multiple = attributes.get("multiple"); + + if multiple.is_none() && size <= 1 { + &ComboBoxRole as &dyn AriaRoleDefinition + } else { + &ListBoxRole as &dyn AriaRoleDefinition + } + } + "b" | "bdi" | "bdo" | "body" | "data" | "div" | "hgroup" | "i" | "q" | "samp" + | "small" | "span" | "u" => &GenericRole as &dyn AriaRoleDefinition, + "header" | "footer" => { + // This crate does not support checking a descendant of an element. + // header (maybe BannerRole): https://www.w3.org/WAI/ARIA/apg/patterns/landmarks/examples/banner.html + // footer (maybe ContentInfoRole): https://www.w3.org/WAI/ARIA/apg/patterns/landmarks/examples/contentinfo.html + &GenericRole as &dyn AriaRoleDefinition + } _ => return None, }; Some(result) } - /// Given a role, it return whether this role is interactive + /// Given a role, it returns whether this role is interactive pub fn is_role_interactive(&self, role: &str) -> bool { let role = self.get_role(role); if let Some(role) = role { @@ -651,10 +959,10 @@ impl<'a> AriaRoles { } } - /// Given the name of element, the function tell whether it's interactive + /// Given the name of element, the function tells whether it's interactive pub fn is_not_interactive_element(&self, element_name: &str) -> bool { //
elements do not technically have semantics, unless the - // element is a direct descendant of , and this plugin cannot + // element is a direct descendant of , and this crate cannot // reliably test that. // // Check: https://www.w3.org/TR/wai-aria-practices/examples/landmarks/banner.html @@ -722,6 +1030,8 @@ pub struct AriaRoles; #[cfg(test)] mod test { + use std::collections::HashMap; + use crate::AriaRoles; #[test] @@ -736,4 +1046,29 @@ mod test { assert!(aria_roles.is_not_interactive_element("h5")); assert!(aria_roles.is_not_interactive_element("h6")); } + + #[test] + fn test_get_implicit_role() { + let aria_roles = AriaRoles {}; + + // No attributes + let implicit_role = aria_roles + .get_implicit_role("button", &HashMap::new()) + .unwrap(); + assert_eq!(implicit_role.type_name(), "rome_aria::roles::ButtonRole"); + + // + let mut attributes = HashMap::new(); + attributes.insert("type".to_string(), vec!["search".to_string()]); + let implicit_role = aria_roles.get_implicit_role("input", &attributes).unwrap(); + assert_eq!(implicit_role.type_name(), "rome_aria::roles::SearchboxRole"); + + // +
+
+ {/* Needs to check the ancestors: */} +
+ bar + + +
    +
      + + +
    • + + {/* Needs to check the ancestors: */} + + + + + {/* Needs to check the ancestors: */} + +
      + + +; diff --git a/crates/rome_js_analyze/tests/specs/nursery/noRedundantRoles/invalid.jsx.snap b/crates/rome_js_analyze/tests/specs/nursery/noRedundantRoles/invalid.jsx.snap new file mode 100644 index 00000000000..ef86be5e939 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noRedundantRoles/invalid.jsx.snap @@ -0,0 +1,557 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +expression: invalid.jsx +--- +# Input +```js +<> +
      + +

      + title +

      +

      title

      +

      + + +
      +
      + {/* Needs to check the ancestors: */} +
      + bar + + +
        +
          + + +
        • + + {/* Needs to check the ancestors: */} + + + + + {/* Needs to check the ancestors: */} + +
          + + +; + +``` + +# Diagnostics +``` +invalid.jsx:2:16 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'article' on the 'article' element is redundant. + + 1 │ <> + > 2 │
          + │ ^^^^^^^^^ + 3 │ + 4 │

          + + i Suggested fix: Remove the role attribute. + + 2 │ → + │ -------------- + +``` + +``` +invalid.jsx:3:15 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'button' on the 'button' element is redundant. + + 1 │ <> + 2 │
          + > 3 │ + │ ^^^^^^^^ + 4 │

          + 5 │ title + + i Suggested fix: Remove the role attribute. + + 3 │ → + │ ------------- + +``` + +``` +invalid.jsx:4:11 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'heading' on the 'h1' element is redundant. + + 2 │
          + 3 │ + > 4 │

          + │ ^^^^^^^^^ + 5 │ title + 6 │

          + + i Suggested fix: Remove the role attribute. + + 4 │ → + │ --------------- + +``` + +``` +invalid.jsx:7:11 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'heading' on the 'h1' element is redundant. + + 5 │ title + 6 │

          + > 7 │

          title

          + │ ^^^^^^^^^ + 8 │

          + 9 │ + + i Suggested fix: Remove the role attribute. + + 7 │ → title

          + │ -------------- + +``` + +``` +invalid.jsx:8:11 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'heading' on the 'h2' element is redundant. + + 6 │ + 7 │

          title

          + > 8 │

          + │ ^^^^^^^^^^^ + 9 │ + 10 │ + + i Suggested fix: Remove the role attribute. + + 8 │ → + │ ---------------- + +``` + +``` +invalid.jsx:9:15 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'dialog' on the 'dialog' element is redundant. + + 7 │

          title

          + 8 │

          + > 9 │ + │ ^^^^^^^^ + 10 │ + 11 │
          + + i Suggested fix: Remove the role attribute. + + 9 │ → + │ ------------- + +``` + +``` +invalid.jsx:10:30 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'checkbox' on the 'input' element is redundant. + + 8 │

          + 9 │ + > 10 │ + │ ^^^^^^^^^^ + 11 │
          + 12 │
          + + i Suggested fix: Remove the role attribute. + + 10 │ → + │ ---------------- + +``` + +``` +invalid.jsx:11:15 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'figure' on the 'figure' element is redundant. + + 9 │ + 10 │ + > 11 │
          + │ ^^^^^^^^ + 12 │
          + 13 │ {/* Needs to check the ancestors: */} + + i Suggested fix: Remove the role attribute. + + 11 │ → + │ ------------- + +``` + +``` +invalid.jsx:12:13 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'form' on the 'form' element is redundant. + + 10 │ + 11 │
          + > 12 │
          + │ ^^^^^^ + 13 │ {/* Needs to check the ancestors: */} + 14 │
          + + i Suggested fix: Remove the role attribute. + + 12 │ → + │ ----------- + +``` + +``` +invalid.jsx:14:17 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'group' on the 'fieldset' element is redundant. + + 12 │
          + 13 │ {/* Needs to check the ancestors: */} + > 14 │
          + │ ^^^^^^^ + 15 │ bar + 16 │ + + i Suggested fix: Remove the role attribute. + + 14 │ → + │ ------------ + +``` + +``` +invalid.jsx:15:32 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'img' on the 'img' element is redundant. + + 13 │ {/* Needs to check the ancestors: */} + 14 │
          + > 15 │ bar + │ ^^^^^ + 16 │ + 17 │ + + i Suggested fix: Remove the role attribute. + + 15 │ → + │ ----------- + +``` + +``` +invalid.jsx:16:19 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'presentation' on the 'img' element is redundant. + + 14 │
          + 15 │ bar + > 16 │ + │ ^^^^^^^^^^^^^^ + 17 │ + 18 │
            + + i Suggested fix: Remove the role attribute. + + 16 │ → + │ ------------------- + +``` + +``` +invalid.jsx:17:19 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'link' on the 'a' element is redundant. + + 15 │ bar + 16 │ + > 17 │ + │ ^^^^^^ + 18 │
              + 19 │
                + + i Suggested fix: Remove the role attribute. + + 17 │ → + │ ----------- + +``` + +``` +invalid.jsx:18:11 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'list' on the 'ol' element is redundant. + + 16 │ + 17 │ + > 18 │
                  + │ ^^^^^^ + 19 │
                    + 20 │ + + i Suggested fix: Remove the role attribute. + + 18 │ → + │ ----------- + +``` + +``` +invalid.jsx:19:11 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'list' on the 'ul' element is redundant. + + 17 │ + 18 │
                      + > 19 │
                        + │ ^^^^^^ + 20 │ + 21 │ + + i Suggested fix: Remove the role attribute. + + 19 │ → + │ ----------- + +``` + +``` +invalid.jsx:20:27 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'combobox' on the 'select' element is redundant. + + 18 │
                          + 19 │
                            + > 20 │ + │ ^^^^^^^^^^ + 21 │ + 22 │
                          • + + i Suggested fix: Remove the role attribute. + + 20 │ → + │ --------------- + +``` + +``` +invalid.jsx:21:45 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'listbox' on the 'select' element is redundant. + + 19 │
                              + 20 │ + > 21 │ + │ ^^^^^^^^^ + 22 │
                            • + 23 │ + + i Suggested fix: Remove the role attribute. + + 21 │ → + │ -------------- + +``` + +``` +invalid.jsx:22:11 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'listitem' on the 'li' element is redundant. + + 20 │ + 21 │ + > 22 │
                            • + │ ^^^^^^^^^^ + 23 │ + 24 │ {/* Needs to check the ancestors: */} + + i Suggested fix: Remove the role attribute. + + 22 │ → + │ --------------- + +``` + +``` +invalid.jsx:23:12 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'navigation' on the 'nav' element is redundant. + + 21 │ + 22 │
                            • + > 23 │ + │ ^^^^^^^^^^^^ + 24 │ {/* Needs to check the ancestors: */} + 25 │ + + i Suggested fix: Remove the role attribute. + + 23 │ → + │ ----------------- + +``` + +``` +invalid.jsx:25:11 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'row' on the 'tr' element is redundant. + + 23 │ + 24 │ {/* Needs to check the ancestors: */} + > 25 │ + │ ^^^^^ + 26 │ + 27 │ + + i Suggested fix: Remove the role attribute. + + 25 │ → + │ ---------- + +``` + +``` +invalid.jsx:26:14 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'rowgroup' on the 'tbody' element is redundant. + + 24 │ {/* Needs to check the ancestors: */} + 25 │ + > 26 │ + │ ^^^^^^^^^^ + 27 │ + 28 │ + + i Suggested fix: Remove the role attribute. + + 26 │ → + │ --------------- + +``` + +``` +invalid.jsx:27:14 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'rowgroup' on the 'tfoot' element is redundant. + + 25 │ + 26 │ + > 27 │ + │ ^^^^^^^^^^ + 28 │ + 29 │ {/* Needs to check the ancestors: */} + + i Suggested fix: Remove the role attribute. + + 27 │ → + │ --------------- + +``` + +``` +invalid.jsx:28:14 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'rowgroup' on the 'thead' element is redundant. + + 26 │ + 27 │ + > 28 │ + │ ^^^^^^^^^^ + 29 │ {/* Needs to check the ancestors: */} + 30 │ + + i Suggested fix: Remove the role attribute. + + 28 │ → + │ --------------- + +``` + +``` +invalid.jsx:30:28 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'searchbox' on the 'input' element is redundant. + + 28 │ + 29 │ {/* Needs to check the ancestors: */} + > 30 │ + │ ^^^^^^^^^^^ + 31 │
                              + 32 │ + + i Suggested fix: Remove the role attribute. + + 30 │ → + │ ----------------- + +``` + +``` +invalid.jsx:31:14 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'table' on the 'table' element is redundant. + + 29 │ {/* Needs to check the ancestors: */} + 30 │ + > 31 │
                              + │ ^^^^^^^ + 32 │ + 33 │ + + i Suggested fix: Remove the role attribute. + + 31 │ → + │ ------------ + +``` + +``` +invalid.jsx:32:17 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'textbox' on the 'textarea' element is redundant. + + 30 │ + 31 │
                              + > 32 │ + │ ^^^^^^^^^ + 33 │ + 34 │ ; + + i Suggested fix: Remove the role attribute. + + 32 │ → + │ -------------- + +``` + +``` +invalid.jsx:33:26 lint/nursery/noRedundantRoles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Using the role attribute 'textbox' on the 'input' element is redundant. + + 31 │
                              + 32 │ + > 33 │ + │ ^^^^^^^^^ + 34 │ ; + 35 │ + + i Suggested fix: Remove the role attribute. + + 33 │ → + │ --------------- + +``` + + diff --git a/crates/rome_js_analyze/tests/specs/nursery/noRedundantRoles/valid.jsx b/crates/rome_js_analyze/tests/specs/nursery/noRedundantRoles/valid.jsx new file mode 100644 index 00000000000..86e91b3fdab --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noRedundantRoles/valid.jsx @@ -0,0 +1,5 @@ +<> +
                              + + +; diff --git a/crates/rome_js_analyze/tests/specs/nursery/noRedundantRoles/valid.jsx.snap b/crates/rome_js_analyze/tests/specs/nursery/noRedundantRoles/valid.jsx.snap new file mode 100644 index 00000000000..eb169487b0b --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noRedundantRoles/valid.jsx.snap @@ -0,0 +1,15 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +expression: valid.jsx +--- +# Input +```js +<> +
                              + + +; + +``` + + diff --git a/crates/rome_service/src/configuration/linter/rules.rs b/crates/rome_service/src/configuration/linter/rules.rs index 80287c85815..103b7d16a8f 100644 --- a/crates/rome_service/src/configuration/linter/rules.rs +++ b/crates/rome_service/src/configuration/linter/rules.rs @@ -1253,6 +1253,9 @@ pub struct Nursery { #[doc = "Enforce img alt prop does not contain the word \"image\", \"picture\", or \"photo\"."] #[serde(skip_serializing_if = "Option::is_none")] pub no_redundant_alt: Option, + #[doc = "Enforce explicit role property is not the same as implicit/default role property on an element."] + #[serde(skip_serializing_if = "Option::is_none")] + pub no_redundant_roles: Option, #[doc = "This rule allows you to specify global variable names that you don’t want to use in your application."] #[serde(skip_serializing_if = "Option::is_none")] pub no_restricted_globals: Option, @@ -1328,7 +1331,7 @@ pub struct Nursery { } impl Nursery { const GROUP_NAME: &'static str = "nursery"; - pub(crate) const GROUP_RULES: [&'static str; 46] = [ + pub(crate) const GROUP_RULES: [&'static str; 47] = [ "noAssignInExpressions", "noBannedTypes", "noClassAssign", @@ -1351,6 +1354,7 @@ impl Nursery { "noPrototypeBuiltins", "noRedeclare", "noRedundantAlt", + "noRedundantRoles", "noRestrictedGlobals", "noSelfAssign", "noSelfCompare", @@ -1376,7 +1380,7 @@ impl Nursery { "useValidLang", "useYield", ]; - const RECOMMENDED_RULES: [&'static str; 38] = [ + const RECOMMENDED_RULES: [&'static str; 39] = [ "noAssignInExpressions", "noBannedTypes", "noClassAssign", @@ -1396,6 +1400,7 @@ impl Nursery { "noParameterAssign", "noRedeclare", "noRedundantAlt", + "noRedundantRoles", "noSelfAssign", "noSelfCompare", "noSvgWithoutTitle", @@ -1416,7 +1421,7 @@ impl Nursery { "useValidLang", "useYield", ]; - const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 38] = [ + const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 39] = [ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2]), @@ -1436,7 +1441,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26]), @@ -1447,16 +1452,17 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46]), ]; - const ALL_RULES_AS_FILTERS: [RuleFilter<'static>; 46] = [ + const ALL_RULES_AS_FILTERS: [RuleFilter<'static>; 47] = [ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2]), @@ -1503,6 +1509,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46]), ]; pub(crate) fn is_recommended(&self) -> bool { !matches!(self.recommended, Some(false)) } pub(crate) const fn is_not_recommended(&self) -> bool { @@ -1622,126 +1629,131 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } } - if let Some(rule) = self.no_restricted_globals.as_ref() { + if let Some(rule) = self.no_redundant_roles.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } } - if let Some(rule) = self.no_self_assign.as_ref() { + if let Some(rule) = self.no_restricted_globals.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } } - if let Some(rule) = self.no_self_compare.as_ref() { + if let Some(rule) = self.no_self_assign.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } } - if let Some(rule) = self.no_svg_without_title.as_ref() { + if let Some(rule) = self.no_self_compare.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } } - if let Some(rule) = self.no_switch_declarations.as_ref() { + if let Some(rule) = self.no_svg_without_title.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } } - if let Some(rule) = self.no_unreachable_super.as_ref() { + if let Some(rule) = self.no_switch_declarations.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } } - if let Some(rule) = self.no_unsafe_optional_chaining.as_ref() { + if let Some(rule) = self.no_unreachable_super.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); } } - if let Some(rule) = self.no_unused_labels.as_ref() { + if let Some(rule) = self.no_unsafe_optional_chaining.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } } - if let Some(rule) = self.no_useless_catch.as_ref() { + if let Some(rule) = self.no_unused_labels.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); } } - if let Some(rule) = self.no_useless_rename.as_ref() { + if let Some(rule) = self.no_useless_catch.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } } - if let Some(rule) = self.no_useless_switch_case.as_ref() { + if let Some(rule) = self.no_useless_rename.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } } - if let Some(rule) = self.no_with.as_ref() { + if let Some(rule) = self.no_useless_switch_case.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); } } - if let Some(rule) = self.use_aria_prop_types.as_ref() { + if let Some(rule) = self.no_with.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34])); } } - if let Some(rule) = self.use_aria_props_for_role.as_ref() { + if let Some(rule) = self.use_aria_prop_types.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35])); } } - if let Some(rule) = self.use_camel_case.as_ref() { + if let Some(rule) = self.use_aria_props_for_role.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); } } - if let Some(rule) = self.use_exhaustive_dependencies.as_ref() { + if let Some(rule) = self.use_camel_case.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } } - if let Some(rule) = self.use_hook_at_top_level.as_ref() { + if let Some(rule) = self.use_exhaustive_dependencies.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); } } - if let Some(rule) = self.use_iframe_title.as_ref() { + if let Some(rule) = self.use_hook_at_top_level.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); } } - if let Some(rule) = self.use_is_nan.as_ref() { + if let Some(rule) = self.use_iframe_title.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); } } - if let Some(rule) = self.use_media_caption.as_ref() { + if let Some(rule) = self.use_is_nan.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); } } - if let Some(rule) = self.use_namespace_keyword.as_ref() { + if let Some(rule) = self.use_media_caption.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42])); } } - if let Some(rule) = self.use_valid_aria_props.as_ref() { + if let Some(rule) = self.use_namespace_keyword.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43])); } } - if let Some(rule) = self.use_valid_lang.as_ref() { + if let Some(rule) = self.use_valid_aria_props.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44])); } } - if let Some(rule) = self.use_yield.as_ref() { + if let Some(rule) = self.use_valid_lang.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); } } + if let Some(rule) = self.use_yield.as_ref() { + if rule.is_enabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> IndexSet { @@ -1856,126 +1868,131 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } } - if let Some(rule) = self.no_restricted_globals.as_ref() { + if let Some(rule) = self.no_redundant_roles.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } } - if let Some(rule) = self.no_self_assign.as_ref() { + if let Some(rule) = self.no_restricted_globals.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } } - if let Some(rule) = self.no_self_compare.as_ref() { + if let Some(rule) = self.no_self_assign.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } } - if let Some(rule) = self.no_svg_without_title.as_ref() { + if let Some(rule) = self.no_self_compare.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } } - if let Some(rule) = self.no_switch_declarations.as_ref() { + if let Some(rule) = self.no_svg_without_title.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } } - if let Some(rule) = self.no_unreachable_super.as_ref() { + if let Some(rule) = self.no_switch_declarations.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } } - if let Some(rule) = self.no_unsafe_optional_chaining.as_ref() { + if let Some(rule) = self.no_unreachable_super.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); } } - if let Some(rule) = self.no_unused_labels.as_ref() { + if let Some(rule) = self.no_unsafe_optional_chaining.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } } - if let Some(rule) = self.no_useless_catch.as_ref() { + if let Some(rule) = self.no_unused_labels.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30])); } } - if let Some(rule) = self.no_useless_rename.as_ref() { + if let Some(rule) = self.no_useless_catch.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } } - if let Some(rule) = self.no_useless_switch_case.as_ref() { + if let Some(rule) = self.no_useless_rename.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } } - if let Some(rule) = self.no_with.as_ref() { + if let Some(rule) = self.no_useless_switch_case.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); } } - if let Some(rule) = self.use_aria_prop_types.as_ref() { + if let Some(rule) = self.no_with.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34])); } } - if let Some(rule) = self.use_aria_props_for_role.as_ref() { + if let Some(rule) = self.use_aria_prop_types.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35])); } } - if let Some(rule) = self.use_camel_case.as_ref() { + if let Some(rule) = self.use_aria_props_for_role.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36])); } } - if let Some(rule) = self.use_exhaustive_dependencies.as_ref() { + if let Some(rule) = self.use_camel_case.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37])); } } - if let Some(rule) = self.use_hook_at_top_level.as_ref() { + if let Some(rule) = self.use_exhaustive_dependencies.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38])); } } - if let Some(rule) = self.use_iframe_title.as_ref() { + if let Some(rule) = self.use_hook_at_top_level.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39])); } } - if let Some(rule) = self.use_is_nan.as_ref() { + if let Some(rule) = self.use_iframe_title.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40])); } } - if let Some(rule) = self.use_media_caption.as_ref() { + if let Some(rule) = self.use_is_nan.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41])); } } - if let Some(rule) = self.use_namespace_keyword.as_ref() { + if let Some(rule) = self.use_media_caption.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42])); } } - if let Some(rule) = self.use_valid_aria_props.as_ref() { + if let Some(rule) = self.use_namespace_keyword.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43])); } } - if let Some(rule) = self.use_valid_lang.as_ref() { + if let Some(rule) = self.use_valid_aria_props.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44])); } } - if let Some(rule) = self.use_yield.as_ref() { + if let Some(rule) = self.use_valid_lang.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45])); } } + if let Some(rule) = self.use_yield.as_ref() { + if rule.is_disabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -1984,10 +2001,10 @@ impl Nursery { pub(crate) fn is_recommended_rule(rule_name: &str) -> bool { Self::RECOMMENDED_RULES.contains(&rule_name) } - pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 38] { + pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 39] { Self::RECOMMENDED_RULES_AS_FILTERS } - pub(crate) fn all_rules_as_filters() -> [RuleFilter<'static>; 46] { Self::ALL_RULES_AS_FILTERS } + pub(crate) fn all_rules_as_filters() -> [RuleFilter<'static>; 47] { Self::ALL_RULES_AS_FILTERS } #[doc = r" Select preset rules"] pub(crate) fn collect_preset_rules( &self, @@ -2031,6 +2048,7 @@ impl Nursery { "noPrototypeBuiltins" => self.no_prototype_builtins.as_ref(), "noRedeclare" => self.no_redeclare.as_ref(), "noRedundantAlt" => self.no_redundant_alt.as_ref(), + "noRedundantRoles" => self.no_redundant_roles.as_ref(), "noRestrictedGlobals" => self.no_restricted_globals.as_ref(), "noSelfAssign" => self.no_self_assign.as_ref(), "noSelfCompare" => self.no_self_compare.as_ref(), diff --git a/crates/rome_service/src/configuration/parse/json/rules.rs b/crates/rome_service/src/configuration/parse/json/rules.rs index 0dcbea5e1f9..01639921235 100644 --- a/crates/rome_service/src/configuration/parse/json/rules.rs +++ b/crates/rome_service/src/configuration/parse/json/rules.rs @@ -933,6 +933,7 @@ impl VisitNode for Nursery { "noPrototypeBuiltins", "noRedeclare", "noRedundantAlt", + "noRedundantRoles", "noRestrictedGlobals", "noSelfAssign", "noSelfCompare", @@ -1372,6 +1373,24 @@ impl VisitNode for Nursery { )); } }, + "noRedundantRoles" => match value { + AnyJsonValue::JsonStringValue(_) => { + let mut configuration = RuleConfiguration::default(); + self.map_to_known_string(&value, name_text, &mut configuration, diagnostics)?; + self.no_redundant_roles = Some(configuration); + } + AnyJsonValue::JsonObjectValue(_) => { + let mut configuration = RuleConfiguration::default(); + self.map_to_object(&value, name_text, &mut configuration, diagnostics)?; + self.no_redundant_roles = Some(configuration); + } + _ => { + diagnostics.push(DeserializationDiagnostic::new_incorrect_type( + "object or string", + value.range(), + )); + } + }, "noRestrictedGlobals" => match value { AnyJsonValue::JsonStringValue(_) => { let mut configuration = RuleConfiguration::default(); diff --git a/editors/vscode/configuration_schema.json b/editors/vscode/configuration_schema.json index 594a26d4982..e3c913bd017 100644 --- a/editors/vscode/configuration_schema.json +++ b/editors/vscode/configuration_schema.json @@ -636,6 +636,13 @@ { "type": "null" } ] }, + "noRedundantRoles": { + "description": "Enforce explicit role property is not the same as implicit/default role property on an element.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "noRestrictedGlobals": { "description": "This rule allows you to specify global variable names that you don’t want to use in your application.", "anyOf": [ diff --git a/npm/backend-jsonrpc/src/workspace.ts b/npm/backend-jsonrpc/src/workspace.ts index afedb99b989..4547c432f75 100644 --- a/npm/backend-jsonrpc/src/workspace.ts +++ b/npm/backend-jsonrpc/src/workspace.ts @@ -444,6 +444,10 @@ export interface Nursery { * Enforce img alt prop does not contain the word "image", "picture", or "photo". */ noRedundantAlt?: RuleConfiguration; + /** + * Enforce explicit role property is not the same as implicit/default role property on an element. + */ + noRedundantRoles?: RuleConfiguration; /** * This rule allows you to specify global variable names that you don’t want to use in your application. */ @@ -943,6 +947,7 @@ export type Category = | "lint/nursery/noConfusingArrow" | "lint/nursery/noRedeclare" | "lint/nursery/useNamespaceKeyword" + | "lint/nursery/noRedundantRoles" | "lint/performance/noDelete" | "lint/security/noDangerouslySetInnerHtml" | "lint/security/noDangerouslySetInnerHtmlWithChildren" diff --git a/npm/rome/configuration_schema.json b/npm/rome/configuration_schema.json index 594a26d4982..e3c913bd017 100644 --- a/npm/rome/configuration_schema.json +++ b/npm/rome/configuration_schema.json @@ -636,6 +636,13 @@ { "type": "null" } ] }, + "noRedundantRoles": { + "description": "Enforce explicit role property is not the same as implicit/default role property on an element.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "noRestrictedGlobals": { "description": "This rule allows you to specify global variable names that you don’t want to use in your application.", "anyOf": [ diff --git a/website/src/pages/lint/rules/index.mdx b/website/src/pages/lint/rules/index.mdx index 153e7f6c2b3..abb525af7e1 100644 --- a/website/src/pages/lint/rules/index.mdx +++ b/website/src/pages/lint/rules/index.mdx @@ -768,6 +768,12 @@ Disallow variable, function, class, and type redeclarations in the same scope. Enforce img alt prop does not contain the word "image", "picture", or "photo".
                              +

                              + noRedundantRoles +

                              +Enforce explicit role property is not the same as implicit/default role property on an element. +
                              +

                              noRestrictedGlobals

                              diff --git a/website/src/pages/lint/rules/noRedundantRoles.md b/website/src/pages/lint/rules/noRedundantRoles.md new file mode 100644 index 00000000000..a7816d0fee0 --- /dev/null +++ b/website/src/pages/lint/rules/noRedundantRoles.md @@ -0,0 +1,87 @@ +--- +title: Lint Rule noRedundantRoles +parent: lint/rules/index +--- + +# noRedundantRoles (since vnext) + +Enforce explicit `role` property is not the same as implicit/default role property on an element. + +ESLint (eslint-plugin-jsx-a11y) Equivalent: [no-redundant-roles](https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-redundant-roles.md) + +## Examples + +### Invalid + +```jsx +
                              +``` + +
                              nursery/noRedundantRoles.js:1:15 lint/nursery/noRedundantRoles  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━
                              +
                              +   Using the role attribute 'article' on the 'article' element is redundant.
                              +  
                              +  > 1 │ <article role='article'></article>
                              +                 ^^^^^^^^^
                              +    2 │ 
                              +  
                              +   Suggested fix: Remove the role attribute.
                              +  
                              +    1 │ <article·role='article'></article>
                              +           --------------           
                              +
                              + +```jsx + +``` + +
                              nursery/noRedundantRoles.js:1:14 lint/nursery/noRedundantRoles  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━
                              +
                              +   Using the role attribute 'button' on the 'button' element is redundant.
                              +  
                              +  > 1 │ <button role='button'></button>
                              +                ^^^^^^^^
                              +    2 │ 
                              +  
                              +   Suggested fix: Remove the role attribute.
                              +  
                              +    1 │ <button·role='button'></button>
                              +          -------------          
                              +
                              + +```jsx +

                              title

                              +``` + +
                              nursery/noRedundantRoles.js:1:10 lint/nursery/noRedundantRoles  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━
                              +
                              +   Using the role attribute 'heading' on the 'h1' element is redundant.
                              +  
                              +  > 1 │ <h1 role='heading' aria-level='1'>title</h1>
                              +            ^^^^^^^^^
                              +    2 │ 
                              +  
                              +   Suggested fix: Remove the role attribute.
                              +  
                              +    1 │ <h1·role='heading'·aria-level='1'>title</h1>
                              +      ---------------                         
                              +
                              + +## Valid + +```jsx +
                              +``` + +```jsx + +``` + +```jsx + +``` + +## Related links + +- [Disable a rule](/linter/#disable-a-lint-rule) +- [Rule options](/linter/#rule-options)