Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(linter): enhance get_element_type to resolve more element types #7885

Merged
merged 1 commit into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading