From a9a3ef304bb7eb867da34db8b276b3406c5a5a6b Mon Sep 17 00:00:00 2001 From: Tyler Earls Date: Fri, 27 Dec 2024 16:14:05 -0600 Subject: [PATCH 01/17] gen rule file --- crates/oxc_linter/src/rules.rs | 2 + .../jsx_a11y/no_noninteractive_tabindex.rs | 93 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 crates/oxc_linter/src/rules/jsx_a11y/no_noninteractive_tabindex.rs diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 0cfa9eb7a90da..1c8f7776c73bc 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -410,6 +410,7 @@ mod jsx_a11y { pub mod no_aria_hidden_on_focusable; pub mod no_autofocus; pub mod no_distracting_elements; + pub mod no_noninteractive_tabindex; pub mod no_redundant_roles; pub mod prefer_tag_over_role; pub mod role_has_required_aria_props; @@ -745,6 +746,7 @@ oxc_macros::declare_all_lint_rules! { jsx_a11y::lang, jsx_a11y::media_has_caption, jsx_a11y::mouse_events_have_key_events, + jsx_a11y::no_noninteractive_tabindex, jsx_a11y::no_access_key, jsx_a11y::no_aria_hidden_on_focusable, jsx_a11y::no_autofocus, diff --git a/crates/oxc_linter/src/rules/jsx_a11y/no_noninteractive_tabindex.rs b/crates/oxc_linter/src/rules/jsx_a11y/no_noninteractive_tabindex.rs new file mode 100644 index 0000000000000..e0ae608ebb1d3 --- /dev/null +++ b/crates/oxc_linter/src/rules/jsx_a11y/no_noninteractive_tabindex.rs @@ -0,0 +1,93 @@ +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{ + context::LintContext, + fixer::{RuleFix, RuleFixer}, + rule::Rule, + AstNode, +}; + +fn no_noninteractive_tabindex_diagnostic(span: Span) -> OxcDiagnostic { + // See for details + OxcDiagnostic::warn("Should be an imperative statement about what is wrong") + .with_help("Should be a command-like statement that tells the user how to fix the issue") + .with_label(span) +} + +#[derive(Debug, Default, Clone)] +pub struct NoNoninteractiveTabindex; + +declare_oxc_lint!( + /// ### What it does + /// + /// + /// ### Why is this bad? + /// + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```jsx + /// FIXME: Tests will fail if examples are missing or syntactically incorrect. + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```jsx + /// FIXME: Tests will fail if examples are missing or syntactically incorrect. + /// ``` + NoNoninteractiveTabindex, + nursery, // TODO: change category to `correctness`, `suspicious`, `pedantic`, `perf`, `restriction`, or `style` + // See for details + + pending // TODO: describe fix capabilities. Remove if no fix can be done, + // keep at 'pending' if you think one could be added but don't know how. + // Options are 'fix', 'fix_dangerous', 'suggestion', and 'conditional_fix_suggestion' +); + +impl Rule for NoNoninteractiveTabindex { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {} +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + (r#"
"#, None), + (r#"
{}} tabIndex="0" />;"#, None), + ( + r#"
{}} tabIndex="0" />;"#, + Some(serde_json::json!([{ "allowExpressionValues": true }])), + ), + ( + r#"
{}} tabIndex="0" />;"#, + Some(serde_json::json!([{ "allowExpressionValues": true }])), + ), + ( + r#"
{}} tabIndex="0" />;"#, + Some(serde_json::json!([{ "allowExpressionValues": true }])), + ), + ( + r#"
{}} tabIndex="0"/>;"#, + Some(serde_json::json!([{ "allowExpressionValues": true }])), + ), + ]; + + let fail = vec![ + (r#"
"#, None), + (r#"
{}} tabIndex="0" />;"#, None), + ( + r#"
{}} tabIndex="0" />;"#, + Some(serde_json::json!([{ "allowExpressionValues": false }])), + ), + ( + r#"
{}} tabIndex="0" />;"#, + Some(serde_json::json!([{ "allowExpressionValues": false }])), + ), + ]; + + Tester::new(NoNoninteractiveTabindex::NAME, NoNoninteractiveTabindex::CATEGORY, pass, fail) + .test_and_snapshot(); +} From a3f7747a55dea87fd501c00ec65c464983b9bcf7 Mon Sep 17 00:00:00 2001 From: Tyler Earls Date: Fri, 27 Dec 2024 17:20:54 -0600 Subject: [PATCH 02/17] fill out docs, add logic --- .../jsx_a11y/no_noninteractive_tabindex.rs | 81 +++++++++++++++---- 1 file changed, 66 insertions(+), 15 deletions(-) diff --git a/crates/oxc_linter/src/rules/jsx_a11y/no_noninteractive_tabindex.rs b/crates/oxc_linter/src/rules/jsx_a11y/no_noninteractive_tabindex.rs index e0ae608ebb1d3..102db314e7e5b 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/no_noninteractive_tabindex.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/no_noninteractive_tabindex.rs @@ -1,12 +1,11 @@ +use oxc_ast::{ast::{JSXAttributeItem, JSXAttributeValue}, AstKind}; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; -use oxc_span::Span; +use oxc_span::{CompactStr, Span}; +use phf::phf_set; use crate::{ - context::LintContext, - fixer::{RuleFix, RuleFixer}, - rule::Rule, - AstNode, + context::LintContext, rule::Rule, utils::{get_element_type, has_jsx_prop_ignore_case}, AstNode }; fn no_noninteractive_tabindex_diagnostic(span: Span) -> OxcDiagnostic { @@ -21,33 +20,85 @@ pub struct NoNoninteractiveTabindex; declare_oxc_lint!( /// ### What it does - /// + /// This rule checks that non-interactive elements don't have a tabIndex which would make them interactive via keyboard navigation. /// /// ### Why is this bad? /// + /// Tab key navigation should be limited to elements on the page that can be interacted with. + /// Thus it is not necessary to add a tabindex to items in an unordered list, for example, + /// to make them navigable through assistive technology. + /// + /// These applications already afford page traversal mechanisms based on the HTML of the page. + /// Generally, we should try to reduce the size of the page's tab ring rather than increasing it. /// /// ### Examples /// /// Examples of **incorrect** code for this rule: /// ```jsx - /// FIXME: Tests will fail if examples are missing or syntactically incorrect. + ///
+ ///
+ ///
+ ///
/// ``` /// /// Examples of **correct** code for this rule: /// ```jsx - /// FIXME: Tests will fail if examples are missing or syntactically incorrect. + ///
+ /// + ///