Skip to content

Commit

Permalink
feat(linter): enhance get_element_type to resolve more element types (
Browse files Browse the repository at this point in the history
#7885)

I think it should if makes sense.

_Originally posted by @Boshen in
#7881 (comment)
            

Since `jsx-a11y` supports these names, I have aligned them accordingly.
  • Loading branch information
shulaoda authored Dec 14, 2024
1 parent 7637aac commit ee26b44
Show file tree
Hide file tree
Showing 27 changed files with 103 additions and 111 deletions.
5 changes: 2 additions & 3 deletions crates/oxc_linter/src/rules/jsx_a11y/alt_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

// <img>
if let Some(custom_tags) = &self.img {
Expand Down
19 changes: 8 additions & 11 deletions crates/oxc_linter/src/rules/jsx_a11y/anchor_ambiguous_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
5 changes: 2 additions & 3 deletions crates/oxc_linter/src/rules/jsx_a11y/anchor_has_content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 2 additions & 3 deletions crates/oxc_linter/src/rules/jsx_a11y/anchor_is_valid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 1 addition & 3 deletions crates/oxc_linter/src/rules/jsx_a11y/aria_role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 2 additions & 3 deletions crates/oxc_linter/src/rules/jsx_a11y/autocomplete_valid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down
4 changes: 1 addition & 3 deletions crates/oxc_linter/src/rules/jsx_a11y/heading_has_content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 1 addition & 3 deletions crates/oxc_linter/src/rules/jsx_a11y/html_has_lang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 1 addition & 3 deletions crates/oxc_linter/src/rules/jsx_a11y/iframe_has_title.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 2 additions & 3 deletions crates/oxc_linter/src/rules/jsx_a11y/img_redundant_alt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -1589,6 +1585,13 @@ fn test() {
}])),
None,
),
(
"<FilesContext.Provider value={{ addAlert, cwdInfo }} />",
Some(serde_json::json!([{
"labelComponents": ["FilesContext.Provider"],
}])),
None,
),
];

Tester::new(LabelHasAssociatedControl::NAME, LabelHasAssociatedControl::CATEGORY, pass, fail)
Expand Down
4 changes: 1 addition & 3 deletions crates/oxc_linter/src/rules/jsx_a11y/lang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
9 changes: 3 additions & 6 deletions crates/oxc_linter/src/rules/jsx_a11y/media_has_caption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 2 additions & 3 deletions crates/oxc_linter/src/rules/jsx_a11y/no_autofocus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
Expand Down
5 changes: 2 additions & 3 deletions crates/oxc_linter/src/rules/jsx_a11y/no_redundant_roles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 3 additions & 4 deletions crates/oxc_linter/src/rules/jsx_a11y/prefer_tag_over_role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
4 changes: 1 addition & 3 deletions crates/oxc_linter/src/rules/jsx_a11y/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<FilesContext.Provider value={{ addAlert, cwdInfo }} />
· ───────────────────────────────────────────────────────
╰────
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.
Loading

0 comments on commit ee26b44

Please sign in to comment.