Skip to content

Commit

Permalink
feat(linter): eslint-plugin-jsx-a11y no-autofocus (#1641)
Browse files Browse the repository at this point in the history
impl linter for #1141
  • Loading branch information
msdlisper authored Dec 8, 2023
1 parent 6e5aad2 commit d88f4f4
Show file tree
Hide file tree
Showing 3 changed files with 370 additions and 0 deletions.
2 changes: 2 additions & 0 deletions crates/oxc_linter/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ mod jsx_a11y {
pub mod html_has_lang;
pub mod iframe_has_title;
pub mod img_redundant_alt;
pub mod no_autofocus;
pub mod scope;
}

Expand Down Expand Up @@ -435,5 +436,6 @@ oxc_macros::declare_all_lint_rules! {
jsx_a11y::iframe_has_title,
jsx_a11y::img_redundant_alt,
jsx_a11y::scope,
jsx_a11y::no_autofocus,
oxc::no_accumulating_spread
}
299 changes: 299 additions & 0 deletions crates/oxc_linter/src/rules/jsx_a11y/no_autofocus.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
use oxc_ast::{ast::JSXElementName, AstKind};
use oxc_diagnostics::{
miette::{self, Diagnostic},
thiserror::Error,
};
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;

use crate::{context::LintContext, rule::Rule, utils::has_jsx_prop, AstNode};

use phf::phf_set;

const HTML_TAG: phf::Set<&'static str> = phf_set! {
"a",
"abbr",
"acronym",
"address",
"applet",
"area",
"article",
"aside",
"audio",
"b",
"base",
"basefont",
"bdi",
"bdo",
"bgsound",
"big",
"blink",
"blockquote",
"body",
"br",
"button",
"canvas",
"caption",
"center",
"cite",
"code",
"col",
"colgroup",
"command",
"content",
"data",
"datalist",
"dd",
"del",
"details",
"dfn",
"dialog",
"dir",
"div",
"dl",
"dt",
"element",
"em",
"embed",
"fieldset",
"figcaption",
"figure",
"font",
"footer",
"form",
"frame",
"frameset",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"head",
"header",
"hgroup",
"hr",
"html",
"i",
"iframe",
"image",
"img",
"input",
"ins",
"isindex",
"kbd",
"keygen",
"label",
"legend",
"li",
"link",
"listing",
"main",
"map",
"mark",
"marquee",
"math",
"menu",
"menuitem",
"meta",
"meter",
"multicol",
"nav",
"nextid",
"nobr",
"noembed",
"noframes",
"noscript",
"object",
"ol",
"optgroup",
"option",
"output",
"p",
"param",
"picture",
"plaintext",
"pre",
"progress",
"q",
"rb",
"rbc",
"rp",
"rt",
"rtc",
"ruby",
"s",
"samp",
"script",
"search",
"section",
"select",
"shadow",
"slot",
"small",
"source",
"spacer",
"span",
"strike",
"strong",
"style",
"sub",
"summary",
"sup",
"svg",
"table",
"tbody",
"td",
"template",
"textarea",
"tfoot",
"th",
"thead",
"time",
"title",
"tr",
"track",
"tt",
"u",
"ul",
"var",
"video",
"wbr",
"xmp",
};

#[derive(Debug, Error, Diagnostic)]
#[error("eslint-plugin-jsx-a11y(no-autofocus): The `autofocus` attribute is found here, which can cause usability issues for sighted and non-sighted users")]
#[diagnostic(severity(warning), help("Remove `autofocus` attribute"))]
struct NoAutofocusDiagnostic(#[label] pub 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:
///
/// ```
/// {
/// "rules": {
/// "jsx-a11y/no-autofocus": [ 2, {
/// "ignoreNonDOM": true
/// }],
/// }
/// }
/// ```
///
/// For the `ignoreNonDOM` option, this determines if developer created components are checked.
///
/// ### Example
/// // good
///
/// ```javascript
/// <div />
/// ```
///
/// // bad
///
/// ```
/// <div autoFocus />
/// <div autoFocus="true" />
/// <div autoFocus="false" />
/// <div autoFocus={undefined} />
/// ```
///
NoAutofocus,
correctness
);

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

let _ = value.as_array().unwrap().iter().find(|v| {
if let serde_json::Value::Object(obj) = v {
let config = obj.get("ignoreNonDOM").unwrap();
if let serde_json::Value::Bool(val) = config {
no_focus.set_option(*val);
}
return true;
}
false
});

no_focus
}

fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
if let AstKind::JSXElement(jsx_el) = node.kind() {
if let Option::Some(autofocus) = has_jsx_prop(&jsx_el.opening_element, "autoFocus") {
if self.ignore_non_dom {
let JSXElementName::Identifier(ident) = &jsx_el.opening_element.name else {
return;
};
let name = ident.name.as_str();
if HTML_TAG.contains(name) {
if let oxc_ast::ast::JSXAttributeItem::Attribute(attr) = autofocus {
ctx.diagnostic(NoAutofocusDiagnostic(attr.span));
}
}
return;
}

if let oxc_ast::ast::JSXAttributeItem::Attribute(attr) = autofocus {
ctx.diagnostic(NoAutofocusDiagnostic(attr.span));
}
}
}
}
}

#[test]
fn test() {
use crate::tester::Tester;
fn array() -> serde_json::Value {
serde_json::json!([2,{
"ignoreNonDOM": true
}])
}

let pass = vec![
("<div />;", None),
("<div autofocus />;", None),
("<input autofocus='true' />;", None),
("<Foo bar />", None),
("<Button />", None),
("<Foo autoFocus />", Some(array())),
("<div><div autofocus /></div>", Some(array())),
// TODO we need components_settings to test this
// ("<Button />", Some(serde_json::json!(ignoreNonDOMSchema))),
// ("<Button />", Some(serde_json::json!(ignoreNonDOMSchema)), setting),
];

let fail = vec![
("<div autoFocus />", None),
("<div autoFocus={true} />", None),
("<div autoFocus={false} />", None),
("<div autoFocus={undefined} />", None),
("<div autoFocus='true' />", None),
("<div autoFocus='false' />", None),
("<input autoFocus />", None),
("<Foo autoFocus />", None),
("<Button autoFocus />", None),
// TODO we need components_settings to test this
// ("<Button autoFocus />", Some(array())),
];

Tester::new(NoAutofocus::NAME, pass, fail).test_and_snapshot();
}
69 changes: 69 additions & 0 deletions crates/oxc_linter/src/snapshots/no_autofocus.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
source: crates/oxc_linter/src/tester.rs
assertion_line: 119
expression: no_autofocus
---
eslint-plugin-jsx-a11y(no-autofocus): The `autofocus` attribute is found here, which can cause usability issues for sighted and non-sighted users
╭─[no_autofocus.tsx:1:1]
1<div autoFocus />
· ─────────
╰────
help: Remove `autofocus` attribute

eslint-plugin-jsx-a11y(no-autofocus): The `autofocus` attribute is found here, which can cause usability issues for sighted and non-sighted users
╭─[no_autofocus.tsx:1:1]
1<div autoFocus={true} />
· ────────────────
╰────
help: Remove `autofocus` attribute

eslint-plugin-jsx-a11y(no-autofocus): The `autofocus` attribute is found here, which can cause usability issues for sighted and non-sighted users
╭─[no_autofocus.tsx:1:1]
1<div autoFocus={false} />
· ─────────────────
╰────
help: Remove `autofocus` attribute

eslint-plugin-jsx-a11y(no-autofocus): The `autofocus` attribute is found here, which can cause usability issues for sighted and non-sighted users
╭─[no_autofocus.tsx:1:1]
1<div autoFocus={undefined} />
· ─────────────────────
╰────
help: Remove `autofocus` attribute

eslint-plugin-jsx-a11y(no-autofocus): The `autofocus` attribute is found here, which can cause usability issues for sighted and non-sighted users
╭─[no_autofocus.tsx:1:1]
1<div autoFocus='true' />
· ────────────────
╰────
help: Remove `autofocus` attribute

eslint-plugin-jsx-a11y(no-autofocus): The `autofocus` attribute is found here, which can cause usability issues for sighted and non-sighted users
╭─[no_autofocus.tsx:1:1]
1<div autoFocus='false' />
· ─────────────────
╰────
help: Remove `autofocus` attribute

eslint-plugin-jsx-a11y(no-autofocus): The `autofocus` attribute is found here, which can cause usability issues for sighted and non-sighted users
╭─[no_autofocus.tsx:1:1]
1<input autoFocus />
· ─────────
╰────
help: Remove `autofocus` attribute

eslint-plugin-jsx-a11y(no-autofocus): The `autofocus` attribute is found here, which can cause usability issues for sighted and non-sighted users
╭─[no_autofocus.tsx:1:1]
1<Foo autoFocus />
· ─────────
╰────
help: Remove `autofocus` attribute

eslint-plugin-jsx-a11y(no-autofocus): The `autofocus` attribute is found here, which can cause usability issues for sighted and non-sighted users
╭─[no_autofocus.tsx:1:1]
1<Button autoFocus />
· ─────────
╰────
help: Remove `autofocus` attribute


0 comments on commit d88f4f4

Please sign in to comment.