feat(linter): eslint-plugin-react: jsx-no-duplicate-props (#1024)

Co-authored-by: Boshen <boshenc@gmail.com>
This commit is contained in:
Cameron 2023-10-21 16:25:32 +01:00 committed by GitHub
parent 25247e3839
commit dea9b7cbac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 193 additions and 1 deletions

View file

@ -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,

View file

@ -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
/// <App a a />;
/// <App foo={2} bar baz foo={3} />;
///
/// // Good
/// <App a />;
/// <App bar baz foo={3} />;
///
/// ```
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<Atom, Span> = 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![
("<App />;", None),
("<App {...this.props} />;", None),
("<App a b c />;", None),
("<App a b c A />;", None),
("<App {...this.props} a b c />;", None),
("<App c {...this.props} a b />;", None),
(r#"<App a="c" b="b" c="a" />;"#, None),
(r#"<App {...this.props} a="c" b="b" c="a" />;"#, None),
(r#"<App c="a" {...this.props} a="c" b="b" />;"#, None),
("<App A a />;", None),
("<App A b a />;", None),
(r#"<App A="a" b="b" B="B" />;"#, None),
];
let fail = vec![
("<App a a />;", None),
("<App A b c A />;", None),
(r#"<App a="a" b="b" a="a" />;"#, None),
(r#"<App a="a" {...this.props} b="b" a="a" />;"#, None),
(r#"<App a b="b" {...this.props} a="a" />;"#, None),
(r#"<App a={[]} b="b" {...this.props} a="a" />;"#, None),
(r#"<App a="a" b="b" a="a" {...this.props} />;"#, None),
(r#"<App {...this.props} a="a" b="b" a="a" />;"#, None),
(
r#"
<App
a="a"
{...this.props}
a={{foo: 'bar'}}
b="b"
/>;
"#,
None,
),
];
Tester::new(JsxNoDuplicateProps::NAME, pass, fail).test_and_snapshot();
}

View file

@ -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 │ <App a a />;
· ─ ─
╰────
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 │ <App A b c A />;
· ─ ─
╰────
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 │ <App a="a" b="b" a="a" />;
· ─ ─
╰────
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 │ <App a="a" {...this.props} b="b" a="a" />;
· ─ ─
╰────
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 │ <App a b="b" {...this.props} a="a" />;
· ─ ─
╰────
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 │ <App a={[]} b="b" {...this.props} a="a" />;
· ─ ─
╰────
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 │ <App a="a" b="b" a="a" {...this.props} />;
· ─ ─
╰────
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 │ <App {...this.props} a="a" b="b" a="a" />;
· ─ ─
╰────
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 │ <App
3 │ a="a"
· ─
4 │ {...this.props}
5 │ a={{foo: 'bar'}}
· ─
6 │ b="b"
╰────
help: Remove one of the props, or rename them so each prop is distinct.