Skip to content

Commit

Permalink
refactor(aria): add AriaRole::from_roles (#4521)
Browse files Browse the repository at this point in the history
  • Loading branch information
Conaclos authored Nov 12, 2024
1 parent 419554b commit 8a90b89
Show file tree
Hide file tree
Showing 11 changed files with 29 additions and 42 deletions.
3 changes: 1 addition & 2 deletions crates/biome_aria/src/roles.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use biome_aria_metadata::AriaRole;
use rustc_hash::FxHashMap;
use std::fmt::Debug;
use std::str::FromStr;

/// Convenient type to retrieve metadata regarding ARIA roles
#[derive(Debug, Default)]
Expand Down Expand Up @@ -261,7 +260,7 @@ impl AriaRoles {
.and_then(|role| role.first())
.map_or_else(
|| self.get_implicit_role(element_name, attributes),
|r| AriaRole::from_str(r).ok(),
|r| AriaRole::from_roles(r),
)
}

Expand Down
15 changes: 15 additions & 0 deletions crates/biome_aria_metadata/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,21 @@ fn is_valid_html_id(id: &str) -> bool {
}

impl AriaRole {
/// Returns the first valid role from `values`, a space-separated list of roles.
///
/// If a role attribute has multiple values, the first valid role (specified role) will be used.
/// See <https://www.w3.org/TR/2014/REC-wai-aria-implementation-20140320/#mapping_role>
///
/// ```
/// use biome_aria_metadata::AriaRole;
/// assert_eq!(AriaRole::from_roles("INVALID main FALLBACK"), Some(AriaRole::Main));
/// ```
pub fn from_roles(roles: &str) -> Option<AriaRole> {
roles
.split_ascii_whitespace()
.find_map(|value| value.parse().ok())
}

/// Returns `true` if the given role inherits of `AriaAbstractRole::Widget` and is not `Self::Progressbar`.
///
/// This corresponds to a role that defines a user interface widget (slider, tree control, ...)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::str::FromStr;

use crate::{services::aria::Aria, JsRuleAction};
use biome_analyze::{
context::RuleContext, declare_lint_rule, FixKind, Rule, RuleDiagnostic, RuleSource,
Expand Down Expand Up @@ -66,8 +64,8 @@ impl Rule for NoInteractiveElementToNoninteractiveRole {
let attributes = ctx.extract_attributes(&node.attributes());
let attributes = ctx.convert_all_attribute_values(attributes);
if !aria_roles.is_not_interactive_element(element_name.text_trimmed(), attributes)
&& AriaRole::from_str(role_attribute_value)
.is_ok_and(|role| role.is_non_interactive())
&& AriaRole::from_roles(role_attribute_value)
.is_some_and(|role| role.is_non_interactive())
{
// <div> and <span> are considered neither interactive nor non-interactive, depending on the presence or absence of the role attribute.
// We don't report <div> and <span> here, because we cannot determine whether they are interactive or non-interactive.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::str::FromStr;

use crate::services::aria::Aria;
use crate::JsRuleAction;
use biome_analyze::context::RuleContext;
Expand Down Expand Up @@ -80,7 +78,8 @@ impl Rule for NoNoninteractiveElementToInteractiveRole {
let attributes = ctx.extract_attributes(&node.attributes());
let attributes = ctx.convert_all_attribute_values(attributes);
if aria_roles.is_not_interactive_element(element_name.text_trimmed(), attributes)
&& AriaRole::from_str(role_attribute_value).is_ok_and(|role| role.is_interactive())
&& AriaRole::from_roles(role_attribute_value)
.is_some_and(|role| role.is_interactive())
{
// <div> and <span> are considered neither interactive nor non-interactive, depending on the presence or absence of the role attribute.
// We don't report <div> and <span> here, because we cannot determine whether they are interactive or non-interactive.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::str::FromStr;

use crate::{services::aria::Aria, JsRuleAction};
use biome_analyze::{
context::RuleContext, declare_lint_rule, FixKind, Rule, RuleDiagnostic, RuleSource,
Expand Down Expand Up @@ -171,7 +169,7 @@ fn attribute_has_negative_tabindex(
/// Checks if the given role attribute value is interactive or not based on ARIA roles.
fn attribute_has_interactive_role(role_attribute_value: &AnyJsxAttributeValue) -> Option<bool> {
Some(
AriaRole::from_str(role_attribute_value.as_static_value()?.text())
.is_ok_and(|role| role.is_interactive()),
AriaRole::from_roles(role_attribute_value.as_static_value()?.text())
.is_some_and(|role| role.is_interactive()),
)
}
16 changes: 1 addition & 15 deletions crates/biome_js_analyze/src/lint/a11y/no_redundant_roles.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::str::FromStr;

use crate::{services::aria::Aria, JsRuleAction};
use biome_analyze::{
context::RuleContext, declare_lint_rule, FixKind, Rule, RuleDiagnostic, RuleSource,
Expand Down Expand Up @@ -78,7 +76,7 @@ impl Rule for NoRedundantRoles {

let role_attribute = node.find_attribute_by_name("role")?;
let role_attribute_value = role_attribute.initializer()?.value().ok()?;
let explicit_role = get_explicit_role(&role_attribute_value)?;
let explicit_role = AriaRole::from_roles(role_attribute_value.as_static_value()?.text())?;

let is_redundant = implicit_role == explicit_role;
if is_redundant {
Expand Down Expand Up @@ -132,15 +130,3 @@ fn get_element_name_and_attributes(node: &AnyJsxElement) -> Option<(String, JsxA
}
}
}

fn get_explicit_role(role_attribute_value: &AnyJsxAttributeValue) -> Option<AriaRole> {
let static_value = role_attribute_value.as_static_value()?;

// If a role attribute has multiple values, the first valid value (specified role) will be used.
// Check: https://www.w3.org/TR/2014/REC-wai-aria-implementation-20140320/#mapping_role
let explicit_role = static_value
.text()
.split_ascii_whitespace()
.find_map(|value| AriaRole::from_str(value).ok())?;
Some(explicit_role)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::str::FromStr;

use biome_analyze::context::RuleContext;
use biome_analyze::{declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource};
use biome_aria_metadata::AriaRole;
Expand Down Expand Up @@ -77,7 +75,7 @@ impl Rule for UseAriaPropsForRole {
.as_jsx_string()?
.inner_string_text()
.ok()?;
let role = AriaRole::from_str(name.text()).ok();
let role = AriaRole::from_roles(name.text());
let missing_aria_props: Vec<_> = role
.into_iter()
.flat_map(|role| role.required_attributes().iter())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::str::FromStr;

use biome_analyze::{context::RuleContext, declare_lint_rule, Rule, RuleDiagnostic, RuleSource};
use biome_aria_metadata::AriaRole;
use biome_console::markup;
Expand Down Expand Up @@ -101,6 +99,6 @@ impl Rule for UseFocusableInteractive {

/// Checks if the given role attribute value is interactive or not based on ARIA roles.
fn attribute_has_interactive_role(role_attribute_value: &AnyJsxAttributeValue) -> Option<bool> {
let role = AriaRole::from_str(role_attribute_value.as_static_value()?.text()).ok()?;
let role = AriaRole::from_roles(role_attribute_value.as_static_value()?.text())?;
Some(role.is_interactive() && !role.is_composite())
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::str::FromStr;

use biome_analyze::{
context::RuleContext, declare_lint_rule, Ast, Rule, RuleDiagnostic, RuleSource,
};
Expand Down Expand Up @@ -69,7 +67,7 @@ impl Rule for UseSemanticElements {
return None;
}

let role = AriaRole::from_str(role_value).ok()?;
let role = AriaRole::from_roles(role_value)?;
if role.base_html_elements().is_empty() && role.related_html_elements().is_empty() {
None
} else {
Expand All @@ -81,7 +79,7 @@ impl Rule for UseSemanticElements {
let role_attribute = state;
let role_value = role_attribute.as_static_value()?;
let role_value = role_value.as_string_constant()?;
let role = AriaRole::from_str(role_value).ok()?;
let role = AriaRole::from_roles(role_value)?;

let candidates = role
.base_html_elements()
Expand Down
4 changes: 1 addition & 3 deletions crates/biome_js_analyze/src/lint/a11y/use_valid_aria_role.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::str::FromStr;

use crate::JsRuleAction;
use biome_analyze::{
context::RuleContext, declare_lint_rule, Ast, FixKind, Rule, RuleDiagnostic, RuleSource,
Expand Down Expand Up @@ -110,7 +108,7 @@ impl Rule for UseValidAriaRole {
let mut role_attribute_value = role_attribute_value.split_ascii_whitespace();

let is_valid = role_attribute_value.all(|val| {
AriaRole::from_str(val).is_ok()
AriaRole::from_roles(val).is_some()
|| allowed_invalid_roles
.iter()
.any(|role| role.as_ref() == val)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ impl Rule for UseAriaPropsSupportedByRole {
let role = attributes
.get("role")
.and_then(|roles| roles.first())
.and_then(|role| AriaRole::from_str(role).ok())
.and_then(|role| AriaRole::from_roles(role))
.or_else(|| aria_roles.get_implicit_role(element_name, attributes));
let role_attributes = role.map_or(Default::default(), |role| role.attributes());
let role_prohibited_attributes =
Expand Down

0 comments on commit 8a90b89

Please sign in to comment.