oxc/crates/oxc_linter/src/rules/jsx_a11y/no_autofocus.rs
dalaoshu ee26b448cc
feat(linter): enhance get_element_type to resolve more element types (#7885)
I think it should if makes sense.

_Originally posted by @Boshen in
https://github.com/oxc-project/oxc/issues/7881#issuecomment-2543108246_
            

Since `jsx-a11y` supports these names, I have aligned them accordingly.
2024-12-14 22:45:37 +08:00

182 lines
5.3 KiB
Rust

use oxc_ast::AstKind;
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use crate::{
context::LintContext,
globals::HTML_TAG,
rule::Rule,
utils::{get_element_type, has_jsx_prop},
AstNode,
};
fn no_autofocus_diagnostic(span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("The `autofocus` attribute is found here, which can cause usability issues for sighted and non-sighted users")
.with_help("Remove `autofocus` attribute")
.with_label(span)
}
#[derive(Debug, Default, Clone)]
pub struct NoAutofocus {
ignore_non_dom: bool,
}
declare_oxc_lint!(
/// ### What it does
///
/// Enforce that `autoFocus` prop is not used on elements. Autofocusing
/// elements can cause usability issues for sighted and non-sighted users,
/// alike.
///
/// ### Rule Option
///
/// This rule takes one optional object argument of type object:
///
/// ```json
/// {
/// "rules": {
/// "jsx-a11y/no-autofocus": [ 2, {
/// "ignoreNonDOM": true
/// }],
/// }
/// }
/// ```
///
/// For the `ignoreNonDOM` option, this determines if developer created
/// components are checked.
///
/// ### Example
///
/// Examples of **incorrect** code for this rule:
///
/// ```jsx
/// <div autoFocus />
/// <div autoFocus="true" />
/// <div autoFocus="false" />
/// <div autoFocus={undefined} />
/// ```
///
/// Examples of **correct** code for this rule:
///
/// ```jsx
/// <div />
/// ```
NoAutofocus,
correctness,
fix
);
impl NoAutofocus {
pub fn set_option(&mut self, value: bool) {
self.ignore_non_dom = value;
}
}
impl Rule for NoAutofocus {
fn from_configuration(value: serde_json::Value) -> Self {
let mut no_focus = Self::default();
if let Some(arr) = value.as_array() {
if arr.iter().any(|v| {
if let serde_json::Value::Object(obj) = v {
if let Some(serde_json::Value::Bool(val)) = obj.get("ignoreNonDOM") {
return *val;
}
}
false
}) {
no_focus.set_option(true);
}
}
no_focus
}
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
let AstKind::JSXElement(jsx_el) = node.kind() else {
return;
};
let Some(autofocus) = has_jsx_prop(&jsx_el.opening_element, "autoFocus") else {
return;
};
let element_type = get_element_type(ctx, &jsx_el.opening_element);
if self.ignore_non_dom {
if HTML_TAG.contains(&element_type) {
if let oxc_ast::ast::JSXAttributeItem::Attribute(attr) = autofocus {
ctx.diagnostic_with_fix(no_autofocus_diagnostic(attr.span), |fixer| {
fixer.delete(&attr.span)
});
}
}
return;
}
if let oxc_ast::ast::JSXAttributeItem::Attribute(attr) = autofocus {
ctx.diagnostic_with_fix(no_autofocus_diagnostic(attr.span), |fixer| {
fixer.delete(&attr.span)
});
}
}
}
#[test]
fn test() {
use crate::tester::Tester;
fn config() -> serde_json::Value {
serde_json::json!([2,{
"ignoreNonDOM": true
}])
}
fn settings() -> serde_json::Value {
serde_json::json!({
"settings": { "jsx-a11y": {
"components": {
"Button": "button",
}
} }
})
}
let pass = vec![
("<div />;", None, None, None),
("<div autofocus />;", None, None, None),
("<input autofocus='true' />;", None, None, None),
("<Foo bar />", None, None, None),
("<Button />", None, None, None),
("<Foo autoFocus />", Some(config()), None, None),
("<div><div autofocus /></div>", Some(config()), None, None),
("<Button />", None, Some(settings()), None),
("<Button />", Some(config()), Some(settings()), None),
];
let fail = vec![
("<div autoFocus />", None, None, None),
("<div autoFocus={true} />", None, None, None),
("<div autoFocus={false} />", None, None, None),
("<div autoFocus={undefined} />", None, None, None),
("<div autoFocus='true' />", None, None, None),
("<div autoFocus='false' />", None, None, None),
("<input autoFocus />", None, None, None),
("<Foo autoFocus />", None, None, None),
("<Button autoFocus />", None, None, None),
("<Button autoFocus />", Some(config()), Some(settings()), None),
];
let fix = vec![
("<div autoFocus />", "<div />", None),
("<div autoFocus={true} />", "<div />", None),
("<div autoFocus='true' />", "<div />", None),
("<Button autoFocus='true' />", "<Button />", None),
("<input autoFocus />", "<input />", None),
("<div autoFocus>foo</div>", "<div >foo</div>", None),
("<div autoFocus id='lol'>foo</div>", "<div id='lol'>foo</div>", None),
];
Tester::new(NoAutofocus::NAME, NoAutofocus::CATEGORY, pass, fail)
.expect_fix(fix)
.test_and_snapshot();
}