mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
perf(linter): change react rules and utils to use Cow and CompactStr instead of String (#4603)
This commit is contained in:
parent
fd2d9dafcd
commit
6ff200d072
18 changed files with 215 additions and 123 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1753,6 +1753,7 @@ dependencies = [
|
|||
"compact_str",
|
||||
"miette",
|
||||
"oxc_allocator",
|
||||
"schemars",
|
||||
"serde",
|
||||
"tsify",
|
||||
"wasm-bindgen",
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
pub polymorphic_prop_name: Option<CompactStr>,
|
||||
#[serde(default)]
|
||||
pub components: FxHashMap<String, String>,
|
||||
pub components: FxHashMap<CompactStr, CompactStr>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<S: Into<CompactStr>, I: IntoIterator<Item = S>>(
|
||||
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::<CompactStr, _>(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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<CompactStr>>;
|
||||
impl ReactPluginSettings {
|
||||
pub fn get_form_component_attrs(&self, name: &str) -> Option<Vec<String>> {
|
||||
pub fn get_form_component_attrs(&self, name: &str) -> Option<ComponentAttrs<'_>> {
|
||||
get_component_attrs_by_name(&self.form_components, name)
|
||||
}
|
||||
|
||||
pub fn get_link_component_attrs(&self, name: &str) -> Option<Vec<String>> {
|
||||
pub fn get_link_component_attrs(&self, name: &str) -> Option<ComponentAttrs<'_>> {
|
||||
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<String>,
|
||||
attributes: Vec<CompactStr>,
|
||||
},
|
||||
}
|
||||
|
||||
fn get_component_attrs_by_name(
|
||||
components: &Vec<CustomComponent>,
|
||||
fn get_component_attrs_by_name<'c>(
|
||||
components: &'c Vec<CustomComponent>,
|
||||
name: &str,
|
||||
) -> Option<Vec<String>> {
|
||||
) -> Option<ComponentAttrs<'c>> {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
input_components: FxHashSet<CompactStr>,
|
||||
}
|
||||
|
||||
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<String> = 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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<MediaHasCaptionConfig>);
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MediaHasCaptionConfig {
|
||||
audio: Vec<String>,
|
||||
video: Vec<String>,
|
||||
track: Vec<String>,
|
||||
audio: Vec<Cow<'static, str>>,
|
||||
video: Vec<Cow<'static, str>>,
|
||||
track: Vec<Cow<'static, str>>,
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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<NoUnknownPropertyConfig>);
|
|||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NoUnknownPropertyConfig {
|
||||
#[serde(default)]
|
||||
ignore: HashSet<String>,
|
||||
ignore: FxHashSet<Cow<'static, str>>,
|
||||
#[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(
|
||||
|
|
|
|||
|
|
@ -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<NoThisAliasConfig>);
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct NoThisAliasConfig {
|
||||
allow_destructuring: bool,
|
||||
allow_names: Vec<CompactStr>,
|
||||
allow_names: FxHashSet<CompactStr>,
|
||||
}
|
||||
|
||||
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<CompactStr> = 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::<Vec<CompactStr>>();
|
||||
.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(_))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
/// <https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/src/util/getElementType.js>
|
||||
pub fn get_element_type(context: &LintContext, element: &JSXOpeningElement) -> Option<String> {
|
||||
pub fn get_element_type<'c, 'a>(
|
||||
context: &'c LintContext<'a>,
|
||||
element: &JSXOpeningElement<'a>,
|
||||
) -> Option<Cow<'c, str>> {
|
||||
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<f64, ()> {
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
Loading…
Reference in a new issue