perf(linter): change react rules and utils to use Cow and CompactStr instead of String (#4603)

This commit is contained in:
DonIsaac 2024-08-03 21:26:08 +00:00
parent fd2d9dafcd
commit 6ff200d072
18 changed files with 215 additions and 123 deletions

1
Cargo.lock generated
View file

@ -1753,6 +1753,7 @@ dependencies = [
"compact_str",
"miette",
"oxc_allocator",
"schemars",
"serde",
"tsify",
"wasm-bindgen",

View file

@ -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 }

View file

@ -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"));
}

View file

@ -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>,
}

View file

@ -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);
}

View file

@ -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

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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()

View file

@ -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));
}
}

View file

@ -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();

View file

@ -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() {

View file

@ -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(

View file

@ -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(_))
}

View file

@ -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, ()> {

View file

@ -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"]

View file

@ -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;

View file

@ -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 }