diff --git a/crates/biome_aria/src/roles.rs b/crates/biome_aria/src/roles.rs
index 4e0a7ec57c61..526b07b1eb68 100644
--- a/crates/biome_aria/src/roles.rs
+++ b/crates/biome_aria/src/roles.rs
@@ -1019,7 +1019,10 @@ impl<'a> AriaRoles {
"code" => &CodeRole as &dyn AriaRoleDefinition,
"datalist" => &ListBoxRole as &dyn AriaRoleDefinition,
"del" => &DeletionRole as &dyn AriaRoleDefinition,
+ "dd" => &DefinitionRole as &dyn AriaRoleDefinition,
+ "dt" => &TermRole as &dyn AriaRoleDefinition,
"dfn" => &TermRole as &dyn AriaRoleDefinition,
+ "mark" => &MarkRole as &dyn AriaRoleDefinition,
"dialog" => &DialogRole as &dyn AriaRoleDefinition,
"em" => &EmphasisRole as &dyn AriaRoleDefinition,
"figure" => &FigureRole as &dyn AriaRoleDefinition,
@@ -1035,6 +1038,7 @@ impl<'a> AriaRoles {
"nav" => &NavigationRole as &dyn AriaRoleDefinition,
"ul" | "ol" => &ListRole as &dyn AriaRoleDefinition,
"li" => &ListItemRole as &dyn AriaRoleDefinition,
+ "option" => &OptionRole as &dyn AriaRoleDefinition,
"optgroup" => &GroupRole as &dyn AriaRoleDefinition,
"output" => &StatusRole as &dyn AriaRoleDefinition,
"p" => &ParagraphRole as &dyn AriaRoleDefinition,
@@ -1047,6 +1051,16 @@ impl<'a> AriaRoles {
"table" => &TableRole as &dyn AriaRoleDefinition,
"textarea" => &TextboxRole as &dyn AriaRoleDefinition,
"tr" => &RowRole as &dyn AriaRoleDefinition,
+ // cell if a descendant of a
element,
+ // but this crate does not support checking a descendant of an element.
+ //
+ // ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td
+ "td" => &CellRole as &dyn AriaRoleDefinition,
+ // element is able to be a rowheader, columnheader,
+ // but this crate does not support checking a descendant of an element.
+ //
+ // ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th
+ "th" => &RowHeaderRole 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,
@@ -1115,7 +1129,7 @@ impl<'a> AriaRoles {
}
}
"b" | "bdi" | "bdo" | "body" | "data" | "div" | "hgroup" | "i" | "q" | "samp"
- | "small" | "span" | "u" => &GenericRole as &dyn AriaRoleDefinition,
+ | "small" | "span" | "u" | "pre" => &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
@@ -1296,6 +1310,44 @@ impl<'a> AriaRoles {
role_candidate.concepts_by_role()
}
+
+ pub fn is_not_static_element(
+ &self,
+ element_name: &str,
+ attributes: &FxHashMap>,
+ ) -> bool {
+ if match element_name {
+ // embedded content
+ // ref: https://html.spec.whatwg.org/multipage/semantics.html#embedded-content
+ "canvas" | "embed" | "iframe" | "video" | "audio" => true,
+ // metadata content
+ // ref: https://html.spec.whatwg.org/multipage/semantics.html#document-metadata
+ "meta" | "link" | "base" | "title" | "basefont" | "head" => false,
+ // scripting content
+ // ref: https://html.spec.whatwg.org/multipage/semantics.html#scripting-content
+ "script" | "noscript" | "template" | "style" => false,
+ // No corresponding role
+ "dl" | "label" | "legend" | "ruby" | "pre" | "figcaption" | "br" => true,
+ _ => false,
+ } {
+ return true;
+ }
+
+ // if the element has a interactive role, it is considered interactive.
+ let role_name = attributes
+ .get("role")
+ .and_then(|role| role.first())
+ .map_or_else(
+ || self.get_implicit_role(element_name, attributes),
+ |r| self.get_role(r),
+ );
+
+ match role_name.map(|role| role.type_name()) {
+ Some("biome_aria::roles::PresentationRole" | "biome_aria::roles::GenericRole") => false,
+ Some(_) => true,
+ None => false,
+ }
+ }
}
type ElementsAndAttributes<'a> = Option>;
diff --git a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs
index 539a36f820d1..1c914b1d625e 100644
--- a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs
+++ b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs
@@ -649,6 +649,16 @@ pub(crate) fn migrate_eslint_any_rule(
let rule = group.no_redundant_roles.get_or_insert(Default::default());
rule.set_level(rule_severity.into());
}
+ "jsx-a11y/no-static-element-interactions" => {
+ if !options.include_nursery {
+ return false;
+ }
+ let group = rules.nursery.get_or_insert_with(Default::default);
+ let rule = group
+ .no_static_element_interactions
+ .get_or_insert(Default::default());
+ rule.set_level(rule_severity.into());
+ }
"jsx-a11y/prefer-tag-over-role" => {
if !options.include_nursery {
return false;
diff --git a/crates/biome_configuration/src/linter/rules.rs b/crates/biome_configuration/src/linter/rules.rs
index f35e445b9cfb..a2859dc56572 100644
--- a/crates/biome_configuration/src/linter/rules.rs
+++ b/crates/biome_configuration/src/linter/rules.rs
@@ -2878,6 +2878,9 @@ pub struct Nursery {
#[doc = "Disallow shorthand properties that override related longhand properties."]
#[serde(skip_serializing_if = "Option::is_none")]
pub no_shorthand_property_overrides: Option>,
+ #[doc = "Enforce that static, visible elements (such as \\) that have click handlers use the valid role attribute."]
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub no_static_element_interactions: Option >,
#[doc = "Enforce the use of String.slice() over String.substr() and String.substring()."]
#[serde(skip_serializing_if = "Option::is_none")]
pub no_substr: Option>,
@@ -3023,6 +3026,7 @@ impl Nursery {
"noReactSpecificProps",
"noRestrictedImports",
"noShorthandPropertyOverrides",
+ "noStaticElementInteractions",
"noSubstr",
"noUndeclaredDependencies",
"noUnknownFunction",
@@ -3098,17 +3102,17 @@ impl Nursery {
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14]),
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16]),
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20]),
- RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23]),
- RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25]),
+ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24]),
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26]),
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27]),
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28]),
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29]),
- RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40]),
- RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43]),
+ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[30]),
+ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41]),
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44]),
- RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48]),
- RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[50]),
+ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45]),
+ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49]),
+ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[51]),
];
const ALL_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]),
@@ -3166,6 +3170,7 @@ impl Nursery {
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[52]),
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[53]),
RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[54]),
+ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[55]),
];
#[doc = r" Retrieves the recommended rules"]
pub(crate) fn is_recommended_true(&self) -> bool {
@@ -3287,176 +3292,181 @@ impl Nursery {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20]));
}
}
- if let Some(rule) = self.no_substr.as_ref() {
+ if let Some(rule) = self.no_static_element_interactions.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]));
}
}
- if let Some(rule) = self.no_undeclared_dependencies.as_ref() {
+ if let Some(rule) = self.no_substr.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22]));
}
}
- if let Some(rule) = self.no_unknown_function.as_ref() {
+ if let Some(rule) = self.no_undeclared_dependencies.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23]));
}
}
- if let Some(rule) = self.no_unknown_media_feature_name.as_ref() {
+ if let Some(rule) = self.no_unknown_function.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24]));
}
}
- if let Some(rule) = self.no_unknown_property.as_ref() {
+ if let Some(rule) = self.no_unknown_media_feature_name.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25]));
}
}
- if let Some(rule) = self.no_unknown_pseudo_class_selector.as_ref() {
+ if let Some(rule) = self.no_unknown_property.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26]));
}
}
- if let Some(rule) = self.no_unknown_selector_pseudo_element.as_ref() {
+ if let Some(rule) = self.no_unknown_pseudo_class_selector.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27]));
}
}
- if let Some(rule) = self.no_unknown_unit.as_ref() {
+ if let Some(rule) = self.no_unknown_selector_pseudo_element.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28]));
}
}
- if let Some(rule) = self.no_unmatchable_anb_selector.as_ref() {
+ if let Some(rule) = self.no_unknown_unit.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29]));
}
}
- if let Some(rule) = self.no_unused_function_parameters.as_ref() {
+ if let Some(rule) = self.no_unmatchable_anb_selector.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_string_concat.as_ref() {
+ if let Some(rule) = self.no_unused_function_parameters.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_undefined_initialization.as_ref() {
+ if let Some(rule) = self.no_useless_string_concat.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32]));
}
}
- if let Some(rule) = self.no_yoda_expression.as_ref() {
+ if let Some(rule) = self.no_useless_undefined_initialization.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33]));
}
}
- if let Some(rule) = self.use_adjacent_overload_signatures.as_ref() {
+ if let Some(rule) = self.no_yoda_expression.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34]));
}
}
- if let Some(rule) = self.use_consistent_builtin_instantiation.as_ref() {
+ if let Some(rule) = self.use_adjacent_overload_signatures.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35]));
}
}
- if let Some(rule) = self.use_consistent_curly_braces.as_ref() {
+ if let Some(rule) = self.use_consistent_builtin_instantiation.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36]));
}
}
- if let Some(rule) = self.use_consistent_grid_areas.as_ref() {
+ if let Some(rule) = self.use_consistent_curly_braces.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37]));
}
}
- if let Some(rule) = self.use_date_now.as_ref() {
+ if let Some(rule) = self.use_consistent_grid_areas.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38]));
}
}
- if let Some(rule) = self.use_default_switch_clause.as_ref() {
+ if let Some(rule) = self.use_date_now.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39]));
}
}
- if let Some(rule) = self.use_deprecated_reason.as_ref() {
+ if let Some(rule) = self.use_default_switch_clause.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40]));
}
}
- if let Some(rule) = self.use_error_message.as_ref() {
+ if let Some(rule) = self.use_deprecated_reason.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41]));
}
}
- if let Some(rule) = self.use_explicit_length_check.as_ref() {
+ if let Some(rule) = self.use_error_message.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42]));
}
}
- if let Some(rule) = self.use_focusable_interactive.as_ref() {
+ if let Some(rule) = self.use_explicit_length_check.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43]));
}
}
- if let Some(rule) = self.use_generic_font_names.as_ref() {
+ if let Some(rule) = self.use_focusable_interactive.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44]));
}
}
- if let Some(rule) = self.use_import_extensions.as_ref() {
+ if let Some(rule) = self.use_generic_font_names.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45]));
}
}
- if let Some(rule) = self.use_import_restrictions.as_ref() {
+ if let Some(rule) = self.use_import_extensions.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46]));
}
}
- if let Some(rule) = self.use_number_to_fixed_digits_argument.as_ref() {
+ if let Some(rule) = self.use_import_restrictions.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47]));
}
}
- if let Some(rule) = self.use_semantic_elements.as_ref() {
+ if let Some(rule) = self.use_number_to_fixed_digits_argument.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48]));
}
}
- if let Some(rule) = self.use_sorted_classes.as_ref() {
+ if let Some(rule) = self.use_semantic_elements.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49]));
}
}
- if let Some(rule) = self.use_strict_mode.as_ref() {
+ if let Some(rule) = self.use_sorted_classes.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[50]));
}
}
- if let Some(rule) = self.use_throw_new_error.as_ref() {
+ if let Some(rule) = self.use_strict_mode.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[51]));
}
}
- if let Some(rule) = self.use_throw_only_error.as_ref() {
+ if let Some(rule) = self.use_throw_new_error.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[52]));
}
}
- if let Some(rule) = self.use_top_level_regex.as_ref() {
+ if let Some(rule) = self.use_throw_only_error.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[53]));
}
}
- if let Some(rule) = self.use_valid_autocomplete.as_ref() {
+ if let Some(rule) = self.use_top_level_regex.as_ref() {
if rule.is_enabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[54]));
}
}
+ if let Some(rule) = self.use_valid_autocomplete.as_ref() {
+ if rule.is_enabled() {
+ index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[55]));
+ }
+ }
index_set
}
pub(crate) fn get_disabled_rules(&self) -> FxHashSet> {
@@ -3566,176 +3576,181 @@ impl Nursery {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20]));
}
}
- if let Some(rule) = self.no_substr.as_ref() {
+ if let Some(rule) = self.no_static_element_interactions.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]));
}
}
- if let Some(rule) = self.no_undeclared_dependencies.as_ref() {
+ if let Some(rule) = self.no_substr.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22]));
}
}
- if let Some(rule) = self.no_unknown_function.as_ref() {
+ if let Some(rule) = self.no_undeclared_dependencies.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23]));
}
}
- if let Some(rule) = self.no_unknown_media_feature_name.as_ref() {
+ if let Some(rule) = self.no_unknown_function.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24]));
}
}
- if let Some(rule) = self.no_unknown_property.as_ref() {
+ if let Some(rule) = self.no_unknown_media_feature_name.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25]));
}
}
- if let Some(rule) = self.no_unknown_pseudo_class_selector.as_ref() {
+ if let Some(rule) = self.no_unknown_property.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26]));
}
}
- if let Some(rule) = self.no_unknown_selector_pseudo_element.as_ref() {
+ if let Some(rule) = self.no_unknown_pseudo_class_selector.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27]));
}
}
- if let Some(rule) = self.no_unknown_unit.as_ref() {
+ if let Some(rule) = self.no_unknown_selector_pseudo_element.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28]));
}
}
- if let Some(rule) = self.no_unmatchable_anb_selector.as_ref() {
+ if let Some(rule) = self.no_unknown_unit.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29]));
}
}
- if let Some(rule) = self.no_unused_function_parameters.as_ref() {
+ if let Some(rule) = self.no_unmatchable_anb_selector.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_string_concat.as_ref() {
+ if let Some(rule) = self.no_unused_function_parameters.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_undefined_initialization.as_ref() {
+ if let Some(rule) = self.no_useless_string_concat.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32]));
}
}
- if let Some(rule) = self.no_yoda_expression.as_ref() {
+ if let Some(rule) = self.no_useless_undefined_initialization.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33]));
}
}
- if let Some(rule) = self.use_adjacent_overload_signatures.as_ref() {
+ if let Some(rule) = self.no_yoda_expression.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34]));
}
}
- if let Some(rule) = self.use_consistent_builtin_instantiation.as_ref() {
+ if let Some(rule) = self.use_adjacent_overload_signatures.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[35]));
}
}
- if let Some(rule) = self.use_consistent_curly_braces.as_ref() {
+ if let Some(rule) = self.use_consistent_builtin_instantiation.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[36]));
}
}
- if let Some(rule) = self.use_consistent_grid_areas.as_ref() {
+ if let Some(rule) = self.use_consistent_curly_braces.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[37]));
}
}
- if let Some(rule) = self.use_date_now.as_ref() {
+ if let Some(rule) = self.use_consistent_grid_areas.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[38]));
}
}
- if let Some(rule) = self.use_default_switch_clause.as_ref() {
+ if let Some(rule) = self.use_date_now.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[39]));
}
}
- if let Some(rule) = self.use_deprecated_reason.as_ref() {
+ if let Some(rule) = self.use_default_switch_clause.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[40]));
}
}
- if let Some(rule) = self.use_error_message.as_ref() {
+ if let Some(rule) = self.use_deprecated_reason.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[41]));
}
}
- if let Some(rule) = self.use_explicit_length_check.as_ref() {
+ if let Some(rule) = self.use_error_message.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[42]));
}
}
- if let Some(rule) = self.use_focusable_interactive.as_ref() {
+ if let Some(rule) = self.use_explicit_length_check.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[43]));
}
}
- if let Some(rule) = self.use_generic_font_names.as_ref() {
+ if let Some(rule) = self.use_focusable_interactive.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[44]));
}
}
- if let Some(rule) = self.use_import_extensions.as_ref() {
+ if let Some(rule) = self.use_generic_font_names.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[45]));
}
}
- if let Some(rule) = self.use_import_restrictions.as_ref() {
+ if let Some(rule) = self.use_import_extensions.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[46]));
}
}
- if let Some(rule) = self.use_number_to_fixed_digits_argument.as_ref() {
+ if let Some(rule) = self.use_import_restrictions.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[47]));
}
}
- if let Some(rule) = self.use_semantic_elements.as_ref() {
+ if let Some(rule) = self.use_number_to_fixed_digits_argument.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[48]));
}
}
- if let Some(rule) = self.use_sorted_classes.as_ref() {
+ if let Some(rule) = self.use_semantic_elements.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[49]));
}
}
- if let Some(rule) = self.use_strict_mode.as_ref() {
+ if let Some(rule) = self.use_sorted_classes.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[50]));
}
}
- if let Some(rule) = self.use_throw_new_error.as_ref() {
+ if let Some(rule) = self.use_strict_mode.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[51]));
}
}
- if let Some(rule) = self.use_throw_only_error.as_ref() {
+ if let Some(rule) = self.use_throw_new_error.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[52]));
}
}
- if let Some(rule) = self.use_top_level_regex.as_ref() {
+ if let Some(rule) = self.use_throw_only_error.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[53]));
}
}
- if let Some(rule) = self.use_valid_autocomplete.as_ref() {
+ if let Some(rule) = self.use_top_level_regex.as_ref() {
if rule.is_disabled() {
index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[54]));
}
}
+ if let Some(rule) = self.use_valid_autocomplete.as_ref() {
+ if rule.is_disabled() {
+ index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[55]));
+ }
+ }
index_set
}
#[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"]
@@ -3856,6 +3871,10 @@ impl Nursery {
.no_shorthand_property_overrides
.as_ref()
.map(|conf| (conf.level(), conf.get_options())),
+ "noStaticElementInteractions" => self
+ .no_static_element_interactions
+ .as_ref()
+ .map(|conf| (conf.level(), conf.get_options())),
"noSubstr" => self
.no_substr
.as_ref()
diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs
index 18368296846c..42c416eb051c 100644
--- a/crates/biome_diagnostics_categories/src/categories.rs
+++ b/crates/biome_diagnostics_categories/src/categories.rs
@@ -136,6 +136,7 @@ define_categories! {
"lint/nursery/noMissingGenericFamilyKeyword": "https://biomejs.dev/linter/rules/no-missing-generic-family-keyword",
"lint/nursery/noReactSpecificProps": "https://biomejs.dev/linter/rules/no-react-specific-props",
"lint/nursery/noRestrictedImports": "https://biomejs.dev/linter/rules/no-restricted-imports",
+ "lint/nursery/noStaticElementInteractions": "https://biomejs.dev/linter/rules/no-static-element-interactions",
"lint/nursery/noShorthandPropertyOverrides": "https://biomejs.dev/linter/rules/no-shorthand-property-overrides",
"lint/nursery/noSubstr": "https://biomejs.dev/linter/rules/no-substr",
"lint/nursery/noUndeclaredDependencies": "https://biomejs.dev/linter/rules/no-undeclared-dependencies",
diff --git a/crates/biome_js_analyze/src/lint/a11y/no_aria_hidden_on_focusable.rs b/crates/biome_js_analyze/src/lint/a11y/no_aria_hidden_on_focusable.rs
index bfa23fb2ec11..22e570f05ae5 100644
--- a/crates/biome_js_analyze/src/lint/a11y/no_aria_hidden_on_focusable.rs
+++ b/crates/biome_js_analyze/src/lint/a11y/no_aria_hidden_on_focusable.rs
@@ -69,6 +69,7 @@ impl Rule for NoAriaHiddenOnFocusable {
let attr_text = attr_static_val.text();
let attributes = ctx.extract_attributes(&node.attributes());
+ let attributes = ctx.convert_all_attribute_values(attributes);
if attr_text == "false" {
return None;
diff --git a/crates/biome_js_analyze/src/lint/a11y/no_interactive_element_to_noninteractive_role.rs b/crates/biome_js_analyze/src/lint/a11y/no_interactive_element_to_noninteractive_role.rs
index 1e97627eac31..e80af692f203 100644
--- a/crates/biome_js_analyze/src/lint/a11y/no_interactive_element_to_noninteractive_role.rs
+++ b/crates/biome_js_analyze/src/lint/a11y/no_interactive_element_to_noninteractive_role.rs
@@ -58,6 +58,7 @@ impl Rule for NoInteractiveElementToNoninteractiveRole {
let element_name = node.name().ok()?.as_jsx_name()?.value_token().ok()?;
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)
&& !aria_roles.is_role_interactive(role_attribute_value)
{
diff --git a/crates/biome_js_analyze/src/lint/a11y/no_noninteractive_element_to_interactive_role.rs b/crates/biome_js_analyze/src/lint/a11y/no_noninteractive_element_to_interactive_role.rs
index 7ee219715b9a..c408880cb754 100644
--- a/crates/biome_js_analyze/src/lint/a11y/no_noninteractive_element_to_interactive_role.rs
+++ b/crates/biome_js_analyze/src/lint/a11y/no_noninteractive_element_to_interactive_role.rs
@@ -75,6 +75,7 @@ impl Rule for NoNoninteractiveElementToInteractiveRole {
let element_name = node.name().ok()?.as_jsx_name()?.value_token().ok()?;
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)
&& aria_roles.is_role_interactive(role_attribute_value)
{
diff --git a/crates/biome_js_analyze/src/lint/a11y/no_noninteractive_tabindex.rs b/crates/biome_js_analyze/src/lint/a11y/no_noninteractive_tabindex.rs
index 1a5070e0e2d2..311600757fe1 100644
--- a/crates/biome_js_analyze/src/lint/a11y/no_noninteractive_tabindex.rs
+++ b/crates/biome_js_analyze/src/lint/a11y/no_noninteractive_tabindex.rs
@@ -112,6 +112,7 @@ impl Rule for NoNoninteractiveTabindex {
let element_name = node.name().ok()?.as_jsx_name()?.value_token().ok()?;
let aria_roles = ctx.aria_roles();
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) {
let tabindex_attribute = node.find_attribute_by_name("tabIndex")?;
diff --git a/crates/biome_js_analyze/src/lint/a11y/no_redundant_roles.rs b/crates/biome_js_analyze/src/lint/a11y/no_redundant_roles.rs
index 7fe595dc5d11..f925d0e9cd6b 100644
--- a/crates/biome_js_analyze/src/lint/a11y/no_redundant_roles.rs
+++ b/crates/biome_js_analyze/src/lint/a11y/no_redundant_roles.rs
@@ -70,9 +70,10 @@ impl Rule for NoRedundantRoles {
let aria_roles = ctx.aria_roles();
let (element_name, attributes) = get_element_name_and_attributes(node)?;
- let attribute_name_to_values = ctx.extract_attributes(&attributes)?;
- let implicit_role =
- aria_roles.get_implicit_role(&element_name, &attribute_name_to_values)?;
+ let attribute_name_to_values = ctx.extract_attributes(&attributes);
+ let attribute_name_to_values = ctx.convert_all_attribute_values(attribute_name_to_values);
+ let attr = attribute_name_to_values?;
+ let implicit_role = aria_roles.get_implicit_role(&element_name, &attr)?;
let role_attribute = node.find_attribute_by_name("role")?;
let role_attribute_value = role_attribute.initializer()?.value().ok()?;
diff --git a/crates/biome_js_analyze/src/lint/a11y/use_aria_activedescendant_with_tabindex.rs b/crates/biome_js_analyze/src/lint/a11y/use_aria_activedescendant_with_tabindex.rs
index 6cddba586091..ef3389f422a6 100644
--- a/crates/biome_js_analyze/src/lint/a11y/use_aria_activedescendant_with_tabindex.rs
+++ b/crates/biome_js_analyze/src/lint/a11y/use_aria_activedescendant_with_tabindex.rs
@@ -69,6 +69,7 @@ impl Rule for UseAriaActivedescendantWithTabindex {
let aria_roles = ctx.aria_roles();
let element_name = node.name().ok()?.as_jsx_name()?.value_token().ok()?;
let attributes = ctx.extract_attributes(&node.attributes());
+ let attributes = ctx.convert_all_attribute_values(attributes);
if node.is_element()
&& aria_roles.is_not_interactive_element(element_name.text_trimmed(), attributes)
diff --git a/crates/biome_js_analyze/src/lint/nursery.rs b/crates/biome_js_analyze/src/lint/nursery.rs
index 584a7d0114bb..b67667ecb6c3 100644
--- a/crates/biome_js_analyze/src/lint/nursery.rs
+++ b/crates/biome_js_analyze/src/lint/nursery.rs
@@ -13,6 +13,7 @@ pub mod no_label_without_control;
pub mod no_misplaced_assertion;
pub mod no_react_specific_props;
pub mod no_restricted_imports;
+pub mod no_static_element_interactions;
pub mod no_substr;
pub mod no_undeclared_dependencies;
pub mod no_unused_function_parameters;
@@ -53,6 +54,7 @@ declare_lint_group! {
self :: no_misplaced_assertion :: NoMisplacedAssertion ,
self :: no_react_specific_props :: NoReactSpecificProps ,
self :: no_restricted_imports :: NoRestrictedImports ,
+ self :: no_static_element_interactions :: NoStaticElementInteractions ,
self :: no_substr :: NoSubstr ,
self :: no_undeclared_dependencies :: NoUndeclaredDependencies ,
self :: no_unused_function_parameters :: NoUnusedFunctionParameters ,
diff --git a/crates/biome_js_analyze/src/lint/nursery/no_static_element_interactions.rs b/crates/biome_js_analyze/src/lint/nursery/no_static_element_interactions.rs
new file mode 100644
index 000000000000..bbd6b20bbf36
--- /dev/null
+++ b/crates/biome_js_analyze/src/lint/nursery/no_static_element_interactions.rs
@@ -0,0 +1,166 @@
+use crate::services::aria::Aria;
+use biome_analyze::context::RuleContext;
+use biome_analyze::{declare_lint_rule, Rule, RuleDiagnostic, RuleSource};
+use biome_console::markup;
+use biome_js_syntax::jsx_ext::AnyJsxElement;
+use biome_rowan::AstNode;
+
+declare_lint_rule! {
+ /// Enforce that static, visible elements (such as ``) that have click handlers use the valid role attribute.
+ ///
+ /// Static HTML elements do not have semantic meaning. This is clear in the case of ` ` and ` `. It is less so clear in the case of elements that seem semantic, but that do not have a semantic mapping in the accessibility layer. For example `` without href attribute, ``, ` |