diff --git a/Cargo.lock b/Cargo.lock index 202dd73262999..af8ff3dd5d258 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1599,7 +1599,7 @@ checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" [[package]] name = "oxc" -version = "0.109.0" +version = "0.110.0" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1683,7 +1683,7 @@ checksum = "42c447978900c2436dac665912cd298d3f9f6d4cac2551080328729ea52f840f" [[package]] name = "oxc_allocator" -version = "0.109.0" +version = "0.110.0" dependencies = [ "allocator-api2", "hashbrown 0.16.1", @@ -1697,7 +1697,7 @@ dependencies = [ [[package]] name = "oxc_ast" -version = "0.109.0" +version = "0.110.0" dependencies = [ "bitflags", "oxc_allocator", @@ -1712,7 +1712,7 @@ dependencies = [ [[package]] name = "oxc_ast_macros" -version = "0.109.0" +version = "0.110.0" dependencies = [ "phf", "proc-macro2", @@ -1757,7 +1757,7 @@ dependencies = [ [[package]] name = "oxc_ast_visit" -version = "0.109.0" +version = "0.110.0" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1792,7 +1792,7 @@ dependencies = [ [[package]] name = "oxc_cfg" -version = "0.109.0" +version = "0.110.0" dependencies = [ "bitflags", "itertools", @@ -1804,7 +1804,7 @@ dependencies = [ [[package]] name = "oxc_codegen" -version = "0.109.0" +version = "0.110.0" dependencies = [ "bitflags", "cow-utils", @@ -1826,7 +1826,7 @@ dependencies = [ [[package]] name = "oxc_compat" -version = "0.109.0" +version = "0.110.0" dependencies = [ "cow-utils", "oxc-browserslist", @@ -1879,14 +1879,14 @@ dependencies = [ [[package]] name = "oxc_data_structures" -version = "0.109.0" +version = "0.110.0" dependencies = [ "ropey", ] [[package]] name = "oxc_diagnostics" -version = "0.109.0" +version = "0.110.0" dependencies = [ "cow-utils", "oxc-miette", @@ -1895,7 +1895,7 @@ dependencies = [ [[package]] name = "oxc_ecmascript" -version = "0.109.0" +version = "0.110.0" dependencies = [ "cow-utils", "num-bigint", @@ -1909,7 +1909,7 @@ dependencies = [ [[package]] name = "oxc_estree" -version = "0.109.0" +version = "0.110.0" dependencies = [ "dragonbox_ecma", "itoa", @@ -1918,7 +1918,7 @@ dependencies = [ [[package]] name = "oxc_formatter" -version = "0.25.0" +version = "0.26.0" dependencies = [ "cow-utils", "insta", @@ -1951,7 +1951,7 @@ dependencies = [ [[package]] name = "oxc_isolated_declarations" -version = "0.109.0" +version = "0.110.0" dependencies = [ "bitflags", "insta", @@ -1969,7 +1969,7 @@ dependencies = [ [[package]] name = "oxc_language_server" -version = "1.40.0" +version = "1.41.0" dependencies = [ "futures", "papaya", @@ -1983,7 +1983,7 @@ dependencies = [ [[package]] name = "oxc_linter" -version = "1.40.0" +version = "1.41.0" dependencies = [ "bitflags", "constcat", @@ -2056,7 +2056,7 @@ dependencies = [ [[package]] name = "oxc_mangler" -version = "0.109.0" +version = "0.110.0" dependencies = [ "itertools", "oxc_allocator", @@ -2072,7 +2072,7 @@ dependencies = [ [[package]] name = "oxc_minifier" -version = "0.109.0" +version = "0.110.0" dependencies = [ "cow-utils", "insta", @@ -2099,7 +2099,7 @@ dependencies = [ [[package]] name = "oxc_minify_napi" -version = "0.109.0" +version = "0.110.0" dependencies = [ "mimalloc-safe", "napi", @@ -2138,7 +2138,7 @@ dependencies = [ [[package]] name = "oxc_napi" -version = "0.109.0" +version = "0.110.0" dependencies = [ "napi", "napi-build", @@ -2152,7 +2152,7 @@ dependencies = [ [[package]] name = "oxc_parser" -version = "0.109.0" +version = "0.110.0" dependencies = [ "bitflags", "cow-utils", @@ -2175,7 +2175,7 @@ dependencies = [ [[package]] name = "oxc_parser_napi" -version = "0.109.0" +version = "0.110.0" dependencies = [ "mimalloc-safe", "napi", @@ -2226,7 +2226,7 @@ dependencies = [ [[package]] name = "oxc_regular_expression" -version = "0.109.0" +version = "0.110.0" dependencies = [ "bitflags", "insta", @@ -2280,7 +2280,7 @@ dependencies = [ [[package]] name = "oxc_semantic" -version = "0.109.0" +version = "0.110.0" dependencies = [ "insta", "itertools", @@ -2320,7 +2320,7 @@ dependencies = [ [[package]] name = "oxc_span" -version = "0.109.0" +version = "0.110.0" dependencies = [ "compact_str", "oxc-miette", @@ -2333,7 +2333,7 @@ dependencies = [ [[package]] name = "oxc_syntax" -version = "0.109.0" +version = "0.110.0" dependencies = [ "bitflags", "cow-utils", @@ -2406,7 +2406,7 @@ dependencies = [ [[package]] name = "oxc_transform_napi" -version = "0.109.0" +version = "0.110.0" dependencies = [ "mimalloc-safe", "napi", @@ -2420,7 +2420,7 @@ dependencies = [ [[package]] name = "oxc_transformer" -version = "0.109.0" +version = "0.110.0" dependencies = [ "base64", "compact_str", @@ -2451,7 +2451,7 @@ dependencies = [ [[package]] name = "oxc_transformer_plugins" -version = "0.109.0" +version = "0.110.0" dependencies = [ "cow-utils", "insta", @@ -2478,7 +2478,7 @@ dependencies = [ [[package]] name = "oxc_traverse" -version = "0.109.0" +version = "0.110.0" dependencies = [ "itoa", "oxc_allocator", @@ -2494,7 +2494,7 @@ dependencies = [ [[package]] name = "oxfmt" -version = "0.25.0" +version = "0.26.0" dependencies = [ "bpaf", "cow-utils", @@ -2531,7 +2531,7 @@ dependencies = [ [[package]] name = "oxlint" -version = "1.40.0" +version = "1.41.0" dependencies = [ "bpaf", "cow-utils", diff --git a/crates/oxc_formatter/Cargo.toml b/crates/oxc_formatter/Cargo.toml index 183f45e356e2b..90310227e3f48 100644 --- a/crates/oxc_formatter/Cargo.toml +++ b/crates/oxc_formatter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxc_formatter" -version = "0.25.0" +version = "0.26.0" authors.workspace = true categories.workspace = true edition.workspace = true diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index e888a64d1b9b5..893e7570baad3 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -555,6 +555,7 @@ pub(crate) mod jsx_a11y { pub mod html_has_lang; pub mod iframe_has_title; pub mod img_redundant_alt; + pub mod interactive_supports_focus; pub mod label_has_associated_control; pub mod lang; pub mod media_has_caption; @@ -563,6 +564,7 @@ pub(crate) mod jsx_a11y { pub mod no_aria_hidden_on_focusable; pub mod no_autofocus; pub mod no_distracting_elements; + pub mod no_interactive_element_to_noninteractive_role; pub mod no_noninteractive_tabindex; pub mod no_redundant_roles; pub mod no_static_element_interactions; @@ -993,10 +995,12 @@ oxc_macros::declare_all_lint_rules! { jsx_a11y::html_has_lang, jsx_a11y::iframe_has_title, jsx_a11y::img_redundant_alt, + jsx_a11y::interactive_supports_focus, jsx_a11y::label_has_associated_control, jsx_a11y::lang, jsx_a11y::media_has_caption, jsx_a11y::mouse_events_have_key_events, + jsx_a11y::no_interactive_element_to_noninteractive_role, jsx_a11y::no_noninteractive_tabindex, jsx_a11y::no_static_element_interactions, jsx_a11y::no_access_key, diff --git a/crates/oxc_linter/src/rules/jsx_a11y/interactive_supports_focus.rs b/crates/oxc_linter/src/rules/jsx_a11y/interactive_supports_focus.rs new file mode 100644 index 0000000000000..fb05648789e73 --- /dev/null +++ b/crates/oxc_linter/src/rules/jsx_a11y/interactive_supports_focus.rs @@ -0,0 +1,177 @@ +use oxc_ast::{ + AstKind, + ast::{Expression, JSXAttributeItem, JSXAttributeValue, JSXExpression}, +}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{ + AstNode, + context::LintContext, + globals::HTML_TAG, + rule::Rule, + utils::{ + get_element_type, has_jsx_prop, is_hidden_from_screen_reader, is_interactive_element, + is_presentation_role, + }, +}; + +fn interactive_supports_focus_diagnostic(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Elements with interactive roles must be focusable.") + .with_help("Interactive elements must be able to receive focus. In JSX, add a valid `tabIndex` prop.") + .with_label(span) +} + +#[derive(Debug, Default, Clone)] +pub struct InteractiveSupportsFocus; + +declare_oxc_lint!( + /// ### What it does + /// + /// Enforces that elements with interactive roles are focusable. + /// + /// ### Why is this bad? + /// + /// Interactive elements that are not focusable cannot be accessed by keyboard users, + /// making them inaccessible to users with disabilities who rely on keyboard navigation. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```jsx + ///
{}} /> + /// {}} /> + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```jsx + ///
{}} tabIndex="0" /> + ///