diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 344112dce1395c..ccad7740e77d7a 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -227,6 +227,7 @@ mod react { pub mod jsx_no_target_blank; pub mod jsx_no_undef; pub mod jsx_no_useless_fragment; + pub mod jsx_props_no_spread_multi; pub mod no_children_prop; pub mod no_danger; pub mod no_direct_mutation_state; @@ -733,6 +734,7 @@ oxc_macros::declare_all_lint_rules! { react::jsx_no_comment_textnodes, react::jsx_no_duplicate_props, react::jsx_no_useless_fragment, + react::jsx_props_no_spread_multi, react::jsx_no_undef, react::react_in_jsx_scope, react::no_children_prop, diff --git a/crates/oxc_linter/src/rules/react/jsx_props_no_spread_multi.rs b/crates/oxc_linter/src/rules/react/jsx_props_no_spread_multi.rs new file mode 100644 index 00000000000000..a7d22788416ff5 --- /dev/null +++ b/crates/oxc_linter/src/rules/react/jsx_props_no_spread_multi.rs @@ -0,0 +1,95 @@ +use std::collections::HashSet; + +use oxc_ast::{ast::JSXAttributeItem, AstKind}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +fn jsx_props_no_spread_multi_diagnostic(span0: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Disallow JSX prop spreading the same identifier multiple times.") + .with_help("Remove duplicate spread attributes.") + .with_label(span0) +} + +#[derive(Debug, Default, Clone)] +pub struct JsxPropsNoSpreadMulti; + +declare_oxc_lint!( + /// ### What it does + /// Enforces that any unique expression is only spread once. + /// Generally spreading the same expression twice is an indicator of a mistake since any attribute between the spreads may be overridden when the intent was not to. + /// Even when that is not the case this will lead to unnecessary computations being performed. + /// + /// ### Example + /// ```javascript + /// // Bad + /// + /// + /// // Good + /// + /// + /// ``` + JsxPropsNoSpreadMulti, + correctness, +); + +impl Rule for JsxPropsNoSpreadMulti { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + if let AstKind::JSXOpeningElement(jsx_opening_el) = node.kind() { + let spread_attrs = jsx_opening_el.attributes.iter().filter_map(|attr| { + if let JSXAttributeItem::SpreadAttribute(spread_attr) = attr { + if spread_attr.argument.is_identifier_reference() { + return Some(spread_attr); + } + } + None + }); + + let mut identifier_names = HashSet::new(); + + for spread_attr in spread_attrs { + let identifier_name = + &spread_attr.argument.get_identifier_reference().unwrap().name; + if !identifier_names.insert(identifier_name) { + ctx.diagnostic(jsx_props_no_spread_multi_diagnostic(spread_attr.span)); + } + } + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + " + const a = {}; + + ", + " + const a = {}; + const b = {}; + + ", + ]; + + let fail = vec![ + " + const props = {}; + + ", + r#" + const props = {}; +
+ "#, + " + const props = {}; +
+ ", + ]; + + Tester::new(JsxPropsNoSpreadMulti::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/jsx_props_no_spread_multi.snap b/crates/oxc_linter/src/snapshots/jsx_props_no_spread_multi.snap new file mode 100644 index 00000000000000..c9b6d3264c914f --- /dev/null +++ b/crates/oxc_linter/src/snapshots/jsx_props_no_spread_multi.snap @@ -0,0 +1,38 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + ⚠ eslint-plugin-react(jsx-props-no-spread-multi): Disallow JSX prop spreading the same identifier multiple times. + ╭─[jsx_props_no_spread_multi.tsx:3:27] + 2 │ const props = {}; + 3 │ + · ────────── + 4 │ + ╰──── + help: Remove duplicate spread attributes. + + ⚠ eslint-plugin-react(jsx-props-no-spread-multi): Disallow JSX prop spreading the same identifier multiple times. + ╭─[jsx_props_no_spread_multi.tsx:3:33] + 2 │ const props = {}; + 3 │
+ · ────────── + 4 │ + ╰──── + help: Remove duplicate spread attributes. + + ⚠ eslint-plugin-react(jsx-props-no-spread-multi): Disallow JSX prop spreading the same identifier multiple times. + ╭─[jsx_props_no_spread_multi.tsx:3:27] + 2 │ const props = {}; + 3 │
+ · ────────── + 4 │ + ╰──── + help: Remove duplicate spread attributes. + + ⚠ eslint-plugin-react(jsx-props-no-spread-multi): Disallow JSX prop spreading the same identifier multiple times. + ╭─[jsx_props_no_spread_multi.tsx:3:38] + 2 │ const props = {}; + 3 │
+ · ────────── + 4 │ + ╰──── + help: Remove duplicate spread attributes.