diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index f1b9b653b..39ca8ba8c 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -126,6 +126,7 @@ mod jest { mod react { pub mod jsx_key; + pub mod jsx_no_duplicate_props; pub mod jsx_no_useless_fragment; pub mod no_children_prop; } @@ -251,9 +252,10 @@ oxc_macros::declare_all_lint_rules! { unicorn::no_thenable, unicorn::throw_new_error, unicorn::prefer_array_flat_map, - react::no_children_prop, react::jsx_key, + react::jsx_no_duplicate_props, react::jsx_no_useless_fragment, + react::no_children_prop, import::named, import::no_cycle, import::no_self_import, diff --git a/crates/oxc_linter/src/rules/react/jsx_no_duplicate_props.rs b/crates/oxc_linter/src/rules/react/jsx_no_duplicate_props.rs new file mode 100644 index 000000000..2384fc191 --- /dev/null +++ b/crates/oxc_linter/src/rules/react/jsx_no_duplicate_props.rs @@ -0,0 +1,117 @@ +use oxc_ast::{ + ast::{JSXAttributeItem, JSXAttributeName}, + AstKind, +}; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::{self, Error}, +}; +use oxc_macros::declare_oxc_lint; +use oxc_span::{Atom, Span}; +use rustc_hash::FxHashMap; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +#[derive(Debug, Error, Diagnostic)] +#[error( + "eslint-plugin-react(jsx-no-duplicate-props): No duplicate props allowed. The prop \"{0}\" is duplicated." +)] +#[diagnostic( + severity(warning), + help("Remove one of the props, or rename them so each prop is distinct.") +)] +struct JsxNoDuplicatePropsDiagnostic(Atom, #[label] pub Span, #[label] pub Span); + +#[derive(Debug, Default, Clone)] +pub struct JsxNoDuplicateProps; + +declare_oxc_lint!( + /// ### What it does + /// + /// This rule prevents duplicate props in JSX elements. + /// + /// ### Why is this bad? + /// + /// Having duplicate props in a JSX element is most likely a mistake. + /// Creating JSX elements with duplicate props can cause unexpected behavior in your application. + /// + /// ### Example + /// ```javascript + /// // Bad + /// ; + /// ; + /// + /// // Good + /// ; + /// ; + /// + /// ``` + JsxNoDuplicateProps, + correctness +); + +impl Rule for JsxNoDuplicateProps { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::JSXOpeningElement(jsx_opening_elem) = node.kind() else { return }; + + let mut props: FxHashMap = FxHashMap::default(); + + for attr in &jsx_opening_elem.attributes { + let JSXAttributeItem::Attribute(jsx_attr) = attr else { continue }; + + let JSXAttributeName::Identifier(ident) = &jsx_attr.name else { continue }; + + if let Some(old_span) = props.insert(ident.name.clone(), ident.span) { + ctx.diagnostic(JsxNoDuplicatePropsDiagnostic( + ident.name.clone(), + old_span, + ident.span, + )); + } + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + (";", None), + (";", None), + (";", None), + (";", None), + (";", None), + (";", None), + (r#";"#, None), + (r#";"#, None), + (r#";"#, None), + (";", None), + (";", None), + (r#";"#, None), + ]; + + let fail = vec![ + (";", None), + (";", None), + (r#";"#, None), + (r#";"#, None), + (r#";"#, None), + (r#";"#, None), + (r#";"#, None), + (r#";"#, None), + ( + r#" + ; + "#, + None, + ), + ]; + + Tester::new(JsxNoDuplicateProps::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/jsx_no_duplicate_props.snap b/crates/oxc_linter/src/snapshots/jsx_no_duplicate_props.snap new file mode 100644 index 000000000..3dd982f48 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/jsx_no_duplicate_props.snap @@ -0,0 +1,73 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: jsx_no_duplicate_props +--- + ⚠ eslint-plugin-react(jsx-no-duplicate-props): No duplicate props allowed. The prop "a" is duplicated. + ╭─[jsx_no_duplicate_props.tsx:1:1] + 1 │ ; + · ─ ─ + ╰──── + help: Remove one of the props, or rename them so each prop is distinct. + + ⚠ eslint-plugin-react(jsx-no-duplicate-props): No duplicate props allowed. The prop "A" is duplicated. + ╭─[jsx_no_duplicate_props.tsx:1:1] + 1 │ ; + · ─ ─ + ╰──── + help: Remove one of the props, or rename them so each prop is distinct. + + ⚠ eslint-plugin-react(jsx-no-duplicate-props): No duplicate props allowed. The prop "a" is duplicated. + ╭─[jsx_no_duplicate_props.tsx:1:1] + 1 │ ; + · ─ ─ + ╰──── + help: Remove one of the props, or rename them so each prop is distinct. + + ⚠ eslint-plugin-react(jsx-no-duplicate-props): No duplicate props allowed. The prop "a" is duplicated. + ╭─[jsx_no_duplicate_props.tsx:1:1] + 1 │ ; + · ─ ─ + ╰──── + help: Remove one of the props, or rename them so each prop is distinct. + + ⚠ eslint-plugin-react(jsx-no-duplicate-props): No duplicate props allowed. The prop "a" is duplicated. + ╭─[jsx_no_duplicate_props.tsx:1:1] + 1 │ ; + · ─ ─ + ╰──── + help: Remove one of the props, or rename them so each prop is distinct. + + ⚠ eslint-plugin-react(jsx-no-duplicate-props): No duplicate props allowed. The prop "a" is duplicated. + ╭─[jsx_no_duplicate_props.tsx:1:1] + 1 │ ; + · ─ ─ + ╰──── + help: Remove one of the props, or rename them so each prop is distinct. + + ⚠ eslint-plugin-react(jsx-no-duplicate-props): No duplicate props allowed. The prop "a" is duplicated. + ╭─[jsx_no_duplicate_props.tsx:1:1] + 1 │ ; + · ─ ─ + ╰──── + help: Remove one of the props, or rename them so each prop is distinct. + + ⚠ eslint-plugin-react(jsx-no-duplicate-props): No duplicate props allowed. The prop "a" is duplicated. + ╭─[jsx_no_duplicate_props.tsx:1:1] + 1 │ ; + · ─ ─ + ╰──── + help: Remove one of the props, or rename them so each prop is distinct. + + ⚠ eslint-plugin-react(jsx-no-duplicate-props): No duplicate props allowed. The prop "a" is duplicated. + ╭─[jsx_no_duplicate_props.tsx:2:1] + 2 │