From 703bc8ad96ff9e5cc7d99f7f050a7328fcc811ae Mon Sep 17 00:00:00 2001 From: shulaoda Date: Sat, 14 Dec 2024 22:33:44 +0800 Subject: [PATCH] feat: enhance get_element_type to resolve more element types --- .../oxc_linter/src/rules/jsx_a11y/alt_text.rs | 5 +- .../rules/jsx_a11y/anchor_ambiguous_text.rs | 19 ++++---- .../src/rules/jsx_a11y/anchor_has_content.rs | 5 +- .../src/rules/jsx_a11y/anchor_is_valid.rs | 5 +- .../aria_activedescendant_has_tabindex.rs | 4 +- .../src/rules/jsx_a11y/aria_role.rs | 4 +- .../jsx_a11y/aria_unsupported_elements.rs | 4 +- .../src/rules/jsx_a11y/autocomplete_valid.rs | 5 +- .../jsx_a11y/click_events_have_key_events.rs | 5 +- .../src/rules/jsx_a11y/heading_has_content.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 | 5 +- .../jsx_a11y/label_has_associated_control.rs | 29 ++++++----- crates/oxc_linter/src/rules/jsx_a11y/lang.rs | 4 +- .../src/rules/jsx_a11y/media_has_caption.rs | 9 ++-- .../jsx_a11y/mouse_events_have_key_events.rs | 4 +- .../jsx_a11y/no_aria_hidden_on_focusable.rs | 4 +- .../src/rules/jsx_a11y/no_autofocus.rs | 5 +- .../rules/jsx_a11y/no_distracting_elements.rs | 5 +- .../src/rules/jsx_a11y/no_redundant_roles.rs | 5 +- .../rules/jsx_a11y/prefer_tag_over_role.rs | 7 ++- .../jsx_a11y/role_supports_aria_props.rs | 5 +- crates/oxc_linter/src/rules/jsx_a11y/scope.rs | 4 +- .../checked_requires_onchange_or_readonly.rs | 5 +- ...jsx_a11y_label_has_associated_control.snap | 7 +++ crates/oxc_linter/src/utils/react.rs | 48 +++++++++++++------ 27 files changed, 103 insertions(+), 111 deletions(-) diff --git a/crates/oxc_linter/src/rules/jsx_a11y/alt_text.rs b/crates/oxc_linter/src/rules/jsx_a11y/alt_text.rs index af8b630d79872..5bea6556e2ff4 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/alt_text.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/alt_text.rs @@ -172,9 +172,8 @@ impl Rule for AltText { let AstKind::JSXOpeningElement(jsx_el) = node.kind() else { return; }; - let Some(name) = &get_element_type(ctx, jsx_el) else { - return; - }; + + let name = &get_element_type(ctx, jsx_el); // if let Some(custom_tags) = &self.img { diff --git a/crates/oxc_linter/src/rules/jsx_a11y/anchor_ambiguous_text.rs b/crates/oxc_linter/src/rules/jsx_a11y/anchor_ambiguous_text.rs index 49c3316f0ebb8..1a04fdfdcf8b1 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/anchor_ambiguous_text.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/anchor_ambiguous_text.rs @@ -107,9 +107,7 @@ impl Rule for AnchorAmbiguousText { return; }; - let Some(name) = get_element_type(ctx, &jsx_el.opening_element) else { - return; - }; + let name = get_element_type(ctx, &jsx_el.opening_element); if name != "a" { return; @@ -167,15 +165,14 @@ fn get_accessible_text<'a, 'b>( }; } - if let Some(name) = get_element_type(ctx, &jsx_el.opening_element) { - if name == "img" { - if let Some(alt_text) = has_jsx_prop_ignore_case(&jsx_el.opening_element, "alt") { - if let Some(text) = get_string_literal_prop_value(alt_text) { - return Some(Cow::Borrowed(text)); - }; + let name = get_element_type(ctx, &jsx_el.opening_element); + if name == "img" { + if let Some(alt_text) = has_jsx_prop_ignore_case(&jsx_el.opening_element, "alt") { + if let Some(text) = get_string_literal_prop_value(alt_text) { + return Some(Cow::Borrowed(text)); }; - } - }; + }; + } if is_hidden_from_screen_reader(ctx, &jsx_el.opening_element) { return None; 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 5a73ece9db63b..18dfa6c782367 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 @@ -64,9 +64,8 @@ declare_oxc_lint!( impl Rule for AnchorHasContent { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { if let AstKind::JSXElement(jsx_el) = node.kind() { - let Some(name) = &get_element_type(ctx, &jsx_el.opening_element) else { - return; - }; + let name = get_element_type(ctx, &jsx_el.opening_element); + if name == "a" { if is_hidden_from_screen_reader(ctx, &jsx_el.opening_element) { 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 92663980089a7..f53e598040bf0 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 @@ -129,9 +129,8 @@ impl Rule for AnchorIsValid { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { if let AstKind::JSXElement(jsx_el) = node.kind() { - let Some(name) = &get_element_type(ctx, &jsx_el.opening_element) else { - return; - }; + let name = get_element_type(ctx, &jsx_el.opening_element); + if name != "a" { return; }; 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 908e84ad5b167..84c276c9e8ef6 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 @@ -64,9 +64,7 @@ impl Rule for AriaActivedescendantHasTabindex { return; }; - let Some(element_type) = get_element_type(ctx, jsx_opening_el) else { - return; - }; + let element_type = get_element_type(ctx, jsx_opening_el); if !HTML_TAG.contains(&element_type) { return; diff --git a/crates/oxc_linter/src/rules/jsx_a11y/aria_role.rs b/crates/oxc_linter/src/rules/jsx_a11y/aria_role.rs index 7b0712f7fef8f..91376a4398903 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/aria_role.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/aria_role.rs @@ -143,9 +143,7 @@ impl Rule for AriaRole { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { if let AstKind::JSXElement(jsx_el) = node.kind() { if let Some(aria_role) = has_jsx_prop(&jsx_el.opening_element, "role") { - let Some(element_type) = get_element_type(ctx, &jsx_el.opening_element) else { - return; - }; + let element_type = get_element_type(ctx, &jsx_el.opening_element); if self.ignore_non_dom && !HTML_TAG.contains(&element_type) { return; diff --git a/crates/oxc_linter/src/rules/jsx_a11y/aria_unsupported_elements.rs b/crates/oxc_linter/src/rules/jsx_a11y/aria_unsupported_elements.rs index 9a8a9a2d9287b..4a4b933247998 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/aria_unsupported_elements.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/aria_unsupported_elements.rs @@ -49,9 +49,7 @@ fn aria_unsupported_elements_diagnostic(span: Span, x1: &str) -> OxcDiagnostic { impl Rule for AriaUnsupportedElements { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { if let AstKind::JSXOpeningElement(jsx_el) = node.kind() { - let Some(el_type) = get_element_type(ctx, jsx_el) else { - return; - }; + let el_type = get_element_type(ctx, jsx_el); if RESERVED_HTML_TAG.contains(&el_type) { for attr in &jsx_el.attributes { let attr = match attr { 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 5f87714f7bd81..e230ef0fc95a4 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/autocomplete_valid.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/autocomplete_valid.rs @@ -181,9 +181,8 @@ impl Rule for AutocompleteValid { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { if let AstKind::JSXOpeningElement(jsx_el) = node.kind() { - let Some(name) = &get_element_type(ctx, jsx_el) else { - return; - }; + let name = &get_element_type(ctx, jsx_el); + if !self.input_components.contains(name.as_ref()) { return; } diff --git a/crates/oxc_linter/src/rules/jsx_a11y/click_events_have_key_events.rs b/crates/oxc_linter/src/rules/jsx_a11y/click_events_have_key_events.rs index af9e24015cb87..fe26185a59743 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/click_events_have_key_events.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/click_events_have_key_events.rs @@ -59,9 +59,8 @@ impl Rule for ClickEventsHaveKeyEvents { }; // Check only native DOM elements or custom component via settings - let Some(element_type) = get_element_type(ctx, jsx_opening_el) else { - return; - }; + let element_type = get_element_type(ctx, jsx_opening_el); + if !HTML_TAG.contains(&element_type) { return; }; diff --git a/crates/oxc_linter/src/rules/jsx_a11y/heading_has_content.rs b/crates/oxc_linter/src/rules/jsx_a11y/heading_has_content.rs index c01549f8ff3f9..cc6954e614f7f 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/heading_has_content.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/heading_has_content.rs @@ -89,9 +89,7 @@ impl Rule for HeadingHasContent { // }; // let name = iden.name.as_str(); - let Some(name) = &get_element_type(ctx, jsx_el) else { - return; - }; + let name = &get_element_type(ctx, jsx_el); if !DEFAULT_COMPONENTS.iter().any(|&comp| comp == name) && !self 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 bff4a6dbf5e14..53b22b91058b9 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 @@ -61,9 +61,7 @@ impl Rule for HtmlHasLang { return; }; - let Some(element_type) = get_element_type(ctx, jsx_el) else { - return; - }; + let element_type = get_element_type(ctx, jsx_el); if element_type != "html" { return; 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 244d40902b2ce..99695e766ac38 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 @@ -66,9 +66,7 @@ impl Rule for IframeHasTitle { return; }; - let Some(name) = get_element_type(ctx, jsx_el) else { - return; - }; + let name = get_element_type(ctx, jsx_el); if name != "iframe" { 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 ef502c5a66cc1..cfcf3c36ba41b 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 @@ -125,9 +125,8 @@ impl Rule for ImgRedundantAlt { let AstKind::JSXOpeningElement(jsx_el) = node.kind() else { return; }; - let Some(element_type) = get_element_type(ctx, jsx_el) else { - return; - }; + + let element_type = get_element_type(ctx, jsx_el); if !self.types_to_validate.iter().any(|comp| comp == &element_type) { return; diff --git a/crates/oxc_linter/src/rules/jsx_a11y/label_has_associated_control.rs b/crates/oxc_linter/src/rules/jsx_a11y/label_has_associated_control.rs index 4167d6d1918bc..79ab8f2601d96 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/label_has_associated_control.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/label_has_associated_control.rs @@ -206,9 +206,7 @@ impl Rule for LabelHasAssociatedControl { return; }; - let Some(element_type) = get_element_type(ctx, &element.opening_element) else { - return; - }; + let element_type = get_element_type(ctx, &element.opening_element); if self.label_components.binary_search(&element_type.into()).is_err() { return; @@ -295,10 +293,9 @@ impl LabelHasAssociatedControl { match node { JSXChild::ExpressionContainer(_) => true, JSXChild::Element(element) => { - if let Some(element_type) = get_element_type(ctx, &element.opening_element) { - if self.control_components.is_match(element_type.to_string()) { - return true; - } + let element_type = get_element_type(ctx, &element.opening_element); + if self.control_components.is_match(element_type.to_string()) { + return true; } for child in &element.children { @@ -359,12 +356,11 @@ impl LabelHasAssociatedControl { } if element.children.is_empty() { - if let Some(name) = get_element_type(ctx, &element.opening_element) { - if is_react_component_name(&name) - && !self.control_components.is_match(name.to_string()) - { - return true; - } + let name = get_element_type(ctx, &element.opening_element); + if is_react_component_name(&name) + && !self.control_components.is_match(name.to_string()) + { + return true; } } @@ -1589,6 +1585,13 @@ fn test() { }])), None, ), + ( + "", + Some(serde_json::json!([{ + "labelComponents": ["FilesContext.Provider"], + }])), + None, + ), ]; Tester::new(LabelHasAssociatedControl::NAME, LabelHasAssociatedControl::CATEGORY, pass, fail) diff --git a/crates/oxc_linter/src/rules/jsx_a11y/lang.rs b/crates/oxc_linter/src/rules/jsx_a11y/lang.rs index 9ee3e5f3aa78c..306d894fd7b9a 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/lang.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/lang.rs @@ -63,9 +63,7 @@ impl Rule for Lang { return; }; - let Some(element_type) = get_element_type(ctx, jsx_el) else { - return; - }; + let element_type = get_element_type(ctx, jsx_el); if element_type != "html" { 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 65044d46a5419..03abb3d446c1b 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 @@ -110,9 +110,7 @@ impl Rule for MediaHasCaption { return; }; - let Some(element_name) = get_element_type(ctx, jsx_el) else { - return; - }; + let element_name = get_element_type(ctx, jsx_el); let is_audio_or_video = self.0.audio.contains(&element_name) || self.0.video.contains(&element_name); @@ -158,9 +156,8 @@ impl Rule for MediaHasCaption { } else { parent.children.iter().any(|child| match child { JSXChild::Element(child_el) => { - let Some(child_name) = get_element_type(ctx, &child_el.opening_element) else { - return false; - }; + let child_name = get_element_type(ctx, &child_el.opening_element); + self.0.track.contains(&child_name) && child_el.opening_element.attributes.iter().any(|attr| { if let JSXAttributeItem::Attribute(attr) = attr { diff --git a/crates/oxc_linter/src/rules/jsx_a11y/mouse_events_have_key_events.rs b/crates/oxc_linter/src/rules/jsx_a11y/mouse_events_have_key_events.rs index 9faa759c6d43d..7017a22a2cfdc 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/mouse_events_have_key_events.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/mouse_events_have_key_events.rs @@ -102,9 +102,7 @@ impl Rule for MouseEventsHaveKeyEvents { return; }; - let Some(el_type) = get_element_type(ctx, jsx_opening_el) else { - return; - }; + let el_type = get_element_type(ctx, jsx_opening_el); if !HTML_TAG.contains(&el_type) { return; 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 1fb2f6b1b92f3..41fc2159cd604 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 @@ -92,9 +92,7 @@ fn is_aria_hidden_true(attr: &JSXAttributeItem) -> bool { /// /// `true` if the element is focusable, `false` otherwise. fn is_focusable<'a>(ctx: &LintContext<'a>, element: &JSXOpeningElement<'a>) -> bool { - let Some(tag_name) = get_element_type(ctx, element) else { - return false; - }; + let tag_name = get_element_type(ctx, element); if let Some(JSXAttributeItem::Attribute(attr)) = has_jsx_prop_ignore_case(element, "tabIndex") { if let Some(attr_value) = &attr.value { diff --git a/crates/oxc_linter/src/rules/jsx_a11y/no_autofocus.rs b/crates/oxc_linter/src/rules/jsx_a11y/no_autofocus.rs index 3cc9f0579cb9e..2114081f3f5f4 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/no_autofocus.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/no_autofocus.rs @@ -100,9 +100,8 @@ impl Rule for NoAutofocus { let Some(autofocus) = has_jsx_prop(&jsx_el.opening_element, "autoFocus") else { return; }; - let Some(element_type) = get_element_type(ctx, &jsx_el.opening_element) else { - return; - }; + + let element_type = get_element_type(ctx, &jsx_el.opening_element); if self.ignore_non_dom { if HTML_TAG.contains(&element_type) { 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 1d980ed1a9f35..7dcbfafec8d1c 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 @@ -58,9 +58,8 @@ impl Rule for NoDistractingElements { let AstKind::JSXOpeningElement(jsx_el) = node.kind() else { return; }; - let Some(element_type) = get_element_type(ctx, jsx_el) else { - return; - }; + + let element_type = get_element_type(ctx, jsx_el); if let "marquee" | "blink" = element_type.as_ref() { ctx.diagnostic(no_distracting_elements_diagnostic(jsx_el.name.span())); 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 6fda1ca69c918..cf4dc6c3d7fd3 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 @@ -61,9 +61,8 @@ impl Rule for NoRedundantRoles { let AstKind::JSXOpeningElement(jsx_el) = node.kind() else { return; }; - let Some(component) = get_element_type(ctx, jsx_el) else { - return; - }; + + let component = get_element_type(ctx, jsx_el); if let Some(JSXAttributeItem::Attribute(attr)) = has_jsx_prop_ignore_case(jsx_el, "role") { if let Some(JSXAttributeValue::StringLiteral(role_values)) = &attr.value { 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 4271ce04acc6d..00a7f897d9d33 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 @@ -92,10 +92,9 @@ lazy_static! { 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_ignore_case(jsx_el, "role") { - Self::check_roles(role_prop, &ROLE_TO_TAG_MAP, &name, ctx); - } + let name = get_element_type(ctx, jsx_el); + 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_supports_aria_props.rs b/crates/oxc_linter/src/rules/jsx_a11y/role_supports_aria_props.rs index fec0865ad4cab..884fa9b32f128 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 @@ -68,9 +68,8 @@ impl Rule for RoleSupportsAriaProps { let AstKind::JSXOpeningElement(jsx_el) = node.kind() else { return; }; - let Some(el_type) = get_element_type(ctx, jsx_el) else { - return; - }; + + let 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( diff --git a/crates/oxc_linter/src/rules/jsx_a11y/scope.rs b/crates/oxc_linter/src/rules/jsx_a11y/scope.rs index 070ae8e4f9fa6..8026c1cff3fb8 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/scope.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/scope.rs @@ -65,9 +65,7 @@ impl Rule for Scope { } }; - let Some(element_type) = get_element_type(ctx, jsx_el) else { - return; - }; + let element_type = get_element_type(ctx, jsx_el); if element_type == "th" { return; diff --git a/crates/oxc_linter/src/rules/react/checked_requires_onchange_or_readonly.rs b/crates/oxc_linter/src/rules/react/checked_requires_onchange_or_readonly.rs index 280b8dd8faa48..6247c1143188d 100644 --- a/crates/oxc_linter/src/rules/react/checked_requires_onchange_or_readonly.rs +++ b/crates/oxc_linter/src/rules/react/checked_requires_onchange_or_readonly.rs @@ -69,9 +69,8 @@ impl Rule for CheckedRequiresOnchangeOrReadonly { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { match node.kind() { AstKind::JSXOpeningElement(jsx_opening_el) => { - let Some(element_type) = get_element_type(ctx, jsx_opening_el) else { - return; - }; + let element_type = get_element_type(ctx, jsx_opening_el); + if element_type != "input" { return; } diff --git a/crates/oxc_linter/src/snapshots/jsx_a11y_label_has_associated_control.snap b/crates/oxc_linter/src/snapshots/jsx_a11y_label_has_associated_control.snap index d1d6c78b40ff8..90815e73a3c94 100644 --- a/crates/oxc_linter/src/snapshots/jsx_a11y_label_has_associated_control.snap +++ b/crates/oxc_linter/src/snapshots/jsx_a11y_label_has_associated_control.snap @@ -616,3 +616,10 @@ snapshot_kind: text · ──────────────────────────────────── ╰──── help: Either give the label a `htmlFor` attribute with the id of the associated control, or wrap the label around the control. + + ⚠ eslint-plugin-jsx-a11y(label-has-associated-control): A form label must have accessible text. + ╭─[label_has_associated_control.tsx:1:1] + 1 │ + · ─────────────────────────────────────────────────────── + ╰──── + help: Ensure the label either has text inside it or is accessibly labelled using an attribute such as `aria-label`, or `aria-labelledby`. You can mark more attributes as accessible labels by configuring the `labelAttributes` option. diff --git a/crates/oxc_linter/src/utils/react.rs b/crates/oxc_linter/src/utils/react.rs index ff9bf38f79a62..eeee8425a75cd 100644 --- a/crates/oxc_linter/src/utils/react.rs +++ b/crates/oxc_linter/src/utils/react.rs @@ -3,7 +3,8 @@ use std::borrow::Cow; use oxc_ast::{ ast::{ CallExpression, Expression, JSXAttributeItem, JSXAttributeName, JSXAttributeValue, - JSXChild, JSXElement, JSXExpression, JSXOpeningElement, MemberExpression, + JSXChild, JSXElement, JSXElementName, JSXExpression, JSXMemberExpression, + JSXMemberExpressionObject, JSXOpeningElement, MemberExpression, }, match_member_expression, AstKind, }; @@ -65,14 +66,13 @@ 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.eq_ignore_ascii_case("input") { - if let Some(item) = has_jsx_prop_ignore_case(node, "type") { - let hidden = get_string_literal_prop_value(item); + let name = get_element_type(ctx, node); + 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.eq_ignore_ascii_case("hidden")) { - return true; - } + if hidden.is_some_and(|val| val.eq_ignore_ascii_case("hidden")) { + return true; } } } @@ -204,14 +204,34 @@ pub fn get_parent_component<'a, 'b>( None } +fn get_jsx_mem_expr_name<'a>(jsx_mem_expr: &JSXMemberExpression) -> Cow<'a, str> { + let prefix = match &jsx_mem_expr.object { + JSXMemberExpressionObject::IdentifierReference(id) => Cow::Borrowed(id.name.as_str()), + JSXMemberExpressionObject::MemberExpression(mem_expr) => { + Cow::Owned(format!("{}.{}", get_jsx_mem_expr_name(mem_expr), mem_expr.property.name)) + } + JSXMemberExpressionObject::ThisExpression(_) => Cow::Borrowed("this"), + }; + + Cow::Owned(format!("{}.{}", prefix, jsx_mem_expr.property.name)) +} + /// Resolve element type(name) using jsx-a11y settings /// ref: /// pub fn get_element_type<'c, 'a>( context: &'c LintContext<'a>, element: &JSXOpeningElement<'a>, -) -> Option> { - let name = element.name.get_identifier_name()?; +) -> Cow<'c, str> { + let name = match &element.name { + JSXElementName::Identifier(id) => Cow::Borrowed(id.as_ref().name.as_str()), + JSXElementName::IdentifierReference(id) => Cow::Borrowed(id.as_ref().name.as_str()), + JSXElementName::NamespacedName(namespaced) => { + Cow::Owned(format!("{}:{}", namespaced.namespace.name, namespaced.property.name)) + } + JSXElementName::MemberExpression(jsx_mem_expr) => get_jsx_mem_expr_name(jsx_mem_expr), + JSXElementName::ThisExpression(_) => Cow::Borrowed("this"), + }; let OxlintSettings { jsx_a11y, .. } = context.settings(); @@ -225,10 +245,10 @@ pub fn get_element_type<'c, 'a>( .and_then(JSXAttributeValue::as_string_literal) .map(|s| s.value.as_str()); - let raw_type = polymorphic_prop.unwrap_or_else(|| name.as_str()); - match jsx_a11y.components.get(raw_type) { - Some(component) => Some(Cow::Borrowed(component)), - None => Some(Cow::Borrowed(raw_type)), + let raw_type = polymorphic_prop.map_or(name, Cow::Borrowed); + match jsx_a11y.components.get(raw_type.as_ref()) { + Some(component) => Cow::Borrowed(component), + None => raw_type, } }