From 61dbed63a5520bb4427cb5e2ad4743adb937050e Mon Sep 17 00:00:00 2001 From: vasu Date: Fri, 20 Oct 2023 00:00:18 +0530 Subject: [PATCH] feat(lint): new UseValidAriaRoles --- .../src/categories.rs | 1 + .../src/aria_analyzers/nursery.rs | 2 + .../nursery/use_valid_aria_role.rs | 136 ++++++++++++++++++ .../nursery/useValidAriaRole/invalid.jsx | 6 + .../nursery/useValidAriaRole/invalid.jsx.snap | 82 +++++++++++ .../specs/nursery/useValidAriaRole/valid.jsx | 6 + .../nursery/useValidAriaRole/valid.jsx.snap | 15 ++ .../src/configuration/linter/rules.rs | 23 ++- .../src/configuration/parse/json/rules.rs | 24 ++++ editors/vscode/configuration_schema.json | 7 + .../@biomejs/backend-jsonrpc/src/workspace.ts | 5 + .../@biomejs/biome/configuration_schema.json | 7 + .../components/generated/NumberOfRules.astro | 2 +- .../src/content/docs/linter/rules/index.mdx | 1 + .../docs/linter/rules/use-valid-aria-role.md | 106 ++++++++++++++ 15 files changed, 419 insertions(+), 4 deletions(-) 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_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index 211bdec7c654..2cf8befb43f2 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -110,6 +110,7 @@ define_categories! { "lint/nursery/useGroupedTypeImport": "https://biomejs.dev/linter/rules/use-grouped-type-import", "lint/nursery/useImportRestrictions": "https://biomejs.dev/linter/rules/use-import-restrictions", "lint/nursery/useShorthandAssign": "https://biomejs.dev/lint/rules/use-shorthand-assign", + "lint/nursery/useValidAriaRole": "https://biomejs.dev/lint/rules/use-valid-aria-role", "lint/performance/noAccumulatingSpread": "https://biomejs.dev/linter/rules/no-accumulating-spread", "lint/performance/noDelete": "https://biomejs.dev/linter/rules/no-delete", "lint/security/noDangerouslySetInnerHtml": "https://biomejs.dev/linter/rules/no-dangerously-set-inner-html", 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..5f135747c90f --- /dev/null +++ b/crates/biome_js_analyze/src/aria_analyzers/nursery/use_valid_aria_role.rs @@ -0,0 +1,136 @@ +use crate::aria_services::Aria; +use biome_analyze::{context::RuleContext, declare_rule, Rule, RuleDiagnostic}; +use biome_console::markup; +use biome_js_syntax::jsx_ext::AnyJsxElement; +use biome_rowan::AstNode; +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 + /// <> + ///
+ ///
+ ///
+ /// + /// ``` + /// + /// ## 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: false, + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct UseValidAriaRoleOptions { + allowed_invalid_roles: Vec, + ignore_non_dom: bool, +} + +impl Default for UseValidAriaRoleOptions { + fn default() -> Self { + Self { + allowed_invalid_roles: Vec::new(), + ignore_non_dom: false, + } + } +} + +impl Rule for UseValidAriaRole { + type Query = Aria; + type State = (); + type Signals = Option; + type Options = UseValidAriaRoleOptions; + + 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 { + if node.is_custom_component() { + return None; + } + } + + let role_attribute = node.find_attribute_by_name("role")?; + if role_attribute.is_value_null_or_undefined() { + return None; + } + + if node.is_custom_component() { + return Some(()); + } + + let role_attribute_static_value = role_attribute.as_static_value()?; + let role_attribute_value = role_attribute_static_value.text(); + if role_attribute_static_value.is_null_or_undefined() { + return None; + } + + let role_data = aria_roles.get_role(role_attribute_value); + + let is_valid = allowed_invalid_roles.contains(&role_attribute_value.to_string()); + + if is_valid || role_data.is_some() { + 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! { + "Elements with ARIA roles must use a valid, non-abstract ARIA role." + }) + ) + } +} 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..164e97526527 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useValidAriaRole/invalid.jsx @@ -0,0 +1,6 @@ +<> +
+
+
+ + \ No newline at end of file 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..311419311175 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useValidAriaRole/invalid.jsx.snap @@ -0,0 +1,82 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.jsx +--- +# Input +```js +<> +
+
+
+ + +``` + +# Diagnostics +``` +invalid.jsx:2:3 lint/nursery/useValidAriaRole ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role. + + 1 │ <> + > 2 │
+ │ ^^^^^^^^^^^^^^^^^^ + 3 │
+ 4 │
+ + i Elements with ARIA roles must use a valid, non-abstract ARIA role. + + +``` + +``` +invalid.jsx:3:3 lint/nursery/useValidAriaRole ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role. + + 1 │ <> + 2 │
+ > 3 │
+ │ ^^^^^^^^^^^^^^^^^^^^^^^ + 4 │
+ 5 │ + + i Elements with ARIA roles must use a valid, non-abstract ARIA role. + + +``` + +``` +invalid.jsx:4:3 lint/nursery/useValidAriaRole ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role. + + 2 │
+ 3 │
+ > 4 │
+ │ ^^^^^^^^^^^^^ + 5 │ + 6 │ + + i Elements with ARIA roles must use a valid, non-abstract ARIA role. + + +``` + +``` +invalid.jsx:5:3 lint/nursery/useValidAriaRole ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role. + + 3 │
+ 4 │
+ > 5 │ + │ ^^^^^^^^^^^^^^^^ + 6 │ + + i Elements with ARIA roles must use a valid, non-abstract ARIA role. + + +``` + + 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..94f4bf48bb0e --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useValidAriaRole/valid.jsx @@ -0,0 +1,6 @@ +<> +
+
+
+
+ \ 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..72705d30f006 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/useValidAriaRole/valid.jsx.snap @@ -0,0 +1,15 @@ +--- +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 301939fc2cef..2311a2f53abf 100644 --- a/crates/biome_service/src/configuration/linter/rules.rs +++ b/crates/biome_service/src/configuration/linter/rules.rs @@ -2363,10 +2363,14 @@ 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 Nursery { const GROUP_NAME: &'static str = "nursery"; - pub(crate) const GROUP_RULES: [&'static str; 17] = [ + pub(crate) const GROUP_RULES: [&'static str; 18] = [ "noApproximativeNumericConstant", "noDuplicateJsonKeys", "noEmptyBlockStatements", @@ -2384,6 +2388,7 @@ impl Nursery { "useGroupedTypeImport", "useImportRestrictions", "useShorthandAssign", + "useValidAriaRole", ]; const RECOMMENDED_RULES: [&'static str; 8] = [ "noDuplicateJsonKeys", @@ -2405,7 +2410,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14]), ]; - const ALL_RULES_AS_FILTERS: [RuleFilter<'static>; 17] = [ + const ALL_RULES_AS_FILTERS: [RuleFilter<'static>; 18] = [ 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]), @@ -2423,6 +2428,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17]), ]; #[doc = r" Retrieves the recommended rules"] pub(crate) fn is_recommended(&self) -> bool { @@ -2524,6 +2530,11 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } } + if let Some(rule) = self.use_valid_aria_role.as_ref() { + if rule.is_enabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> IndexSet { @@ -2613,6 +2624,11 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } } + if let Some(rule) = self.use_valid_aria_role.as_ref() { + if rule.is_disabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -2626,7 +2642,7 @@ impl Nursery { pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 8] { Self::RECOMMENDED_RULES_AS_FILTERS } - pub(crate) fn all_rules_as_filters() -> [RuleFilter<'static>; 17] { + pub(crate) fn all_rules_as_filters() -> [RuleFilter<'static>; 18] { Self::ALL_RULES_AS_FILTERS } #[doc = r" Select preset rules"] @@ -2670,6 +2686,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 4b7819c19d57..c5edf6295dce 100644 --- a/crates/biome_service/src/configuration/parse/json/rules.rs +++ b/crates/biome_service/src/configuration/parse/json/rules.rs @@ -2136,6 +2136,7 @@ impl VisitNode for Nursery { "useGroupedTypeImport", "useImportRestrictions", "useShorthandAssign", + "useValidAriaRole", ], diagnostics, ) @@ -2546,6 +2547,29 @@ impl VisitNode for Nursery { )); } }, + "useValidAriaRole" => match value { + AnyJsonValue::JsonStringValue(_) => { + let mut configuration = RuleConfiguration::default(); + self.map_to_known_string(&value, name_text, &mut configuration, diagnostics)?; + self.use_valid_aria_role = Some(configuration); + } + AnyJsonValue::JsonObjectValue(_) => { + let mut rule_configuration = RuleConfiguration::default(); + rule_configuration.map_rule_configuration( + &value, + name_text, + "useValidAriaRole", + diagnostics, + )?; + self.use_valid_aria_role = Some(rule_configuration); + } + _ => { + diagnostics.push(DeserializationDiagnostic::new_incorrect_type( + "object or string", + value.range(), + )); + } + }, _ => {} } Some(()) diff --git a/editors/vscode/configuration_schema.json b/editors/vscode/configuration_schema.json index ad3d7f6391af..fca8add9d71f 100644 --- a/editors/vscode/configuration_schema.json +++ b/editors/vscode/configuration_schema.json @@ -1152,6 +1152,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" } + ] } } }, diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 58bbc1065fc8..41871e9ded0d 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -806,6 +806,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 @@ -1445,6 +1449,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 ad3d7f6391af..fca8add9d71f 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1152,6 +1152,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" } + ] } } }, diff --git a/website/src/components/generated/NumberOfRules.astro b/website/src/components/generated/NumberOfRules.astro index 6b9300bc0694..ddca54d93573 100644 --- a/website/src/components/generated/NumberOfRules.astro +++ b/website/src/components/generated/NumberOfRules.astro @@ -1,2 +1,2 @@ -

Biome's linter has a total of 170 rules

\ No newline at end of file +

Biome's linter has a total of 171 rules

\ No newline at end of file diff --git a/website/src/content/docs/linter/rules/index.mdx b/website/src/content/docs/linter/rules/index.mdx index ab5a1482ab17..32ae222833cd 100644 --- a/website/src/content/docs/linter/rules/index.mdx +++ b/website/src/content/docs/linter/rules/index.mdx @@ -232,3 +232,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..0acade85aab8 --- /dev/null +++ b/website/src/content/docs/linter/rules/use-valid-aria-role.md @@ -0,0 +1,106 @@ +--- +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 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role.
+  
+  > 1 │  <div role="datepicker"></div>
+    ^^^^^^^^^^^^^^^^^^^^^^^
+    2 │ 
+  
+   Elements with ARIA roles must use a valid, non-abstract ARIA role.
+  
+
+ +```jsx +
+``` + +
nursery/useValidAriaRole.js:1:2 lint/nursery/useValidAriaRole ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role.
+  
+  > 1 │  <div role="range"></div>
+    ^^^^^^^^^^^^^^^^^^
+    2 │ 
+  
+   Elements with ARIA roles must use a valid, non-abstract ARIA role.
+  
+
+ +```jsx +
+``` + +
nursery/useValidAriaRole.js:1:2 lint/nursery/useValidAriaRole ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role.
+  
+  > 1 │  <div role=""></div>
+    ^^^^^^^^^^^^^
+    2 │ 
+  
+   Elements with ARIA roles must use a valid, non-abstract ARIA role.
+  
+
+ +```jsx + +``` + +
nursery/useValidAriaRole.js:1:3 lint/nursery/useValidAriaRole ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role.
+  
+  > 1 │   <Foo role="foo"></Foo>
+     ^^^^^^^^^^^^^^^^
+    2 │ 
+  
+   Elements with ARIA roles must use a valid, non-abstract ARIA role.
+  
+
+ +### Valid + +```jsx +<> +
+
+
+ +``` + +## 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)