From 6ff200d072fe55cb720c8dbe3a00f885fd1bbd28 Mon Sep 17 00:00:00 2001 From: DonIsaac <22823424+DonIsaac@users.noreply.github.com> Date: Sat, 3 Aug 2024 21:26:08 +0000 Subject: [PATCH] perf(linter): change react rules and utils to use `Cow` and `CompactStr` instead of `String` (#4603) --- Cargo.lock | 1 + crates/oxc_linter/Cargo.toml | 2 +- crates/oxc_linter/src/config/mod.rs | 6 ++- .../src/config/settings/jsx_a11y.rs | 5 +- crates/oxc_linter/src/config/settings/mod.rs | 32 +++++++---- .../oxc_linter/src/config/settings/react.rs | 47 +++++++++------- .../src/rules/jsx_a11y/autocomplete_valid.rs | 38 ++++++------- .../src/rules/jsx_a11y/media_has_caption.rs | 53 ++++++++++++------- .../jsx_a11y/no_aria_hidden_on_focusable.rs | 4 +- .../rules/jsx_a11y/no_distracting_elements.rs | 4 +- .../jsx_a11y/role_supports_aria_props.rs | 2 +- .../src/rules/react/jsx_no_target_blank.rs | 8 +-- .../src/rules/react/no_unknown_property.rs | 14 ++--- .../src/rules/typescript/no_this_alias.rs | 45 +++++++++------- crates/oxc_linter/src/utils/react.rs | 53 ++++++++++++------- crates/oxc_span/Cargo.toml | 2 + crates/oxc_span/src/atom.rs | 20 +++++++ tasks/benchmark/Cargo.toml | 2 +- 18 files changed, 215 insertions(+), 123 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74b20a859..0ec04c940 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1753,6 +1753,7 @@ dependencies = [ "compact_str", "miette", "oxc_allocator", + "schemars", "serde", "tsify", "wasm-bindgen", diff --git a/crates/oxc_linter/Cargo.toml b/crates/oxc_linter/Cargo.toml index c0ae2bb42..f5a3222cc 100644 --- a/crates/oxc_linter/Cargo.toml +++ b/crates/oxc_linter/Cargo.toml @@ -22,7 +22,7 @@ doctest = false [dependencies] oxc_allocator = { workspace = true } oxc_parser = { workspace = true } -oxc_span = { workspace = true } +oxc_span = { workspace = true, features = ["schemars"] } oxc_ast = { workspace = true } oxc_cfg = { workspace = true } oxc_diagnostics = { workspace = true } diff --git a/crates/oxc_linter/src/config/mod.rs b/crates/oxc_linter/src/config/mod.rs index db9580fee..8adb2871c 100644 --- a/crates/oxc_linter/src/config/mod.rs +++ b/crates/oxc_linter/src/config/mod.rs @@ -187,6 +187,7 @@ fn transform_rule_and_plugin_name<'a>( mod test { use std::env; + use oxc_span::CompactStr; use rustc_hash::FxHashSet; use serde::Deserialize; @@ -235,7 +236,10 @@ mod test { let OxlintConfig { rules, settings, env, globals } = config.unwrap(); assert!(!rules.is_empty()); - assert_eq!(settings.jsx_a11y.polymorphic_prop_name, Some("role".to_string())); + assert_eq!( + settings.jsx_a11y.polymorphic_prop_name.as_ref().map(CompactStr::as_str), + Some("role") + ); assert_eq!(env.iter().count(), 1); assert!(globals.is_enabled("foo")); } diff --git a/crates/oxc_linter/src/config/settings/jsx_a11y.rs b/crates/oxc_linter/src/config/settings/jsx_a11y.rs index dab21aa64..6632311a8 100644 --- a/crates/oxc_linter/src/config/settings/jsx_a11y.rs +++ b/crates/oxc_linter/src/config/settings/jsx_a11y.rs @@ -1,3 +1,4 @@ +use oxc_span::CompactStr; use rustc_hash::FxHashMap; use schemars::JsonSchema; use serde::Deserialize; @@ -6,7 +7,7 @@ use serde::Deserialize; #[derive(Debug, Deserialize, Default, JsonSchema)] pub struct JSXA11yPluginSettings { #[serde(rename = "polymorphicPropName")] - pub polymorphic_prop_name: Option, + pub polymorphic_prop_name: Option, #[serde(default)] - pub components: FxHashMap, + pub components: FxHashMap, } diff --git a/crates/oxc_linter/src/config/settings/mod.rs b/crates/oxc_linter/src/config/settings/mod.rs index f0b93fb3c..e3ff653e8 100644 --- a/crates/oxc_linter/src/config/settings/mod.rs +++ b/crates/oxc_linter/src/config/settings/mod.rs @@ -30,10 +30,21 @@ pub struct OxlintSettings { #[cfg(test)] mod test { + use std::borrow::Cow; + + use oxc_span::CompactStr; use serde::Deserialize; + use crate::config::settings::react::ComponentAttrs; + use super::OxlintSettings; + fn as_attrs, I: IntoIterator>( + attrs: I, + ) -> ComponentAttrs<'static> { + ComponentAttrs::from(Cow::Owned(attrs.into_iter().map(Into::into).collect())) + } + #[test] fn test_parse_settings() { let settings = OxlintSettings::deserialize(&serde_json::json!({ @@ -62,21 +73,24 @@ mod test { })) .unwrap(); - assert_eq!(settings.jsx_a11y.polymorphic_prop_name, Some("role".to_string())); - assert_eq!(settings.jsx_a11y.components.get("Link"), Some(&"Anchor".to_string())); + assert_eq!(settings.jsx_a11y.polymorphic_prop_name, Some("role".into())); + assert_eq!(settings.jsx_a11y.components.get("Link"), Some(&"Anchor".into())); assert!(settings.next.get_root_dirs().contains(&"app".to_string())); - assert_eq!(settings.react.get_form_component_attrs("CustomForm"), Some(vec![])); assert_eq!( - settings.react.get_form_component_attrs("SimpleForm"), - Some(vec!["endpoint".to_string()]) + settings.react.get_form_component_attrs("CustomForm").unwrap(), + as_attrs::(vec![]) ); assert_eq!( - settings.react.get_form_component_attrs("Form"), - Some(vec!["registerEndpoint".to_string(), "loginEndpoint".to_string()]) + settings.react.get_form_component_attrs("SimpleForm").unwrap(), + as_attrs(["endpoint"]) ); assert_eq!( - settings.react.get_link_component_attrs("Link"), - Some(vec!["to".to_string(), "href".to_string()]) + settings.react.get_form_component_attrs("Form").unwrap(), + as_attrs(["registerEndpoint", "loginEndpoint"]) + ); + assert_eq!( + settings.react.get_link_component_attrs("Link").unwrap(), + as_attrs(["to", "href"]) ); assert_eq!(settings.react.get_link_component_attrs("Noop"), None); } diff --git a/crates/oxc_linter/src/config/settings/react.rs b/crates/oxc_linter/src/config/settings/react.rs index d1efbfe5e..71fec9c94 100644 --- a/crates/oxc_linter/src/config/settings/react.rs +++ b/crates/oxc_linter/src/config/settings/react.rs @@ -1,3 +1,6 @@ +use std::borrow::Cow; + +use oxc_span::CompactStr; use schemars::JsonSchema; use serde::Deserialize; @@ -14,12 +17,13 @@ pub struct ReactPluginSettings { // TODO: More properties should be added } +pub type ComponentAttrs<'c> = Cow<'c, Vec>; impl ReactPluginSettings { - pub fn get_form_component_attrs(&self, name: &str) -> Option> { + pub fn get_form_component_attrs(&self, name: &str) -> Option> { get_component_attrs_by_name(&self.form_components, name) } - pub fn get_link_component_attrs(&self, name: &str) -> Option> { + pub fn get_link_component_attrs(&self, name: &str) -> Option> { get_component_attrs_by_name(&self.link_components, name) } } @@ -29,35 +33,40 @@ impl ReactPluginSettings { #[derive(Clone, Debug, Deserialize, JsonSchema)] #[serde(untagged)] enum CustomComponent { - NameOnly(String), + NameOnly(CompactStr), ObjectWithOneAttr { - name: String, + name: CompactStr, #[serde(alias = "formAttribute", alias = "linkAttribute")] - attribute: String, + attribute: CompactStr, }, ObjectWithManyAttrs { - name: String, + name: CompactStr, #[serde(alias = "formAttribute", alias = "linkAttribute")] - attributes: Vec, + attributes: Vec, }, } -fn get_component_attrs_by_name( - components: &Vec, +fn get_component_attrs_by_name<'c>( + components: &'c Vec, name: &str, -) -> Option> { +) -> Option> { for item in components { - let comp = match item { - CustomComponent::NameOnly(name) => (name, vec![]), - CustomComponent::ObjectWithOneAttr { name, attribute } => { - (name, vec![attribute.to_string()]) + match item { + CustomComponent::NameOnly(comp_name) if comp_name == name => { + return Some(Cow::Owned(vec![])) } - CustomComponent::ObjectWithManyAttrs { name, attributes } => (name, attributes.clone()), + CustomComponent::ObjectWithOneAttr { name: comp_name, attribute } + if comp_name == name => + { + return Some(Cow::Owned(vec![attribute.clone()])); + } + CustomComponent::ObjectWithManyAttrs { name: comp_name, attributes } + if comp_name == name => + { + return Some(Cow::Borrowed(attributes)); + } + _ => {} }; - - if comp.0 == name { - return Some(comp.1); - } } None 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 1cef920b2..044234469 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/autocomplete_valid.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/autocomplete_valid.rs @@ -4,8 +4,10 @@ use oxc_ast::{ }; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; -use oxc_span::Span; +use oxc_span::{CompactStr, Span}; use phf::{phf_map, phf_set}; +use rustc_hash::FxHashSet; +use serde_json::Value; use crate::{ context::LintContext, @@ -43,7 +45,7 @@ declare_oxc_lint!( #[derive(Debug, Clone, PartialEq, Eq)] pub struct AutocompleteValidConfig { - input_components: Vec, + input_components: FxHashSet, } impl std::ops::Deref for AutocompleteValid { @@ -56,7 +58,7 @@ impl std::ops::Deref for AutocompleteValid { impl std::default::Default for AutocompleteValidConfig { fn default() -> Self { - Self { input_components: vec!["input".to_string()] } + Self { input_components: FxHashSet::from_iter([CompactStr::new("input")]) } } } @@ -157,21 +159,21 @@ fn is_valid_autocomplete_value(value: &str) -> bool { } impl Rule for AutocompleteValid { - fn from_configuration(value: serde_json::Value) -> Self { - let mut input_components: Vec = vec!["input".to_string()]; - if let Some(config) = value.get(0) { - if let Some(serde_json::Value::Array(components)) = config.get("inputComponents") { - input_components = components + fn from_configuration(config: Value) -> Self { + config + .get(0) + .and_then(|c| c.get("inputComponents")) + .and_then(Value::as_array) + .map(|components| { + components .iter() - .filter_map(|c| c.as_str().map(std::string::ToString::to_string)) - .collect(); - } - } - - // Add default input component - input_components.push("input".to_string()); - - Self(Box::new(AutocompleteValidConfig { input_components })) + .filter_map(Value::as_str) + .map(CompactStr::from) + .chain(Some("input".into())) + .collect() + }) + .map(|input_components| Self(Box::new(AutocompleteValidConfig { input_components }))) + .unwrap_or_default() } fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { @@ -179,7 +181,7 @@ impl Rule for AutocompleteValid { let Some(name) = &get_element_type(ctx, jsx_el) else { return; }; - if !self.input_components.contains(name) { + if !self.input_components.contains(name.as_ref()) { return; } 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 27e124226..621580d09 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 @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use oxc_ast::{ ast::{JSXAttributeItem, JSXAttributeName, JSXAttributeValue, JSXChild, JSXExpression}, AstKind, @@ -5,6 +7,7 @@ use oxc_ast::{ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::Span; +use serde_json::Value; use crate::{context::LintContext, rule::Rule, utils::get_element_type, AstNode}; @@ -19,17 +22,17 @@ pub struct MediaHasCaption(Box); #[derive(Debug, Clone)] pub struct MediaHasCaptionConfig { - audio: Vec, - video: Vec, - track: Vec, + audio: Vec>, + video: Vec>, + track: Vec>, } impl Default for MediaHasCaptionConfig { fn default() -> Self { Self { - audio: vec!["audio".to_string()], - video: vec!["video".to_string()], - track: vec!["track".to_string()], + audio: vec![Cow::Borrowed("audio")], + video: vec![Cow::Borrowed("video")], + track: vec![Cow::Borrowed("track")], } } } @@ -58,26 +61,38 @@ declare_oxc_lint!( ); impl Rule for MediaHasCaption { - fn from_configuration(value: serde_json::Value) -> Self { + fn from_configuration(value: Value) -> Self { let mut config = MediaHasCaptionConfig::default(); if let Some(arr) = value.as_array() { for v in arr { if let serde_json::Value::Object(rule_config) = v { - if let Some(audio) = rule_config.get("audio").and_then(|v| v.as_array()) { - config - .audio - .extend(audio.iter().filter_map(|v| v.as_str().map(String::from))); + if let Some(audio) = rule_config.get("audio").and_then(Value::as_array) { + config.audio.extend( + audio + .iter() + .filter_map(Value::as_str) + .map(String::from) + .map(Into::into), + ); } - if let Some(video) = rule_config.get("video").and_then(|v| v.as_array()) { - config - .video - .extend(video.iter().filter_map(|v| v.as_str().map(String::from))); + if let Some(video) = rule_config.get("video").and_then(Value::as_array) { + config.video.extend( + video + .iter() + .filter_map(Value::as_str) + .map(String::from) + .map(Into::into), + ); } - if let Some(track) = rule_config.get("track").and_then(|v| v.as_array()) { - config - .track - .extend(track.iter().filter_map(|v| v.as_str().map(String::from))); + if let Some(track) = rule_config.get("track").and_then(Value::as_array) { + config.track.extend( + track + .iter() + .filter_map(Value::as_str) + .map(String::from) + .map(Into::into), + ); } break; } 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 e2fac86a0..57c1edc35 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 @@ -84,7 +84,7 @@ fn is_aria_hidden_true(attr: &JSXAttributeItem) -> bool { /// # Returns /// /// `true` if the element is focusable, `false` otherwise. -fn is_focusable(ctx: &LintContext, element: &JSXOpeningElement) -> bool { +fn is_focusable<'a>(ctx: &LintContext<'a>, element: &JSXOpeningElement<'a>) -> bool { let Some(tag_name) = get_element_type(ctx, element) else { return false; }; @@ -95,7 +95,7 @@ fn is_focusable(ctx: &LintContext, element: &JSXOpeningElement) -> bool { } } - match tag_name.as_str() { + match tag_name.as_ref() { "a" | "area" => has_jsx_prop_ignore_case(element, "href").is_some(), "button" | "input" | "select" | "textarea" => { has_jsx_prop_ignore_case(element, "disabled").is_none() diff --git a/crates/oxc_linter/src/rules/jsx_a11y/no_distracting_elements.rs b/crates/oxc_linter/src/rules/jsx_a11y/no_distracting_elements.rs index 98ef5baa9..faf44d9c2 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/no_distracting_elements.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/no_distracting_elements.rs @@ -60,9 +60,7 @@ impl Rule for NoDistractingElements { return; }; - let name = element_type.as_str(); - - if let "marquee" | "blink" = name { + if let "marquee" | "blink" = element_type.as_ref() { ctx.diagnostic(no_distracting_elements_diagnostic(iden.span)); } } 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 d83eac629..c99ecec7e 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 @@ -65,7 +65,7 @@ impl Rule for RoleSupportsAriaProps { if let Some(el_type) = get_element_type(ctx, jsx_el) { 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()), + || get_implicit_role(jsx_el, &el_type), |i| get_string_literal_prop_value(i), ); let is_implicit = role_value.is_some() && role.is_none(); 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 da8800591..4997c7cbf 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 @@ -9,7 +9,7 @@ use oxc_ast::{ }; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; -use oxc_span::{GetSpan, Span}; +use oxc_span::{CompactStr, GetSpan, Span}; use crate::{context::LintContext, rule::Rule, AstNode}; @@ -150,14 +150,16 @@ impl Rule for JsxNoTargetBlank { .react .get_link_component_attrs(tag_name) .map_or(false, |link_attribute| { - link_attribute.contains(&attribute_name.to_string()) + link_attribute + .contains(&CompactStr::new(attribute_name)) }) || ctx .settings() .react .get_form_component_attrs(tag_name) .map_or(false, |form_attribute| { - form_attribute.contains(&attribute_name.to_string()) + form_attribute + .contains(&CompactStr::new(attribute_name)) }) { if let Some(val) = attribute.value.as_ref() { 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 875acc123..ca9d42941 100644 --- a/crates/oxc_linter/src/rules/react/no_unknown_property.rs +++ b/crates/oxc_linter/src/rules/react/no_unknown_property.rs @@ -1,4 +1,4 @@ -use std::collections::{hash_map::HashMap, hash_set::HashSet}; +use std::{borrow::Cow, collections::hash_map::HashMap}; use itertools::Itertools; use once_cell::sync::Lazy; @@ -11,6 +11,7 @@ use oxc_macros::declare_oxc_lint; use oxc_span::{GetSpan, Span}; use phf::{phf_map, phf_set, Map, Set}; use regex::Regex; +use rustc_hash::FxHashSet; use serde::Deserialize; use crate::{context::LintContext, rule::Rule, utils::get_jsx_attribute_name, AstNode}; @@ -48,7 +49,7 @@ pub struct NoUnknownProperty(Box); #[serde(rename_all = "camelCase")] pub struct NoUnknownPropertyConfig { #[serde(default)] - ignore: HashSet, + ignore: FxHashSet>, #[serde(default)] require_data_lowercase: bool, } @@ -72,6 +73,7 @@ declare_oxc_lint!( NoUnknownProperty, restriction ); + const ATTRIBUTE_TAGS_MAP: Map<&'static str, Set<&'static str>> = phf_map! { "abbr" => phf_set! {"th", "td"}, "charset" => phf_set! {"meta"}, @@ -491,16 +493,16 @@ impl Rule for NoUnknownProperty { if self.0.ignore.contains(&(actual_name)) { return; }; - if is_valid_data_attr(actual_name.as_str()) { - if self.0.require_data_lowercase && has_uppercase(actual_name.as_str()) { + if is_valid_data_attr(&actual_name) { + if self.0.require_data_lowercase && has_uppercase(&actual_name) { ctx.diagnostic(data_lowercase_required(span, &actual_name.to_lowercase())); } return; }; - if ARIA_PROPERTIES.contains(actual_name.as_str()) || !is_valid_html_tag { + if ARIA_PROPERTIES.contains(&actual_name) || !is_valid_html_tag { return; }; - let name = normalize_attribute_case(actual_name.as_str()); + let name = normalize_attribute_case(&actual_name); if let Some(tags) = ATTRIBUTE_TAGS_MAP.get(name) { if !tags.contains(el_type) { ctx.diagnostic(invalid_prop_on_tag( diff --git a/crates/oxc_linter/src/rules/typescript/no_this_alias.rs b/crates/oxc_linter/src/rules/typescript/no_this_alias.rs index b8cd28825..3f7eb0b78 100644 --- a/crates/oxc_linter/src/rules/typescript/no_this_alias.rs +++ b/crates/oxc_linter/src/rules/typescript/no_this_alias.rs @@ -5,6 +5,8 @@ use oxc_ast::{ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::{CompactStr, GetSpan, Span}; +use rustc_hash::FxHashSet; +use serde_json::Value; use crate::{context::LintContext, rule::Rule, AstNode}; @@ -28,7 +30,7 @@ pub struct NoThisAlias(Box); #[derive(Debug, Clone)] pub struct NoThisAliasConfig { allow_destructuring: bool, - allow_names: Vec, + allow_names: FxHashSet, } impl std::ops::Deref for NoThisAlias { @@ -41,7 +43,12 @@ impl std::ops::Deref for NoThisAlias { impl Default for NoThisAliasConfig { fn default() -> Self { - Self { allow_destructuring: true, allow_names: vec![] } + Self { allow_destructuring: true, allow_names: FxHashSet::default() } + } +} +impl NoThisAlias { + fn is_allowed(&self, name: &str) -> bool { + self.allow_names.contains(name) } } @@ -66,21 +73,20 @@ declare_oxc_lint!( impl Rule for NoThisAlias { fn from_configuration(value: serde_json::Value) -> Self { let obj = value.get(0); - let allowed_names = value + let allowed_names: FxHashSet = value .get(0) .and_then(|v| v.get("allow_names")) - .and_then(serde_json::Value::as_array) + .and_then(Value::as_array) .unwrap_or(&vec![]) .iter() - .map(serde_json::Value::as_str) - .filter(std::option::Option::is_some) - .map(|x| CompactStr::from(x.unwrap())) - .collect::>(); + .filter_map(Value::as_str) + .map(CompactStr::from) + .collect(); Self(Box::new(NoThisAliasConfig { allow_destructuring: obj .and_then(|v| v.get("allow_destructuring")) - .and_then(serde_json::Value::as_bool) + .and_then(Value::as_bool) .unwrap_or_default(), allow_names: allowed_names, })) @@ -102,7 +108,7 @@ impl Rule for NoThisAlias { } if let BindingPatternKind::BindingIdentifier(identifier) = &decl.id.kind { - if !self.allow_names.iter().any(|s| s.as_str() == identifier.name.as_str()) { + if !self.is_allowed(&identifier.name) { ctx.diagnostic(no_this_alias_diagnostic(identifier.span)); } @@ -123,19 +129,20 @@ impl Rule for NoThisAlias { ctx.diagnostic(no_this_destructure_diagnostic(left.span())); } AssignmentTarget::AssignmentTargetIdentifier(id) => { - if !self.allow_names.iter().any(|s| s.as_str() == id.name.as_str()) { + if !self.is_allowed(&id.name) { ctx.diagnostic(no_this_alias_diagnostic(id.span)); } } left @ match_simple_assignment_target!(AssignmentTarget) => { let pat = left.to_simple_assignment_target(); - if let Some(expr) = pat.get_expression() { - if let Some(id) = expr.get_identifier_reference() { - if !self.allow_names.iter().any(|s| s.as_str() == id.name.as_str()) - { - ctx.diagnostic(no_this_alias_diagnostic(id.span)); - } - } + let Some(expr) = pat.get_expression() else { + return; + }; + let Some(id) = expr.get_identifier_reference() else { + return; + }; + if !self.is_allowed(&id.name) { + ctx.diagnostic(no_this_alias_diagnostic(id.span)); } } } @@ -143,11 +150,13 @@ impl Rule for NoThisAlias { _ => {} } } + fn should_run(&self, ctx: &LintContext) -> bool { ctx.source_type().is_typescript() } } +#[inline] fn rhs_is_this_reference(rhs_expression: &Expression) -> bool { matches!(rhs_expression, Expression::ThisExpression(_)) } diff --git a/crates/oxc_linter/src/utils/react.rs b/crates/oxc_linter/src/utils/react.rs index bfd7247ac..734dad139 100644 --- a/crates/oxc_linter/src/utils/react.rs +++ b/crates/oxc_linter/src/utils/react.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use oxc_ast::{ ast::{ CallExpression, Expression, JSXAttributeItem, JSXAttributeName, JSXAttributeValue, @@ -10,15 +12,16 @@ use oxc_semantic::{AstNode, SymbolFlags}; use crate::{LintContext, OxlintSettings}; pub fn is_create_element_call(call_expr: &CallExpression) -> bool { - if let Some(member_expr) = call_expr.callee.get_member_expr() { - return member_expr.static_property_name() == Some("createElement"); + match &call_expr.callee { + Expression::StaticMemberExpression(member_expr) => { + member_expr.property.name == "createElement" + } + Expression::ComputedMemberExpression(member_expr) => { + member_expr.static_property_name().is_some_and(|name| name == "createElement") + } + Expression::Identifier(ident) => ident.name == "createElement", + _ => false, } - - if let Some(ident) = call_expr.callee.get_identifier_reference() { - return ident.name == "createElement"; - } - - false } pub fn has_jsx_prop<'a, 'b>( @@ -32,7 +35,7 @@ pub fn has_jsx_prop<'a, 'b>( return false; }; - name.name.as_str() == target_prop + name.name == target_prop } }) } @@ -48,7 +51,7 @@ pub fn has_jsx_prop_ignore_case<'a, 'b>( return false; }; - name.name.as_str().eq_ignore_ascii_case(target_prop) + name.name.eq_ignore_ascii_case(target_prop) } }) } @@ -61,12 +64,12 @@ pub fn get_prop_value<'a, 'b>(item: &'b JSXAttributeItem<'a>) -> Option<&'b JSXA } } -pub fn get_jsx_attribute_name(attr: &JSXAttributeName) -> String { +pub fn get_jsx_attribute_name<'a>(attr: &JSXAttributeName<'a>) -> Cow<'a, str> { match attr { JSXAttributeName::NamespacedName(name) => { - format!("{}:{}", name.namespace.name, name.property.name) + Cow::Owned(format!("{}:{}", name.namespace.name, name.property.name)) } - JSXAttributeName::Identifier(ident) => ident.name.to_string(), + JSXAttributeName::Identifier(ident) => Cow::Borrowed(ident.name.as_str()), } } @@ -81,13 +84,16 @@ pub fn get_string_literal_prop_value<'a>(item: &'a JSXAttributeItem<'_>) -> Opti } // ref: https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/src/util/isHiddenFromScreenReader.js -pub fn is_hidden_from_screen_reader(ctx: &LintContext, node: &JSXOpeningElement) -> bool { +pub fn is_hidden_from_screen_reader<'a>( + ctx: &LintContext<'a>, + node: &JSXOpeningElement<'a>, +) -> bool { if let Some(name) = get_element_type(ctx, node) { - if name.as_str().to_uppercase() == "INPUT" { + if name.eq_ignore_ascii_case("input") { 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") { + if hidden.is_some_and(|val| val.eq_ignore_ascii_case("hidden")) { return true; } } @@ -109,7 +115,7 @@ pub fn is_hidden_from_screen_reader(ctx: &LintContext, node: &JSXOpeningElement) } // ref: https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/src/util/hasAccessibleChild.js -pub fn object_has_accessible_child(ctx: &LintContext, node: &JSXElement<'_>) -> bool { +pub fn object_has_accessible_child<'a>(ctx: &LintContext<'a>, node: &JSXElement<'a>) -> bool { node.children.iter().any(|child| match child { JSXChild::Text(text) => !text.value.is_empty(), JSXChild::Element(el) => !is_hidden_from_screen_reader(ctx, &el.opening_element), @@ -150,7 +156,7 @@ pub fn is_interactive_element(element_type: &str, jsx_opening_el: &JSXOpeningEle "input" => { if let Some(input_type) = has_jsx_prop(jsx_opening_el, "type") { if get_string_literal_prop_value(input_type) - .is_some_and(|val| val.to_uppercase() == "HIDDEN") + .is_some_and(|val| val.eq_ignore_ascii_case("hidden")) { return false; } @@ -236,7 +242,10 @@ pub fn get_parent_es6_component<'a, 'b>(ctx: &'b LintContext<'a>) -> Option<&'b /// Resolve element type(name) using jsx-a11y settings /// ref: /// -pub fn get_element_type(context: &LintContext, element: &JSXOpeningElement) -> Option { +pub fn get_element_type<'c, 'a>( + context: &'c LintContext<'a>, + element: &JSXOpeningElement<'a>, +) -> Option> { let JSXElementName::Identifier(ident) = &element.name else { return None; }; @@ -256,7 +265,11 @@ pub fn get_element_type(context: &LintContext, element: &JSXOpeningElement) -> O }); let raw_type = polymorphic_prop.unwrap_or_else(|| ident.name.as_str()); - Some(String::from(jsx_a11y.components.get(raw_type).map_or(raw_type, |c| c))) + match jsx_a11y.components.get(raw_type) { + Some(component) => Some(Cow::Borrowed(component)), + None => Some(Cow::Borrowed(raw_type)), + } + // Some(String::from(jsx_a11y.components.get(raw_type).map_or(raw_type, |c| c))) } pub fn parse_jsx_value(value: &JSXAttributeValue) -> Result { diff --git a/crates/oxc_span/Cargo.toml b/crates/oxc_span/Cargo.toml index 6dd803b43..71df68894 100644 --- a/crates/oxc_span/Cargo.toml +++ b/crates/oxc_span/Cargo.toml @@ -28,7 +28,9 @@ compact_str = { workspace = true } tsify = { workspace = true, optional = true } wasm-bindgen = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"], optional = true } +schemars = { workspace = true, optional = true } [features] default = [] serialize = ["compact_str/serde", "dep:serde", "dep:tsify", "dep:wasm-bindgen"] +schemars = ["dep:schemars"] diff --git a/crates/oxc_span/src/atom.rs b/crates/oxc_span/src/atom.rs index 6985001f5..f81bd59a0 100644 --- a/crates/oxc_span/src/atom.rs +++ b/crates/oxc_span/src/atom.rs @@ -191,6 +191,7 @@ impl<'a> fmt::Display for Atom<'a> { /// Currently implemented as just a wrapper around [`compact_str::CompactString`], /// but will be reduced in size with a custom implementation later. #[derive(Clone, Eq)] +#[cfg_attr(feature = "serialize", derive(serde::Deserialize))] pub struct CompactStr(CompactString); impl CompactStr { @@ -389,6 +390,25 @@ impl Serialize for CompactStr { } } +#[cfg(feature = "schemars")] +impl schemars::JsonSchema for CompactStr { + fn is_referenceable() -> bool { + false + } + + fn schema_name() -> std::string::String { + "String".to_string() + } + + fn schema_id() -> Cow<'static, str> { + Cow::Borrowed("String") + } + + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + <&str>::json_schema(gen) + } +} + #[cfg(test)] mod test { use super::CompactStr; diff --git a/tasks/benchmark/Cargo.toml b/tasks/benchmark/Cargo.toml index 0ae885809..d345d40b9 100644 --- a/tasks/benchmark/Cargo.toml +++ b/tasks/benchmark/Cargo.toml @@ -74,7 +74,7 @@ oxc_minifier = { workspace = true, optional = true } oxc_parser = { workspace = true, features = ["benchmarking"], optional = true } oxc_prettier = { workspace = true, optional = true } oxc_semantic = { workspace = true, optional = true } -oxc_span = { workspace = true, optional = true } +oxc_span = { workspace = true, optional = true, features = ["serialize", "schemars"] } oxc_tasks_common = { workspace = true, optional = true } oxc_transformer = { workspace = true, optional = true } oxc_codegen = { workspace = true, optional = true }