From 7585e16bebb1a819d331553cb450cc381c135d1f Mon Sep 17 00:00:00 2001 From: DonIsaac <22823424+DonIsaac@users.noreply.github.com> Date: Wed, 31 Jul 2024 00:24:12 +0000 Subject: [PATCH] perf(linter): remove allocations for string comparisons (#4570) Refactors a lot of case-insensitive comparisons from ```rust a.to_lowercase() == b.to_lowercase() ``` with ```rust a.eq_ignore_ascii_case(b) ``` These mostly happened when checking JSX props, so I'm expecting the most benefit from JSX-related rules. --- crates/oxc_codegen/src/gen.rs | 3 +- .../oxc_linter/src/rules/jsx_a11y/alt_text.rs | 32 +++++++++---------- .../src/rules/jsx_a11y/anchor_has_content.rs | 4 +-- .../src/rules/jsx_a11y/anchor_is_valid.rs | 6 ++-- .../aria_activedescendant_has_tabindex.rs | 6 ++-- .../src/rules/jsx_a11y/autocomplete_valid.rs | 4 +-- .../src/rules/jsx_a11y/html_has_lang.rs | 4 +-- .../src/rules/jsx_a11y/iframe_has_title.rs | 4 +-- .../src/rules/jsx_a11y/img_redundant_alt.rs | 4 +-- crates/oxc_linter/src/rules/jsx_a11y/lang.rs | 4 +-- .../src/rules/jsx_a11y/media_has_caption.rs | 2 +- .../src/rules/jsx_a11y/no_access_key.rs | 5 +-- .../jsx_a11y/no_aria_hidden_on_focusable.rs | 10 +++--- .../src/rules/jsx_a11y/no_redundant_roles.rs | 4 +-- .../rules/jsx_a11y/prefer_tag_over_role.rs | 4 +-- .../jsx_a11y/role_has_required_aria_props.rs | 6 ++-- .../jsx_a11y/role_supports_aria_props.rs | 31 ++++++++---------- crates/oxc_linter/src/rules/jsx_a11y/scope.rs | 4 +-- .../rules/jsx_a11y/tabindex_no_positive.rs | 4 +-- .../src/rules/nextjs/google_font_display.rs | 4 +-- .../rules/nextjs/google_font_preconnect.rs | 6 ++-- .../src/rules/nextjs/next_script_for_ga.rs | 6 ++-- .../src/rules/react/button_has_type.rs | 4 +-- .../src/rules/react/jsx_no_target_blank.rs | 6 ++-- .../src/rules/react/no_unknown_property.rs | 2 +- .../unicorn/text_encoding_identifier_case.rs | 4 +-- crates/oxc_linter/src/utils/react.rs | 14 ++++---- 27 files changed, 92 insertions(+), 95 deletions(-) diff --git a/crates/oxc_codegen/src/gen.rs b/crates/oxc_codegen/src/gen.rs index bf76ce195506a..af23c3bf1ca09 100644 --- a/crates/oxc_codegen/src/gen.rs +++ b/crates/oxc_codegen/src/gen.rs @@ -1267,8 +1267,7 @@ impl<'a, const MINIFY: bool> Gen for RegExpLiteral<'a> { let last = p.peek_nth(0); // Avoid forming a single-line comment or " if let Some(custom_tags) = &self.input_type_image { - let has_input_with_type_image = name.to_lowercase() == "input" - && has_jsx_prop_lowercase(jsx_el, "type").map_or(false, |v| { + let has_input_with_type_image = name.eq_ignore_ascii_case("input") + && has_jsx_prop_ignore_case(jsx_el, "type").map_or(false, |v| { get_string_literal_prop_value(v).map_or(false, |v| v == "image") }); if has_input_with_type_image || custom_tags.iter().any(|i| i == name) { @@ -247,26 +247,26 @@ fn aria_label_has_value<'a>(item: &'a JSXAttributeItem<'a>) -> bool { } fn img_rule<'a>(node: &'a JSXOpeningElement<'a>, ctx: &LintContext<'a>) { - if let Some(alt_prop) = has_jsx_prop_lowercase(node, "alt") { + if let Some(alt_prop) = has_jsx_prop_ignore_case(node, "alt") { if !is_valid_alt_prop(alt_prop) { ctx.diagnostic(missing_alt_value(node.span)); } return; } - if has_jsx_prop_lowercase(node, "role").map_or(false, is_presentation_role) { + if has_jsx_prop_ignore_case(node, "role").map_or(false, is_presentation_role) { ctx.diagnostic(prefer_alt(node.span)); return; } - if let Some(aria_label_prop) = has_jsx_prop_lowercase(node, "aria-label") { + if let Some(aria_label_prop) = has_jsx_prop_ignore_case(node, "aria-label") { if !aria_label_has_value(aria_label_prop) { ctx.diagnostic(aria_label_value(node.span)); } return; } - if let Some(aria_labelledby_prop) = has_jsx_prop_lowercase(node, "aria-labelledby") { + if let Some(aria_labelledby_prop) = has_jsx_prop_ignore_case(node, "aria-labelledby") { if !aria_label_has_value(aria_labelledby_prop) { ctx.diagnostic(aria_labelled_by_value(node.span)); } @@ -282,11 +282,11 @@ fn object_rule<'a>( ctx: &LintContext<'a>, ) { let has_aria_label = - has_jsx_prop_lowercase(node, "aria-label").map_or(false, aria_label_has_value); + has_jsx_prop_ignore_case(node, "aria-label").map_or(false, aria_label_has_value); let has_aria_labelledby = - has_jsx_prop_lowercase(node, "aria-labelledby").map_or(false, aria_label_has_value); + has_jsx_prop_ignore_case(node, "aria-labelledby").map_or(false, aria_label_has_value); let has_label = has_aria_label || has_aria_labelledby; - let has_title_attr = has_jsx_prop_lowercase(node, "title") + let has_title_attr = has_jsx_prop_ignore_case(node, "title") .and_then(get_string_literal_prop_value) .map_or(false, |v| !v.is_empty()); @@ -298,14 +298,14 @@ fn object_rule<'a>( fn area_rule<'a>(node: &'a JSXOpeningElement<'a>, ctx: &LintContext<'a>) { let has_aria_label = - has_jsx_prop_lowercase(node, "aria-label").map_or(false, aria_label_has_value); + has_jsx_prop_ignore_case(node, "aria-label").map_or(false, aria_label_has_value); let has_aria_labelledby = - has_jsx_prop_lowercase(node, "aria-labelledby").map_or(false, aria_label_has_value); + has_jsx_prop_ignore_case(node, "aria-labelledby").map_or(false, aria_label_has_value); let has_label = has_aria_label || has_aria_labelledby; if has_label { return; } - has_jsx_prop_lowercase(node, "alt").map_or_else( + has_jsx_prop_ignore_case(node, "alt").map_or_else( || { ctx.diagnostic(area(node.span)); }, @@ -319,14 +319,14 @@ fn area_rule<'a>(node: &'a JSXOpeningElement<'a>, ctx: &LintContext<'a>) { fn input_type_image_rule<'a>(node: &'a JSXOpeningElement<'a>, ctx: &LintContext<'a>) { let has_aria_label = - has_jsx_prop_lowercase(node, "aria-label").map_or(false, aria_label_has_value); + has_jsx_prop_ignore_case(node, "aria-label").map_or(false, aria_label_has_value); let has_aria_labelledby = - has_jsx_prop_lowercase(node, "aria-labelledby").map_or(false, aria_label_has_value); + has_jsx_prop_ignore_case(node, "aria-labelledby").map_or(false, aria_label_has_value); let has_label = has_aria_label || has_aria_labelledby; if has_label { return; } - has_jsx_prop_lowercase(node, "alt").map_or_else( + has_jsx_prop_ignore_case(node, "alt").map_or_else( || { ctx.diagnostic(input_type_image(node.span)); }, diff --git a/crates/oxc_linter/src/rules/jsx_a11y/anchor_has_content.rs b/crates/oxc_linter/src/rules/jsx_a11y/anchor_has_content.rs index 3c093f1478f86..15c81fd6c389b 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/anchor_has_content.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/anchor_has_content.rs @@ -7,7 +7,7 @@ use crate::{ context::LintContext, rule::Rule, utils::{ - get_element_type, has_jsx_prop_lowercase, is_hidden_from_screen_reader, + get_element_type, has_jsx_prop_ignore_case, is_hidden_from_screen_reader, object_has_accessible_child, }, AstNode, @@ -79,7 +79,7 @@ impl Rule for AnchorHasContent { } for attr in ["title", "aria-label"] { - if has_jsx_prop_lowercase(&jsx_el.opening_element, attr).is_some() { + if has_jsx_prop_ignore_case(&jsx_el.opening_element, attr).is_some() { return; }; } diff --git a/crates/oxc_linter/src/rules/jsx_a11y/anchor_is_valid.rs b/crates/oxc_linter/src/rules/jsx_a11y/anchor_is_valid.rs index a4f69db2d2fa2..2a8ceea64c638 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/anchor_is_valid.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/anchor_is_valid.rs @@ -9,7 +9,7 @@ use oxc_span::Span; use crate::{ context::LintContext, rule::Rule, - utils::{get_element_type, has_jsx_prop_lowercase}, + utils::{get_element_type, has_jsx_prop_ignore_case}, AstNode, }; @@ -134,7 +134,7 @@ impl Rule for AnchorIsValid { }; if name == "a" { if let Option::Some(herf_attr) = - has_jsx_prop_lowercase(&jsx_el.opening_element, "href") + has_jsx_prop_ignore_case(&jsx_el.opening_element, "href") { // Check if the 'a' element has a correct href attribute match herf_attr { @@ -142,7 +142,7 @@ impl Rule for AnchorIsValid { Some(value) => { let is_empty = check_value_is_empty(value, &self.0.valid_hrefs); if is_empty { - if has_jsx_prop_lowercase(&jsx_el.opening_element, "onclick") + if has_jsx_prop_ignore_case(&jsx_el.opening_element, "onclick") .is_some() { ctx.diagnostic(cant_be_anchor(ident.span)); diff --git a/crates/oxc_linter/src/rules/jsx_a11y/aria_activedescendant_has_tabindex.rs b/crates/oxc_linter/src/rules/jsx_a11y/aria_activedescendant_has_tabindex.rs index 50f4603a32d90..5d7962c3d8f62 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/aria_activedescendant_has_tabindex.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/aria_activedescendant_has_tabindex.rs @@ -10,7 +10,7 @@ use crate::{ context::LintContext, globals::HTML_TAG, rule::Rule, - utils::{get_element_type, has_jsx_prop_lowercase, is_interactive_element, parse_jsx_value}, + utils::{get_element_type, has_jsx_prop_ignore_case, is_interactive_element, parse_jsx_value}, AstNode, }; @@ -62,7 +62,7 @@ impl Rule for AriaActivedescendantHasTabindex { return; }; - if has_jsx_prop_lowercase(jsx_opening_el, "aria-activedescendant").is_none() { + if has_jsx_prop_ignore_case(jsx_opening_el, "aria-activedescendant").is_none() { return; }; @@ -75,7 +75,7 @@ impl Rule for AriaActivedescendantHasTabindex { }; if let Some(JSXAttributeItem::Attribute(tab_index_attr)) = - has_jsx_prop_lowercase(jsx_opening_el, "tabIndex") + has_jsx_prop_ignore_case(jsx_opening_el, "tabIndex") { if !is_valid_tab_index_attr(tab_index_attr) { return; diff --git a/crates/oxc_linter/src/rules/jsx_a11y/autocomplete_valid.rs b/crates/oxc_linter/src/rules/jsx_a11y/autocomplete_valid.rs index 780d90b5feb88..1cef920b2afdf 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/autocomplete_valid.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/autocomplete_valid.rs @@ -10,7 +10,7 @@ use phf::{phf_map, phf_set}; use crate::{ context::LintContext, rule::Rule, - utils::{get_element_type, has_jsx_prop_lowercase}, + utils::{get_element_type, has_jsx_prop_ignore_case}, AstNode, }; @@ -183,7 +183,7 @@ impl Rule for AutocompleteValid { return; } - let Some(autocomplete_prop) = has_jsx_prop_lowercase(jsx_el, "autocomplete") else { + let Some(autocomplete_prop) = has_jsx_prop_ignore_case(jsx_el, "autocomplete") else { return; }; let attr = match autocomplete_prop { diff --git a/crates/oxc_linter/src/rules/jsx_a11y/html_has_lang.rs b/crates/oxc_linter/src/rules/jsx_a11y/html_has_lang.rs index 6cc62083aaae2..f4812f6c50f66 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/html_has_lang.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/html_has_lang.rs @@ -9,7 +9,7 @@ use oxc_span::Span; use crate::{ context::LintContext, rule::Rule, - utils::{get_element_type, get_prop_value, has_jsx_prop_lowercase}, + utils::{get_element_type, get_prop_value, has_jsx_prop_ignore_case}, AstNode, }; @@ -70,7 +70,7 @@ impl Rule for HtmlHasLang { return; }; - has_jsx_prop_lowercase(jsx_el, "lang").map_or_else( + has_jsx_prop_ignore_case(jsx_el, "lang").map_or_else( || ctx.diagnostic(missing_lang_prop(identifier.span)), |lang_prop| { if !is_valid_lang_prop(lang_prop) { diff --git a/crates/oxc_linter/src/rules/jsx_a11y/iframe_has_title.rs b/crates/oxc_linter/src/rules/jsx_a11y/iframe_has_title.rs index 6b9e973100e1d..23833b414cde9 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/iframe_has_title.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/iframe_has_title.rs @@ -9,7 +9,7 @@ use oxc_span::Span; use crate::{ context::LintContext, rule::Rule, - utils::{get_element_type, get_prop_value, has_jsx_prop_lowercase}, + utils::{get_element_type, get_prop_value, has_jsx_prop_ignore_case}, AstNode, }; @@ -75,7 +75,7 @@ impl Rule for IframeHasTitle { return; } - let Some(alt_prop) = has_jsx_prop_lowercase(jsx_el, "title") else { + let Some(alt_prop) = has_jsx_prop_ignore_case(jsx_el, "title") else { ctx.diagnostic(iframe_has_title_diagnostic(iden.span)); return; }; diff --git a/crates/oxc_linter/src/rules/jsx_a11y/img_redundant_alt.rs b/crates/oxc_linter/src/rules/jsx_a11y/img_redundant_alt.rs index 38f29cdd523ae..949cf907284c9 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/img_redundant_alt.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/img_redundant_alt.rs @@ -11,7 +11,7 @@ use crate::{ context::LintContext, rule::Rule, utils::{ - get_element_type, get_prop_value, has_jsx_prop_lowercase, is_hidden_from_screen_reader, + get_element_type, get_prop_value, has_jsx_prop_ignore_case, is_hidden_from_screen_reader, }, AstNode, }; @@ -120,7 +120,7 @@ impl Rule for ImgRedundantAlt { return; } - let Some(alt_prop) = has_jsx_prop_lowercase(jsx_el, "alt") else { + let Some(alt_prop) = has_jsx_prop_ignore_case(jsx_el, "alt") else { return; }; diff --git a/crates/oxc_linter/src/rules/jsx_a11y/lang.rs b/crates/oxc_linter/src/rules/jsx_a11y/lang.rs index 92b90016d8cbf..d8820c97472c0 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/lang.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/lang.rs @@ -10,7 +10,7 @@ use oxc_span::Span; use crate::{ context::LintContext, rule::Rule, - utils::{get_element_type, get_prop_value, has_jsx_prop_lowercase}, + utils::{get_element_type, get_prop_value, has_jsx_prop_ignore_case}, AstNode, }; @@ -75,7 +75,7 @@ impl Rule for Lang { return; }; - has_jsx_prop_lowercase(jsx_el, "lang").map_or_else( + has_jsx_prop_ignore_case(jsx_el, "lang").map_or_else( || ctx.diagnostic(lang_diagnostic(identifier.span)), |lang_prop| { if !is_valid_lang_prop(lang_prop) { diff --git a/crates/oxc_linter/src/rules/jsx_a11y/media_has_caption.rs b/crates/oxc_linter/src/rules/jsx_a11y/media_has_caption.rs index 5ca598a7fe8bc..27e12422687fe 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/media_has_caption.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/media_has_caption.rs @@ -149,7 +149,7 @@ impl Rule for MediaHasCaption { if let JSXAttributeName::Identifier(iden) = &attr.name { if let Some(JSXAttributeValue::StringLiteral(s)) = &attr.value { return iden.name == "kind" - && s.value.to_lowercase() == "captions"; + && s.value.eq_ignore_ascii_case("captions"); } } } diff --git a/crates/oxc_linter/src/rules/jsx_a11y/no_access_key.rs b/crates/oxc_linter/src/rules/jsx_a11y/no_access_key.rs index 19ab03f06ba4a..eeab4ae0caa4e 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/no_access_key.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/no_access_key.rs @@ -6,7 +6,7 @@ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::Span; -use crate::{context::LintContext, rule::Rule, utils::has_jsx_prop_lowercase, AstNode}; +use crate::{context::LintContext, rule::Rule, utils::has_jsx_prop_ignore_case, AstNode}; fn no_access_key_diagnostic(span0: Span) -> OxcDiagnostic { OxcDiagnostic::warn("No access key attribute allowed.") @@ -42,7 +42,8 @@ impl Rule for NoAccessKey { let AstKind::JSXOpeningElement(jsx_el) = node.kind() else { return; }; - if let Some(JSXAttributeItem::Attribute(attr)) = has_jsx_prop_lowercase(jsx_el, "accessKey") + if let Some(JSXAttributeItem::Attribute(attr)) = + has_jsx_prop_ignore_case(jsx_el, "accessKey") { match attr.value.as_ref() { Some(JSXAttributeValue::StringLiteral(_)) => { diff --git a/crates/oxc_linter/src/rules/jsx_a11y/no_aria_hidden_on_focusable.rs b/crates/oxc_linter/src/rules/jsx_a11y/no_aria_hidden_on_focusable.rs index 0ea76e63b6854..e2fac86a07b0b 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/no_aria_hidden_on_focusable.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/no_aria_hidden_on_focusable.rs @@ -9,7 +9,7 @@ use oxc_span::Span; use crate::{ context::LintContext, rule::Rule, - utils::{get_element_type, has_jsx_prop_lowercase, parse_jsx_value}, + utils::{get_element_type, has_jsx_prop_ignore_case, parse_jsx_value}, AstNode, }; @@ -46,7 +46,7 @@ impl Rule for NoAriaHiddenOnFocusable { let AstKind::JSXOpeningElement(jsx_el) = node.kind() else { return; }; - if let Some(aria_hidden_prop) = has_jsx_prop_lowercase(jsx_el, "aria-hidden") { + if let Some(aria_hidden_prop) = has_jsx_prop_ignore_case(jsx_el, "aria-hidden") { if is_aria_hidden_true(aria_hidden_prop) && is_focusable(ctx, jsx_el) { if let JSXAttributeItem::Attribute(boxed_attr) = aria_hidden_prop { ctx.diagnostic(no_aria_hidden_on_focusable_diagnostic(boxed_attr.span)); @@ -89,16 +89,16 @@ fn is_focusable(ctx: &LintContext, element: &JSXOpeningElement) -> bool { return false; }; - if let Some(JSXAttributeItem::Attribute(attr)) = has_jsx_prop_lowercase(element, "tabIndex") { + if let Some(JSXAttributeItem::Attribute(attr)) = has_jsx_prop_ignore_case(element, "tabIndex") { if let Some(attr_value) = &attr.value { return parse_jsx_value(attr_value).map_or(false, |num| num >= 0.0); } } match tag_name.as_str() { - "a" | "area" => has_jsx_prop_lowercase(element, "href").is_some(), + "a" | "area" => has_jsx_prop_ignore_case(element, "href").is_some(), "button" | "input" | "select" | "textarea" => { - has_jsx_prop_lowercase(element, "disabled").is_none() + has_jsx_prop_ignore_case(element, "disabled").is_none() } _ => false, } diff --git a/crates/oxc_linter/src/rules/jsx_a11y/no_redundant_roles.rs b/crates/oxc_linter/src/rules/jsx_a11y/no_redundant_roles.rs index f2d55e44c4533..a4542a8391ef8 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/no_redundant_roles.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/no_redundant_roles.rs @@ -10,7 +10,7 @@ use phf::phf_map; use crate::{ context::LintContext, rule::Rule, - utils::{get_element_type, has_jsx_prop_lowercase}, + utils::{get_element_type, has_jsx_prop_ignore_case}, AstNode, }; @@ -55,7 +55,7 @@ impl Rule for NoRedundantRoles { if let AstKind::JSXOpeningElement(jsx_el) = node.kind() { if let Some(component) = get_element_type(ctx, jsx_el) { if let Some(JSXAttributeItem::Attribute(attr)) = - has_jsx_prop_lowercase(jsx_el, "role") + has_jsx_prop_ignore_case(jsx_el, "role") { if let Some(JSXAttributeValue::StringLiteral(role_values)) = &attr.value { let roles: Vec = role_values diff --git a/crates/oxc_linter/src/rules/jsx_a11y/prefer_tag_over_role.rs b/crates/oxc_linter/src/rules/jsx_a11y/prefer_tag_over_role.rs index c28f48bfb25aa..0e43aae3130b1 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/prefer_tag_over_role.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/prefer_tag_over_role.rs @@ -11,7 +11,7 @@ use phf::phf_map; use crate::{ context::LintContext, rule::Rule, - utils::{get_element_type, has_jsx_prop_lowercase}, + utils::{get_element_type, has_jsx_prop_ignore_case}, AstNode, }; @@ -90,7 +90,7 @@ impl Rule for PreferTagOverRole { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { if let AstKind::JSXOpeningElement(jsx_el) = node.kind() { if let Some(name) = get_element_type(ctx, jsx_el) { - if let Some(role_prop) = has_jsx_prop_lowercase(jsx_el, "role") { + if let Some(role_prop) = has_jsx_prop_ignore_case(jsx_el, "role") { Self::check_roles(role_prop, &ROLE_TO_TAG_MAP, &name, ctx); } } diff --git a/crates/oxc_linter/src/rules/jsx_a11y/role_has_required_aria_props.rs b/crates/oxc_linter/src/rules/jsx_a11y/role_has_required_aria_props.rs index 263679e825a6a..d96c715ce8aaf 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/role_has_required_aria_props.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/role_has_required_aria_props.rs @@ -7,7 +7,7 @@ use oxc_macros::declare_oxc_lint; use oxc_span::Span; use phf::{phf_map, phf_set}; -use crate::{context::LintContext, rule::Rule, utils::has_jsx_prop_lowercase, AstNode}; +use crate::{context::LintContext, rule::Rule, utils::has_jsx_prop_ignore_case, AstNode}; fn role_has_required_aria_props_diagnostic(span: Span, role: &str, props: &str) -> OxcDiagnostic { OxcDiagnostic::warn(format!("`{role}` role is missing required aria props `{props}`.")) @@ -50,7 +50,7 @@ static ROLE_TO_REQUIRED_ARIA_PROPS: phf::Map<&'static str, phf::Set<&'static str impl Rule for RoleHasRequiredAriaProps { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { if let AstKind::JSXOpeningElement(jsx_el) = node.kind() { - let Some(role_prop) = has_jsx_prop_lowercase(jsx_el, "role") else { + let Some(role_prop) = has_jsx_prop_ignore_case(jsx_el, "role") else { return; }; let JSXAttributeItem::Attribute(attr) = role_prop else { @@ -63,7 +63,7 @@ impl Rule for RoleHasRequiredAriaProps { for role in roles { if let Some(props) = ROLE_TO_REQUIRED_ARIA_PROPS.get(role) { for prop in props { - if has_jsx_prop_lowercase(jsx_el, prop).is_none() { + if has_jsx_prop_ignore_case(jsx_el, prop).is_none() { ctx.diagnostic(role_has_required_aria_props_diagnostic( attr.span, role, prop, )); diff --git a/crates/oxc_linter/src/rules/jsx_a11y/role_supports_aria_props.rs b/crates/oxc_linter/src/rules/jsx_a11y/role_supports_aria_props.rs index 1c70f1e01a32e..d83eac629334b 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/role_supports_aria_props.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/role_supports_aria_props.rs @@ -13,7 +13,7 @@ use crate::{ rule::Rule, utils::{ get_element_type, get_jsx_attribute_name, get_string_literal_prop_value, - has_jsx_prop_lowercase, + has_jsx_prop_ignore_case, }, AstNode, }; @@ -63,7 +63,7 @@ impl Rule for RoleSupportsAriaProps { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { if let AstKind::JSXOpeningElement(jsx_el) = node.kind() { if let Some(el_type) = get_element_type(ctx, jsx_el) { - let role = has_jsx_prop_lowercase(jsx_el, "role"); + let role = has_jsx_prop_ignore_case(jsx_el, "role"); let role_value = role.map_or_else( || get_implicit_role(jsx_el, el_type.as_str()), |i| get_string_literal_prop_value(i), @@ -98,7 +98,7 @@ fn get_implicit_role<'a>( element_type: &str, ) -> Option<&'static str> { let implicit_role = match element_type { - "a" | "area" | "link" => match has_jsx_prop_lowercase(node, "href") { + "a" | "area" | "link" => match has_jsx_prop_ignore_case(node, "href") { Some(_) => "link", None => "", }, @@ -112,11 +112,11 @@ fn get_implicit_role<'a>( "form" => "form", "h1" | "h2" | "h3" | "h4" | "h5" | "h6" => "heading", "hr" => "separator", - "img" => has_jsx_prop_lowercase(node, "alt").map_or("img", |i| { + "img" => has_jsx_prop_ignore_case(node, "alt").map_or("img", |i| { get_string_literal_prop_value(i) .map_or("img", |v| if v.is_empty() { "" } else { "img" }) }), - "input" => has_jsx_prop_lowercase(node, "type").map_or("textbox", |input_type| { + "input" => has_jsx_prop_ignore_case(node, "type").map_or("textbox", |input_type| { match get_string_literal_prop_value(input_type) { Some("button" | "image" | "reset" | "submit") => "button", Some("checkbox") => "checkbox", @@ -126,21 +126,18 @@ fn get_implicit_role<'a>( } }), "li" => "listitem", - "menu" => has_jsx_prop_lowercase(node, "type").map_or("", |v| { + "menu" => has_jsx_prop_ignore_case(node, "type").map_or("", |v| { get_string_literal_prop_value(v) .map_or("", |v| if v == "toolbar" { "toolbar" } else { "" }) }), - "menuitem" => { - has_jsx_prop_lowercase(node, "type").map_or( - "", - |v| match get_string_literal_prop_value(v) { - Some("checkbox") => "menuitemcheckbox", - Some("command") => "menuitem", - Some("radio") => "menuitemradio", - _ => "", - }, - ) - } + "menuitem" => has_jsx_prop_ignore_case(node, "type").map_or("", |v| { + match get_string_literal_prop_value(v) { + Some("checkbox") => "menuitemcheckbox", + Some("command") => "menuitem", + Some("radio") => "menuitemradio", + _ => "", + } + }), "meter" | "progress" => "progressbar", "nav" => "navigation", "ol" | "ul" => "list", diff --git a/crates/oxc_linter/src/rules/jsx_a11y/scope.rs b/crates/oxc_linter/src/rules/jsx_a11y/scope.rs index a316f5ca078cc..ec7662aa6422a 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/scope.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/scope.rs @@ -7,7 +7,7 @@ use crate::{ context::LintContext, globals::HTML_TAG, rule::Rule, - utils::{get_element_type, has_jsx_prop_lowercase}, + utils::{get_element_type, has_jsx_prop_ignore_case}, AstNode, }; @@ -49,7 +49,7 @@ impl Rule for Scope { return; }; - let scope_attribute = match has_jsx_prop_lowercase(jsx_el, "scope") { + let scope_attribute = match has_jsx_prop_ignore_case(jsx_el, "scope") { Some(v) => match v { JSXAttributeItem::Attribute(attr) => attr, JSXAttributeItem::SpreadAttribute(_) => { diff --git a/crates/oxc_linter/src/rules/jsx_a11y/tabindex_no_positive.rs b/crates/oxc_linter/src/rules/jsx_a11y/tabindex_no_positive.rs index 77b6d6bcb2615..16dc7f9ab2018 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/tabindex_no_positive.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/tabindex_no_positive.rs @@ -6,7 +6,7 @@ use oxc_span::Span; use crate::{ context::LintContext, rule::Rule, - utils::{has_jsx_prop_lowercase, parse_jsx_value}, + utils::{has_jsx_prop_ignore_case, parse_jsx_value}, AstNode, }; @@ -44,7 +44,7 @@ impl Rule for TabindexNoPositive { let AstKind::JSXOpeningElement(jsx_el) = node.kind() else { return; }; - if let Some(tab_index_prop) = has_jsx_prop_lowercase(jsx_el, "tabIndex") { + if let Some(tab_index_prop) = has_jsx_prop_ignore_case(jsx_el, "tabIndex") { check_and_diagnose(tab_index_prop, ctx); } } diff --git a/crates/oxc_linter/src/rules/nextjs/google_font_display.rs b/crates/oxc_linter/src/rules/nextjs/google_font_display.rs index 29c5dcbaedc09..967c0befb1aa5 100644 --- a/crates/oxc_linter/src/rules/nextjs/google_font_display.rs +++ b/crates/oxc_linter/src/rules/nextjs/google_font_display.rs @@ -6,7 +6,7 @@ use oxc_span::{GetSpan, Span}; use crate::{ context::LintContext, rule::Rule, - utils::{get_string_literal_prop_value, has_jsx_prop_lowercase}, + utils::{get_string_literal_prop_value, has_jsx_prop_ignore_case}, AstNode, }; @@ -55,7 +55,7 @@ impl Rule for GoogleFontDisplay { return; } - let Some(href_prop) = has_jsx_prop_lowercase(jsx_opening_element, "href") else { + let Some(href_prop) = has_jsx_prop_ignore_case(jsx_opening_element, "href") else { return; }; diff --git a/crates/oxc_linter/src/rules/nextjs/google_font_preconnect.rs b/crates/oxc_linter/src/rules/nextjs/google_font_preconnect.rs index 2079728fbd8d8..ae9ad1061eab6 100644 --- a/crates/oxc_linter/src/rules/nextjs/google_font_preconnect.rs +++ b/crates/oxc_linter/src/rules/nextjs/google_font_preconnect.rs @@ -6,7 +6,7 @@ use oxc_span::Span; use crate::{ context::LintContext, rule::Rule, - utils::{get_string_literal_prop_value, has_jsx_prop_lowercase}, + utils::{get_string_literal_prop_value, has_jsx_prop_ignore_case}, AstNode, }; @@ -47,7 +47,7 @@ impl Rule for GoogleFontPreconnect { return; } - let Some(href_prop) = has_jsx_prop_lowercase(jsx_opening_element, "href") else { + let Some(href_prop) = has_jsx_prop_ignore_case(jsx_opening_element, "href") else { return; }; let Some(href_prop_value) = get_string_literal_prop_value(href_prop) else { @@ -55,7 +55,7 @@ impl Rule for GoogleFontPreconnect { }; let preconnect_missing = - has_jsx_prop_lowercase(jsx_opening_element, "rel").map_or(true, |rel_prop| { + has_jsx_prop_ignore_case(jsx_opening_element, "rel").map_or(true, |rel_prop| { let rel_prop_value = get_string_literal_prop_value(rel_prop); rel_prop_value != Some("preconnect") }); diff --git a/crates/oxc_linter/src/rules/nextjs/next_script_for_ga.rs b/crates/oxc_linter/src/rules/nextjs/next_script_for_ga.rs index 0ec5b6eee0f04..f013faeeecdaa 100644 --- a/crates/oxc_linter/src/rules/nextjs/next_script_for_ga.rs +++ b/crates/oxc_linter/src/rules/nextjs/next_script_for_ga.rs @@ -12,7 +12,7 @@ use oxc_span::Span; use crate::{ context::LintContext, rule::Rule, - utils::{get_string_literal_prop_value, has_jsx_prop_lowercase}, + utils::{get_string_literal_prop_value, has_jsx_prop_ignore_case}, AstNode, }; @@ -58,7 +58,7 @@ impl Rule for NextScriptForGa { // Check if the Alternative async tag is being used to add GA. // https://developers.google.com/analytics/devguides/collection/analyticsjs#alternative_async_tag // https://developers.google.com/analytics/devguides/collection/gtagjs - if let Some(src_prop) = has_jsx_prop_lowercase(jsx_opening_element, "src") { + if let Some(src_prop) = has_jsx_prop_ignore_case(jsx_opening_element, "src") { if let Some(src_prop_value) = get_string_literal_prop_value(src_prop) { if SUPPORTED_SRCS.iter().any(|s| src_prop_value.contains(s)) { ctx.diagnostic(next_script_for_ga_diagnostic(jsx_opening_element_name.span)); @@ -92,7 +92,7 @@ fn get_dangerously_set_inner_html_prop_value<'a>( jsx_opening_element: &'a JSXOpeningElement<'a>, ) -> Option<&'a ObjectProperty<'a>> { let Some(JSXAttributeItem::Attribute(dangerously_set_inner_html_prop)) = - has_jsx_prop_lowercase(jsx_opening_element, "dangerouslysetinnerhtml") + has_jsx_prop_ignore_case(jsx_opening_element, "dangerouslysetinnerhtml") else { return None; }; diff --git a/crates/oxc_linter/src/rules/react/button_has_type.rs b/crates/oxc_linter/src/rules/react/button_has_type.rs index 6d35d1eac0d79..5fd224a820d9c 100644 --- a/crates/oxc_linter/src/rules/react/button_has_type.rs +++ b/crates/oxc_linter/src/rules/react/button_has_type.rs @@ -12,7 +12,7 @@ use oxc_span::{GetSpan, Span}; use crate::{ context::LintContext, rule::Rule, - utils::{get_prop_value, has_jsx_prop_lowercase, is_create_element_call}, + utils::{get_prop_value, has_jsx_prop_ignore_case, is_create_element_call}, AstNode, }; @@ -77,7 +77,7 @@ impl Rule for ButtonHasType { return; } - has_jsx_prop_lowercase(jsx_el, "type").map_or_else( + has_jsx_prop_ignore_case(jsx_el, "type").map_or_else( || { ctx.diagnostic(missing_type_prop(identifier.span)); }, diff --git a/crates/oxc_linter/src/rules/react/jsx_no_target_blank.rs b/crates/oxc_linter/src/rules/react/jsx_no_target_blank.rs index 7c1a50aedbfe6..da88005911830 100644 --- a/crates/oxc_linter/src/rules/react/jsx_no_target_blank.rs +++ b/crates/oxc_linter/src/rules/react/jsx_no_target_blank.rs @@ -317,7 +317,7 @@ fn check_rel_val(str: &StringLiteral, allow_referrer: bool) -> bool { false }); } - splits.any(|str| str.to_lowercase() == "noreferrer") + splits.any(|str| str.eq_ignore_ascii_case("noreferrer")) } fn match_rel_expression<'a>( @@ -367,7 +367,7 @@ fn match_target_expression<'a>(expr: &'a Expression<'a>) -> (bool, &'a str, bool let default = (false, "", false, false); match expr { Expression::StringLiteral(str) => { - (str.value.as_str().to_lowercase() == "_blank", "", false, false) + (str.value.eq_ignore_ascii_case("_blank"), "", false, false) } Expression::ConditionalExpression(expr) => { let consequent = match_target_expression(&expr.consequent); @@ -390,7 +390,7 @@ fn check_target<'a>(attribute_value: &'a JSXAttributeValue<'a>) -> (bool, &'a st let default = (false, "", false, false); match attribute_value { JSXAttributeValue::StringLiteral(str) => { - (str.value.as_str().to_lowercase() == "_blank", "", false, false) + (str.value.eq_ignore_ascii_case("_blank"), "", false, false) } JSXAttributeValue::ExpressionContainer(expr) => { if let Some(expr) = expr.expression.as_expression() { diff --git a/crates/oxc_linter/src/rules/react/no_unknown_property.rs b/crates/oxc_linter/src/rules/react/no_unknown_property.rs index bd2c55cebd913..875acc12328dc 100644 --- a/crates/oxc_linter/src/rules/react/no_unknown_property.rs +++ b/crates/oxc_linter/src/rules/react/no_unknown_property.rs @@ -436,7 +436,7 @@ fn is_valid_data_attr(name: &str) -> bool { fn normalize_attribute_case(name: &str) -> &str { DOM_PROPERTIES_IGNORE_CASE .iter() - .find(|camel_name| camel_name.to_lowercase() == name.to_lowercase()) + .find(|camel_name| camel_name.eq_ignore_ascii_case(name)) .unwrap_or(&name) } fn has_uppercase(name: &str) -> bool { diff --git a/crates/oxc_linter/src/rules/unicorn/text_encoding_identifier_case.rs b/crates/oxc_linter/src/rules/unicorn/text_encoding_identifier_case.rs index d795f30fc4495..11a7cc22d1170 100644 --- a/crates/oxc_linter/src/rules/unicorn/text_encoding_identifier_case.rs +++ b/crates/oxc_linter/src/rules/unicorn/text_encoding_identifier_case.rs @@ -103,7 +103,7 @@ fn is_jsx_meta_elem_with_charset_attr(id: AstNodeId, ctx: &LintContext) -> bool let JSXAttributeName::Identifier(ident) = &jsx_attr.name else { return false; }; - if ident.name.to_lowercase() != "charset" { + if !ident.name.eq_ignore_ascii_case("charset") { return false; } @@ -116,7 +116,7 @@ fn is_jsx_meta_elem_with_charset_attr(id: AstNodeId, ctx: &LintContext) -> bool return false; }; - if name.name.to_lowercase() != "meta" { + if !name.name.eq_ignore_ascii_case("meta") { return false; } diff --git a/crates/oxc_linter/src/utils/react.rs b/crates/oxc_linter/src/utils/react.rs index 4d166581dba05..bfd7247ac9c3a 100644 --- a/crates/oxc_linter/src/utils/react.rs +++ b/crates/oxc_linter/src/utils/react.rs @@ -37,7 +37,7 @@ pub fn has_jsx_prop<'a, 'b>( }) } -pub fn has_jsx_prop_lowercase<'a, 'b>( +pub fn has_jsx_prop_ignore_case<'a, 'b>( node: &'b JSXOpeningElement<'a>, target_prop: &'b str, ) -> Option<&'b JSXAttributeItem<'a>> { @@ -48,7 +48,7 @@ pub fn has_jsx_prop_lowercase<'a, 'b>( return false; }; - name.name.as_str().to_lowercase() == target_prop.to_lowercase() + name.name.as_str().eq_ignore_ascii_case(target_prop) } }) } @@ -84,7 +84,7 @@ pub fn get_string_literal_prop_value<'a>(item: &'a JSXAttributeItem<'_>) -> Opti pub fn is_hidden_from_screen_reader(ctx: &LintContext, node: &JSXOpeningElement) -> bool { if let Some(name) = get_element_type(ctx, node) { if name.as_str().to_uppercase() == "INPUT" { - if let Some(item) = has_jsx_prop_lowercase(node, "type") { + if let Some(item) = has_jsx_prop_ignore_case(node, "type") { let hidden = get_string_literal_prop_value(item); if hidden.is_some_and(|val| val.to_uppercase() == "HIDDEN") { @@ -94,7 +94,7 @@ pub fn is_hidden_from_screen_reader(ctx: &LintContext, node: &JSXOpeningElement) } } - has_jsx_prop_lowercase(node, "aria-hidden").map_or(false, |v| match get_prop_value(v) { + has_jsx_prop_ignore_case(node, "aria-hidden").map_or(false, |v| match get_prop_value(v) { None => true, Some(JSXAttributeValue::StringLiteral(s)) if s.value == "true" => true, Some(JSXAttributeValue::ExpressionContainer(container)) => { @@ -118,8 +118,8 @@ pub fn object_has_accessible_child(ctx: &LintContext, node: &JSXElement<'_>) -> && !container.expression.is_undefined() } _ => false, - }) || has_jsx_prop_lowercase(&node.opening_element, "dangerouslySetInnerHTML").is_some() - || has_jsx_prop_lowercase(&node.opening_element, "children").is_some() + }) || has_jsx_prop_ignore_case(&node.opening_element, "dangerouslySetInnerHTML").is_some() + || has_jsx_prop_ignore_case(&node.opening_element, "children").is_some() } pub fn is_presentation_role(jsx_opening_el: &JSXOpeningElement) -> bool { @@ -247,7 +247,7 @@ pub fn get_element_type(context: &LintContext, element: &JSXOpeningElement) -> O .polymorphic_prop_name .as_ref() .and_then(|polymorphic_prop_name_value| { - has_jsx_prop_lowercase(element, polymorphic_prop_name_value) + has_jsx_prop_ignore_case(element, polymorphic_prop_name_value) }) .and_then(get_prop_value) .and_then(|prop_value| match prop_value {