From 17b07507ab27802432861c72205a9c3abfd1a234 Mon Sep 17 00:00:00 2001 From: vasu Date: Tue, 31 Oct 2023 14:32:55 +0530 Subject: [PATCH] feat(lint): new UseValidAriaRoles --- .../src/aria_analyzers/nursery.rs | 2 + .../nursery/use_valid_aria_role.rs | 199 ++++++++++++++ crates/biome_js_analyze/src/options.rs | 16 ++ .../nursery/useValidAriaRole/invalid.jsx | 12 + .../nursery/useValidAriaRole/invalid.jsx.snap | 242 ++++++++++++++++++ .../specs/nursery/useValidAriaRole/valid.jsx | 14 + .../nursery/useValidAriaRole/valid.jsx.snap | 23 ++ .../src/configuration/linter/rules.rs | 10 + .../src/configuration/parse/json/rules.rs | 6 + editors/vscode/configuration_schema.json | 23 ++ .../@biomejs/backend-jsonrpc/src/workspace.ts | 12 +- .../@biomejs/biome/configuration_schema.json | 23 ++ .../src/content/docs/linter/rules/index.mdx | 1 + .../docs/linter/rules/use-valid-aria-role.md | 138 ++++++++++ 14 files changed, 720 insertions(+), 1 deletion(-) create mode 100644 crates/biome_js_analyze/src/aria_analyzers/nursery/use_valid_aria_role.rs create mode 100644 crates/biome_js_analyze/tests/specs/nursery/useValidAriaRole/invalid.jsx create mode 100644 crates/biome_js_analyze/tests/specs/nursery/useValidAriaRole/invalid.jsx.snap create mode 100644 crates/biome_js_analyze/tests/specs/nursery/useValidAriaRole/valid.jsx create mode 100644 crates/biome_js_analyze/tests/specs/nursery/useValidAriaRole/valid.jsx.snap create mode 100644 website/src/content/docs/linter/rules/use-valid-aria-role.md diff --git a/crates/biome_js_analyze/src/aria_analyzers/nursery.rs b/crates/biome_js_analyze/src/aria_analyzers/nursery.rs index 8858532021fb..5af20e3873c2 100644 --- a/crates/biome_js_analyze/src/aria_analyzers/nursery.rs +++ b/crates/biome_js_analyze/src/aria_analyzers/nursery.rs @@ -4,6 +4,7 @@ use biome_analyze::declare_group; pub(crate) mod no_interactive_element_to_noninteractive_role; pub(crate) mod use_aria_activedescendant_with_tabindex; +pub(crate) mod use_valid_aria_role; declare_group! { pub (crate) Nursery { @@ -11,6 +12,7 @@ declare_group! { rules : [ self :: no_interactive_element_to_noninteractive_role :: NoInteractiveElementToNoninteractiveRole , self :: use_aria_activedescendant_with_tabindex :: UseAriaActivedescendantWithTabindex , + self :: use_valid_aria_role :: UseValidAriaRole , ] } } diff --git a/crates/biome_js_analyze/src/aria_analyzers/nursery/use_valid_aria_role.rs b/crates/biome_js_analyze/src/aria_analyzers/nursery/use_valid_aria_role.rs new file mode 100644 index 000000000000..2eccb91d7e33 --- /dev/null +++ b/crates/biome_js_analyze/src/aria_analyzers/nursery/use_valid_aria_role.rs @@ -0,0 +1,199 @@ +use std::str::FromStr; + +use crate::{aria_services::Aria, JsRuleAction}; +use biome_analyze::{ + context::RuleContext, declare_rule, ActionCategory, FixKind, Rule, RuleDiagnostic, +}; +use biome_console::markup; +use biome_deserialize::json::{report_unknown_map_key, VisitJsonNode}; +use biome_deserialize::{DeserializationDiagnostic, VisitNode}; +use biome_diagnostics::Applicability; +use biome_js_syntax::jsx_ext::AnyJsxElement; +use biome_json_syntax::JsonLanguage; +use biome_rowan::{AstNode, BatchMutationExt, SyntaxNode}; +use bpaf::Bpaf; +use serde::{Deserialize, Serialize}; + +declare_rule! { + /// Elements with ARIA roles must use a valid, non-abstract ARIA role. + /// + /// Source: https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/aria-role.md + /// + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + ///
+ /// ``` + /// + /// ```js,expect_diagnostic + ///
+ /// ``` + /// + /// ```js,expect_diagnostic + ///
+ /// ``` + /// + /// ```js,expect_diagnostic + /// + /// ``` + /// + /// ### Valid + /// + /// ```js + /// <> + ///
+ ///
+ ///
+ /// + /// ``` + /// + /// ### Options + /// + /// ```json + /// { + /// "//": "...", + /// "options": { + /// "allowInvalidRoles": ["invalid", "text"], + /// "nonIgnoreDom": true + /// } + /// } + /// ``` + /// + /// ## Accessibility guidelines + /// - [WCAG 4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) + /// + /// ## Resources + /// - [Chrome Audit Rules, AX_ARIA_01](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#ax_aria_01) + /// - [DPUB-ARIA roles](https://www.w3.org/TR/dpub-aria-1.0/) + /// - [MDN: Using ARIA: Roles, states, and properties](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques) + /// + pub(crate) UseValidAriaRole { + version: "next", + name: "useValidAriaRole", + recommended: true, + fix_kind: FixKind::Unsafe, + } +} + +#[derive(Default, Deserialize, Serialize, Eq, PartialEq, Debug, Clone, Bpaf)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct ValidAriaRoleOptions { + #[bpaf(hide, argument::("roles"), many)] + allowed_invalid_roles: Vec, + #[bpaf(hide)] + ignore_non_dom: bool, +} + +impl ValidAriaRoleOptions { + pub const ALLOWED_KEYS: &'static [&'static str] = &["allowInvalidRoles", "ignoreNonDom"]; +} + +impl FromStr for ValidAriaRoleOptions { + type Err = (); + + fn from_str(_s: &str) -> Result { + Ok(ValidAriaRoleOptions::default()) + } +} + +impl VisitNode for ValidAriaRoleOptions { + fn visit_map( + &mut self, + key: &SyntaxNode, + value: &SyntaxNode, + diagnostics: &mut Vec, + ) -> Option<()> { + let (name, value) = self.get_key_and_value(key, value)?; + let name_text = name.inner_string_text().ok()?; + let name_text = name_text.text(); + match name_text { + "allowInvalidRoles" => { + self.allowed_invalid_roles = + self.map_to_array_of_strings(&value, name_text, diagnostics)?; + } + "ignoreNonDom" => { + self.ignore_non_dom = self.map_to_boolean(&value, name_text, diagnostics)?; + } + _ => { + report_unknown_map_key(&name, Self::ALLOWED_KEYS, diagnostics); + } + } + + Some(()) + } +} + +impl Rule for UseValidAriaRole { + type Query = Aria; + type State = (); + type Signals = Option; + type Options = ValidAriaRoleOptions; + + fn run(ctx: &RuleContext) -> Self::Signals { + let node = ctx.query(); + let options = ctx.options(); + let aria_roles = ctx.aria_roles(); + + let ignore_non_dom = options.ignore_non_dom; + let allowed_invalid_roles = &options.allowed_invalid_roles; + + if ignore_non_dom && node.is_custom_component() { + return None; + } + + let role_attribute = node.find_attribute_by_name("role")?; + + let role_attribute_static_value = role_attribute.as_static_value()?; + let role_attribute_value = role_attribute_static_value.text(); + let mut role_attribute_value = role_attribute_value.split(' '); + + let is_valid = role_attribute_value.all(|val| { + let role_data = aria_roles.get_role(val); + allowed_invalid_roles.contains(&val.to_string()) || role_data.is_some() + }); + + if is_valid { + return None; + } + + Some(()) + } + + fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { + let node = ctx.query(); + + Some( + RuleDiagnostic::new( + rule_category!(), + node.range(), + markup! { + "Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role." + }, + ) + .note(markup! { + "Check ""WAI-ARIA"" for valid roles or provide options accordingly." + }) + ) + } + + fn action(ctx: &RuleContext, _: &Self::State) -> Option { + let node = ctx.query(); + let mut mutation = ctx.root().begin(); + let role_attribute = node.find_attribute_by_name("role")?; + + mutation.remove_node(role_attribute); + + Some(JsRuleAction { + category: ActionCategory::QuickFix, + applicability: Applicability::MaybeIncorrect, + message: + markup! { "Remove the invalid ""role"" attribute.\n Check the list of all ""valid"" role attributes." } + .to_owned(), + mutation, + }) + } +} diff --git a/crates/biome_js_analyze/src/options.rs b/crates/biome_js_analyze/src/options.rs index 934a539888a4..8760d749954f 100644 --- a/crates/biome_js_analyze/src/options.rs +++ b/crates/biome_js_analyze/src/options.rs @@ -3,6 +3,9 @@ use crate::analyzers::complexity::no_excessive_cognitive_complexity::{ complexity_options, ComplexityOptions, }; +use crate::aria_analyzers::nursery::use_valid_aria_role::{ + valid_aria_role_options, ValidAriaRoleOptions, +}; use crate::semantic_analyzers::correctness::use_exhaustive_dependencies::{ hooks_options, HooksOptions, }; @@ -34,6 +37,8 @@ pub enum PossibleOptions { NamingConvention(#[bpaf(external(naming_convention_options), hide)] NamingConventionOptions), /// Options for `noRestrictedGlobals` rule RestrictedGlobals(#[bpaf(external(restricted_globals_options), hide)] RestrictedGlobalsOptions), + /// Options for `useValidAriaRole` rule + ValidAriaRole(#[bpaf(external(valid_aria_role_options), hide)] ValidAriaRoleOptions), } // Required by [Bpaf]. @@ -60,6 +65,7 @@ impl PossibleOptions { "useNamingConvention" => { Some(Self::NamingConvention(NamingConventionOptions::default())) } + "useValidAriaRole" => Some(Self::ValidAriaRole(ValidAriaRoleOptions::default())), _ => None, } } @@ -94,6 +100,13 @@ impl PossibleOptions { }; RuleOptions::new(options) } + "useValidAriaRole" => { + let options = match self { + PossibleOptions::ValidAriaRole(options) => options.clone(), + _ => ValidAriaRoleOptions::default(), + }; + RuleOptions::new(options) + } // TODO: review error _ => panic!("This rule {:?} doesn't have options", rule_key), } @@ -120,6 +133,9 @@ impl VisitNode for PossibleOptions { PossibleOptions::RestrictedGlobals(options) => { options.visit_map(key, value, diagnostics)?; } + PossibleOptions::ValidAriaRole(options) => { + options.visit_map(key, value, diagnostics)?; + } } Some(()) } diff --git a/crates/biome_js_analyze/tests/specs/nursery/useValidAriaRole/invalid.jsx b/crates/biome_js_analyze/tests/specs/nursery/useValidAriaRole/invalid.jsx new file mode 100644 index 000000000000..3ab8351b3961 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useValidAriaRole/invalid.jsx @@ -0,0 +1,12 @@ +<> +
+
+
+ + +
+
+
+
+ + diff --git a/crates/biome_js_analyze/tests/specs/nursery/useValidAriaRole/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/useValidAriaRole/invalid.jsx.snap new file mode 100644 index 000000000000..b049fd0ef2ca --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useValidAriaRole/invalid.jsx.snap @@ -0,0 +1,242 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.jsx +--- +# Input +```js +<> +
+
+
+ + +
+
+
+
+ + + +``` + +# Diagnostics +``` +invalid.jsx:2:3 lint/nursery/useValidAriaRole FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role. + + 1 │ <> + > 2 │
+ │ ^^^^^^^^^^^^^^^^^^ + 3 │
+ 4 │
+ + i Check WAI-ARIA for valid roles or provide options accordingly. + + i Unsafe fix: Remove the invalid role attribute. + Check the list of all valid role attributes. + + 2 │ ··
+ │ ------------ + +``` + +``` +invalid.jsx:3:3 lint/nursery/useValidAriaRole FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role. + + 1 │ <> + 2 │
+ > 3 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^ + 4 │
+ 5 │ + + i Check WAI-ARIA for valid roles or provide options accordingly. + + i Unsafe fix: Remove the invalid role attribute. + Check the list of all valid role attributes. + + 3 │ ··
+ │ ----------------- + +``` + +``` +invalid.jsx:4:3 lint/nursery/useValidAriaRole FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role. + + 2 │
+ 3 │
+ > 4 │
+ │ ^^^^^^^^^^^^^ + 5 │ + 6 │ + + i Check WAI-ARIA for valid roles or provide options accordingly. + + i Unsafe fix: Remove the invalid role attribute. + Check the list of all valid role attributes. + + 4 │ ·· + │ ------- + +``` + +``` +invalid.jsx:5:3 lint/nursery/useValidAriaRole FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role. + + 3 │
+ 4 │
+ > 5 │ + │ ^^^^^^^^^^^^^^^^^^^^^ + 6 │ + 7 │
+ + i Check WAI-ARIA for valid roles or provide options accordingly. + + i Unsafe fix: Remove the invalid role attribute. + Check the list of all valid role attributes. + + 5 │ ·· + │ -------------- + +``` + +``` +invalid.jsx:6:3 lint/nursery/useValidAriaRole FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role. + + 4 │
+ 5 │ + > 6 │ + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ + 7 │
+ 8 │
+ + i Check WAI-ARIA for valid roles or provide options accordingly. + + i Unsafe fix: Remove the invalid role attribute. + Check the list of all valid role attributes. + + 6 │ ·· + │ ------------------ + +``` + +``` +invalid.jsx:7:3 lint/nursery/useValidAriaRole FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role. + + 5 │ + 6 │ + > 7 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 8 │
+ 9 │
+ + i Check WAI-ARIA for valid roles or provide options accordingly. + + i Unsafe fix: Remove the invalid role attribute. + Check the list of all valid role attributes. + + 7 │ ·· + │ ---------------------------- + +``` + +``` +invalid.jsx:8:3 lint/nursery/useValidAriaRole FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role. + + 6 │ + 7 │
+ > 8 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 9 │
+ 10 │
+ + i Check WAI-ARIA for valid roles or provide options accordingly. + + i Unsafe fix: Remove the invalid role attribute. + Check the list of all valid role attributes. + + 8 │ ··
+ │ -------------------------- + +``` + +``` +invalid.jsx:9:3 lint/nursery/useValidAriaRole FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role. + + 7 │
+ 8 │
+ > 9 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 10 │
+ 11 │ + + i Check WAI-ARIA for valid roles or provide options accordingly. + + i Unsafe fix: Remove the invalid role attribute. + Check the list of all valid role attributes. + + 9 │ ··
+ │ ------------------------- + +``` + +``` +invalid.jsx:10:3 lint/nursery/useValidAriaRole FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role. + + 8 │
+ 9 │
+ > 10 │
+ │ ^^^^^^^^^^^^^^^^^ + 11 │ + 12 │ + + i Check WAI-ARIA for valid roles or provide options accordingly. + + i Unsafe fix: Remove the invalid role attribute. + Check the list of all valid role attributes. + + 10 │ ··
+ │ ----------- + +``` + +``` +invalid.jsx:11:3 lint/nursery/useValidAriaRole FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role. + + 9 │
+ 10 │
+ > 11 │ + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ + 12 │ + 13 │ + + i Check WAI-ARIA for valid roles or provide options accordingly. + + i Unsafe fix: Remove the invalid role attribute. + Check the list of all valid role attributes. + + 11 │ ·· + │ ------------------ + +``` + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/useValidAriaRole/valid.jsx b/crates/biome_js_analyze/tests/specs/nursery/useValidAriaRole/valid.jsx new file mode 100644 index 000000000000..f35d8765546c --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useValidAriaRole/valid.jsx @@ -0,0 +1,14 @@ +<> +
+
+
+
+
+
+ + +
+
+
+
+ \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/useValidAriaRole/valid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/useValidAriaRole/valid.jsx.snap new file mode 100644 index 000000000000..062e7bd9f1ce --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useValidAriaRole/valid.jsx.snap @@ -0,0 +1,23 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid.jsx +--- +# Input +```js +<> +
+
+
+
+
+
+ + +
+
+
+
+ +``` + + diff --git a/crates/biome_service/src/configuration/linter/rules.rs b/crates/biome_service/src/configuration/linter/rules.rs index 7c7b9ab455fc..4f9023f32b30 100644 --- a/crates/biome_service/src/configuration/linter/rules.rs +++ b/crates/biome_service/src/configuration/linter/rules.rs @@ -2735,6 +2735,10 @@ pub struct Nursery { #[bpaf(long("use-shorthand-assign"), argument("on|off|warn"), optional, hide)] #[serde(skip_serializing_if = "Option::is_none")] pub use_shorthand_assign: Option, + #[doc = "Elements with ARIA roles must use a valid, non-abstract ARIA role."] + #[bpaf(long("use-valid-aria-role"), argument("on|off|warn"), optional, hide)] + #[serde(skip_serializing_if = "Option::is_none")] + pub use_valid_aria_role: Option, } impl MergeWith for Nursery { fn merge_with(&mut self, other: Nursery) { @@ -2804,6 +2808,9 @@ impl MergeWith for Nursery { if let Some(use_shorthand_assign) = other.use_shorthand_assign { self.use_shorthand_assign = Some(use_shorthand_assign); } + if let Some(use_valid_aria_role) = other.use_valid_aria_role { + self.use_valid_aria_role = Some(use_valid_aria_role); + } } fn merge_with_if_not_default(&mut self, other: Nursery) where @@ -2837,6 +2844,7 @@ impl Nursery { "useGroupedTypeImport", "useImportRestrictions", "useShorthandAssign", + "useValidAriaRole", ]; const RECOMMENDED_RULES: [&'static str; 9] = [ "noDuplicateJsonKeys", @@ -2848,6 +2856,7 @@ impl Nursery { "useAsConstAssertion", "useAwait", "useGroupedTypeImport", + "useValidAriaRole", ]; const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 9] = [ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1]), @@ -3161,6 +3170,7 @@ impl Nursery { "useGroupedTypeImport" => self.use_grouped_type_import.as_ref(), "useImportRestrictions" => self.use_import_restrictions.as_ref(), "useShorthandAssign" => self.use_shorthand_assign.as_ref(), + "useValidAriaRole" => self.use_valid_aria_role.as_ref(), _ => None, } } diff --git a/crates/biome_service/src/configuration/parse/json/rules.rs b/crates/biome_service/src/configuration/parse/json/rules.rs index 981960cc83fa..fb6988190cf5 100644 --- a/crates/biome_service/src/configuration/parse/json/rules.rs +++ b/crates/biome_service/src/configuration/parse/json/rules.rs @@ -918,6 +918,11 @@ impl VisitNode for Nursery { configuration.map_rule_configuration(&value, "useShorthandAssign", diagnostics)?; self.use_shorthand_assign = Some(configuration); } + "useValidAriaRole" => { + let mut configuration = RuleConfiguration::default(); + configuration.map_rule_configuration(&value, "useValidAriaRole", diagnostics)?; + self.use_valid_aria_role = Some(configuration); + } _ => { report_unknown_map_key( &name, @@ -944,6 +949,7 @@ impl VisitNode for Nursery { "useGroupedTypeImport", "useImportRestrictions", "useShorthandAssign", + "useValidAriaRole", ], diagnostics, ); diff --git a/editors/vscode/configuration_schema.json b/editors/vscode/configuration_schema.json index 049af3699807..29119f733043 100644 --- a/editors/vscode/configuration_schema.json +++ b/editors/vscode/configuration_schema.json @@ -1173,6 +1173,13 @@ { "$ref": "#/definitions/RuleConfiguration" }, { "type": "null" } ] + }, + "useValidAriaRole": { + "description": "Elements with ARIA roles must use a valid, non-abstract ARIA role.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] } } }, @@ -1359,6 +1366,10 @@ { "description": "Options for `noRestrictedGlobals` rule", "allOf": [{ "$ref": "#/definitions/RestrictedGlobalsOptions" }] + }, + { + "description": "Options for `useValidAriaRole` rule", + "allOf": [{ "$ref": "#/definitions/ValidAriaRoleOptions" }] } ] }, @@ -2010,6 +2021,18 @@ } ] }, + "ValidAriaRoleOptions": { + "type": "object", + "required": ["allowedInvalidRoles", "ignoreNonDom"], + "properties": { + "allowedInvalidRoles": { + "type": "array", + "items": { "type": "string" } + }, + "ignoreNonDom": { "type": "boolean" } + }, + "additionalProperties": false + }, "VcsClientKind": { "oneOf": [ { diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index c8623873e980..55f8561be43f 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -816,6 +816,10 @@ export interface Nursery { * Require assignment operator shorthand where possible. */ useShorthandAssign?: RuleConfiguration; + /** + * Elements with ARIA roles must use a valid, non-abstract ARIA role. + */ + useValidAriaRole?: RuleConfiguration; } /** * A list of rules that belong to this group @@ -1214,7 +1218,8 @@ export type PossibleOptions = | ComplexityOptions | HooksOptions | NamingConventionOptions - | RestrictedGlobalsOptions; + | RestrictedGlobalsOptions + | ValidAriaRoleOptions; /** * Options for the rule `noExcessiveCognitiveComplexity`. */ @@ -1255,6 +1260,10 @@ export interface RestrictedGlobalsOptions { */ deniedGlobals?: string[]; } +export interface ValidAriaRoleOptions { + allowedInvalidRoles: string[]; + ignoreNonDom: boolean; +} export interface Hooks { /** * The "position" of the closure function, starting from zero. @@ -1461,6 +1470,7 @@ export type Category = | "lint/nursery/useGroupedTypeImport" | "lint/nursery/useImportRestrictions" | "lint/nursery/useShorthandAssign" + | "lint/nursery/useValidAriaRole" | "lint/performance/noAccumulatingSpread" | "lint/performance/noDelete" | "lint/security/noDangerouslySetInnerHtml" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 049af3699807..29119f733043 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1173,6 +1173,13 @@ { "$ref": "#/definitions/RuleConfiguration" }, { "type": "null" } ] + }, + "useValidAriaRole": { + "description": "Elements with ARIA roles must use a valid, non-abstract ARIA role.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] } } }, @@ -1359,6 +1366,10 @@ { "description": "Options for `noRestrictedGlobals` rule", "allOf": [{ "$ref": "#/definitions/RestrictedGlobalsOptions" }] + }, + { + "description": "Options for `useValidAriaRole` rule", + "allOf": [{ "$ref": "#/definitions/ValidAriaRoleOptions" }] } ] }, @@ -2010,6 +2021,18 @@ } ] }, + "ValidAriaRoleOptions": { + "type": "object", + "required": ["allowedInvalidRoles", "ignoreNonDom"], + "properties": { + "allowedInvalidRoles": { + "type": "array", + "items": { "type": "string" } + }, + "ignoreNonDom": { "type": "boolean" } + }, + "additionalProperties": false + }, "VcsClientKind": { "oneOf": [ { diff --git a/website/src/content/docs/linter/rules/index.mdx b/website/src/content/docs/linter/rules/index.mdx index 15ba3f59b19c..56d72826586f 100644 --- a/website/src/content/docs/linter/rules/index.mdx +++ b/website/src/content/docs/linter/rules/index.mdx @@ -235,3 +235,4 @@ Rules that belong to this group are not subject to semantic versionimport type when an import only has specifiers with type qualifier. | ⚠️ | | [useImportRestrictions](/linter/rules/use-import-restrictions) | Disallows package private imports. | | | [useShorthandAssign](/linter/rules/use-shorthand-assign) | Require assignment operator shorthand where possible. | ⚠️ | +| [useValidAriaRole](/linter/rules/use-valid-aria-role) | Elements with ARIA roles must use a valid, non-abstract ARIA role. | ⚠️ | diff --git a/website/src/content/docs/linter/rules/use-valid-aria-role.md b/website/src/content/docs/linter/rules/use-valid-aria-role.md new file mode 100644 index 000000000000..a4b4ba02460d --- /dev/null +++ b/website/src/content/docs/linter/rules/use-valid-aria-role.md @@ -0,0 +1,138 @@ +--- +title: useValidAriaRole (since vnext) +--- + +**Diagnostic Category: `lint/nursery/useValidAriaRole`** + +:::caution +This rule is part of the [nursery](/linter/rules/#nursery) group. +::: + +Elements with ARIA roles must use a valid, non-abstract ARIA role. + +Source: https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/aria-role.md + +## Examples + +### Invalid + +```jsx +
+``` + +
nursery/useValidAriaRole.js:1:2 lint/nursery/useValidAriaRole  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role.
+  
+  > 1 │  <div role="datepicker"></div>
+    ^^^^^^^^^^^^^^^^^^^^^^^
+    2 │ 
+  
+   Check WAI-ARIA for valid roles or provide options accordingly.
+  
+   Unsafe fix: Remove the invalid role attribute.
+     Check the list of all valid role attributes.
+  
+    1 │ ·<div·role="datepicker"></div>
+        -----------------       
+
+ +```jsx +
+``` + +
nursery/useValidAriaRole.js:1:2 lint/nursery/useValidAriaRole  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role.
+  
+  > 1 │  <div role="range"></div>
+    ^^^^^^^^^^^^^^^^^^
+    2 │ 
+  
+   Check WAI-ARIA for valid roles or provide options accordingly.
+  
+   Unsafe fix: Remove the invalid role attribute.
+     Check the list of all valid role attributes.
+  
+    1 │ ·<div·role="range"></div>
+        ------------       
+
+ +```jsx +
+``` + +
nursery/useValidAriaRole.js:1:2 lint/nursery/useValidAriaRole  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role.
+  
+  > 1 │  <div role=""></div>
+    ^^^^^^^^^^^^^
+    2 │ 
+  
+   Check WAI-ARIA for valid roles or provide options accordingly.
+  
+   Unsafe fix: Remove the invalid role attribute.
+     Check the list of all valid role attributes.
+  
+    1 │ ·<div·role=""></div>
+        -------       
+
+ +```jsx + +``` + +
nursery/useValidAriaRole.js:1:3 lint/nursery/useValidAriaRole  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role.
+  
+  > 1 │   <Foo role="foo"></Foo>
+     ^^^^^^^^^^^^^^^^
+    2 │ 
+  
+   Check WAI-ARIA for valid roles or provide options accordingly.
+  
+   Unsafe fix: Remove the invalid role attribute.
+     Check the list of all valid role attributes.
+  
+    1 │ ··<Foo·role="foo"></Foo>
+         ----------       
+
+ +### Valid + +```jsx +<> +
+
+
+ +``` + +### Options + +```json +{ + "//": "...", + "options": { + "allowInvalidRoles": ["invalid", "text"], + "nonIgnoreDom": true + } +} +``` + +## Accessibility guidelines + +- [WCAG 4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value) + +## Resources + +- [Chrome Audit Rules, AX_ARIA_01](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#ax_aria_01) +- [DPUB-ARIA roles](https://www.w3.org/TR/dpub-aria-1.0/) +- [MDN: Using ARIA: Roles, states, and properties](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques) + +## Related links + +- [Disable a rule](/linter/#disable-a-lint-rule) +- [Rule options](/linter/#rule-options)