Skip to content

Commit

Permalink
feat(linter): eslint-plugin-react jsx-props-no-spread-multi
Browse files Browse the repository at this point in the history
  • Loading branch information
keita-hino committed Aug 13, 2024
1 parent 1808529 commit dce7419
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 0 deletions.
2 changes: 2 additions & 0 deletions crates/oxc_linter/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
95 changes: 95 additions & 0 deletions crates/oxc_linter/src/rules/react/jsx_props_no_spread_multi.rs
Original file line number Diff line number Diff line change
@@ -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
/// <App {...props} myAttr="1" {...props} />
///
/// // Good
/// <App myAttr="1" {...props} />
/// <App {...props} myAttr="1" />
/// ```
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 = {};
<App {...a} />
",
"
const a = {};
const b = {};
<App {...a} {...b} />
",
];

let fail = vec![
"
const props = {};
<App {...props} {...props} />
",
r#"
const props = {};
<div {...props} a="a" {...props} />
"#,
"
const props = {};
<div {...props} {...props} {...props} />
",
];

Tester::new(JsxPropsNoSpreadMulti::NAME, pass, fail).test_and_snapshot();
}
38 changes: 38 additions & 0 deletions crates/oxc_linter/src/snapshots/jsx_props_no_spread_multi.snap
Original file line number Diff line number Diff line change
@@ -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]
2const props = {};
3<App {...props} {...props} />
· ──────────
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]
2const props = {};
3<div {...props} a="a" {...props} />
· ──────────
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]
2const props = {};
3<div {...props} {...props} {...props} />
· ──────────
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]
2const props = {};
3<div {...props} {...props} {...props} />
· ──────────
4
╰────
help: Remove duplicate spread attributes.

0 comments on commit dce7419

Please sign in to comment.