Skip to content

Commit

Permalink
feat: add jsx-props-no-spread-multi rule (#1358)
Browse files Browse the repository at this point in the history
* feat: add jsx-props-no-spread-multi rule

* fix: update schemas

* chore: add recommended
  • Loading branch information
marvinhagemeister authored Nov 29, 2024
1 parent a171958 commit 37d3df9
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 0 deletions.
18 changes: 18 additions & 0 deletions docs/rules/jsx_props_no_spread_multi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Spreading the same expression twice is typically a mistake and causes
unnecessary computations.

### Invalid:

```tsx
<div {...foo} {...foo} />
<div {...foo} a {...foo} />
<Foo {...foo.bar} {...foo.bar} />
```

### Valid:

```tsx
<div {...foo} />
<div {...foo.bar} a />
<Foo {...foo.bar} />
```
1 change: 1 addition & 0 deletions schemas/rules.v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"fresh-server-event-handlers",
"getter-return",
"guard-for-in",
"jsx-props-no-spread-multi",
"no-array-constructor",
"no-async-promise-executor",
"no-await-in-loop",
Expand Down
2 changes: 2 additions & 0 deletions src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub mod fresh_handler_export;
pub mod fresh_server_event_handlers;
pub mod getter_return;
pub mod guard_for_in;
pub mod jsx_props_no_spread_multi;
pub mod no_array_constructor;
pub mod no_async_promise_executor;
pub mod no_await_in_loop;
Expand Down Expand Up @@ -256,6 +257,7 @@ fn get_all_rules_raw() -> Vec<Box<dyn LintRule>> {
Box::new(fresh_server_event_handlers::FreshServerEventHandlers),
Box::new(getter_return::GetterReturn),
Box::new(guard_for_in::GuardForIn),
Box::new(jsx_props_no_spread_multi::JSXPropsNoSpreadMulti),
Box::new(no_array_constructor::NoArrayConstructor),
Box::new(no_async_promise_executor::NoAsyncPromiseExecutor),
Box::new(no_await_in_loop::NoAwaitInLoop),
Expand Down
139 changes: 139 additions & 0 deletions src/rules/jsx_props_no_spread_multi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

use std::collections::HashSet;

use super::{Context, LintRule};
use crate::diagnostic::{LintFix, LintFixChange};
use crate::handler::{Handler, Traverse};
use crate::Program;
use deno_ast::view::{JSXAttrOrSpread, JSXOpeningElement, NodeTrait};
use deno_ast::{SourceRange, SourceRanged};

#[derive(Debug)]
pub struct JSXPropsNoSpreadMulti;

const CODE: &str = "jsx-props-no-spread-multi";

impl LintRule for JSXPropsNoSpreadMulti {
fn tags(&self) -> &'static [&'static str] {
&["recommended", "react", "jsx"]
}

fn code(&self) -> &'static str {
CODE
}

fn lint_program_with_ast_view(
&self,
context: &mut Context,
program: Program,
) {
JSXPropsNoSpreadMultiHandler.traverse(program, context);
}

#[cfg(feature = "docs")]
fn docs(&self) -> &'static str {
include_str!("../../docs/rules/jsx_props_no_spread_multi.md")
}
}

const MESSAGE: &str = "Duplicate spread attribute found";
const HINT: &str = "Remove this spread attribute";

struct JSXPropsNoSpreadMultiHandler;

impl Handler for JSXPropsNoSpreadMultiHandler {
fn jsx_opening_element(
&mut self,
node: &JSXOpeningElement,
ctx: &mut Context,
) {
let mut seen: HashSet<&str> = HashSet::new();
for attr in node.attrs {
if let JSXAttrOrSpread::SpreadElement(spread) = attr {
let text = spread.expr.text();
if seen.contains(text) {
ctx.add_diagnostic_with_fixes(
spread.range(),
CODE,
MESSAGE,
Some(HINT.to_string()),
vec![LintFix {
description: "Remove this spread attribute".into(),
changes: vec![LintFixChange {
new_text: "".into(),
range: SourceRange {
start: attr.range().start - 2,
end: attr.range().end + 1,
},
}],
}],
);
}

seen.insert(text);
}
}
}
}

// most tests are taken from ESlint, commenting those
// requiring code path support
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn jsx_props_no_spread_multi_valid() {
assert_lint_ok! {
JSXPropsNoSpreadMulti,
filename: "file:///foo.jsx",
r#"<div {...foo} />"#,
r#"<div {...foo} {...bar} />"#,
r#"<Foo {...foo} />"#,
r#"<Foo {...foo} {...bar} />"#,
r#"<Foo {...foo.bar} {...foo.bar.baz} />"#,
};
}

#[test]
fn jsx_props_no_spread_multi_invalid() {
assert_lint_err! {
JSXPropsNoSpreadMulti,
filename: "file:///foo.jsx",
r#"<div {...foo} {...foo} />"#: [
{
col: 15,
message: MESSAGE,
hint: HINT,
fix: (
"Remove this spread attribute",
"<div {...foo} />"
)
}
],
r#"<Foo {...foo} {...foo} />"#: [
{
col: 15,
message: MESSAGE,
hint: HINT,
fix: (
"Remove this spread attribute",
"<Foo {...foo} />"
)
}
],
r#"<div {...foo.bar.baz} a {...foo.bar.baz} />"#: [
{
col: 25,
message: MESSAGE,
hint: HINT,
fix: (
"Remove this spread attribute",
"<div {...foo.bar.baz} a />"
)
}
],
};
}
}
9 changes: 9 additions & 0 deletions www/static/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@
"docs": "Require `for-in` loops to include an `if` statement\n\nLooping over objects with a `for-in` loop will include properties that are\ninherited through the prototype chain. This behavior can lead to unexpected\nitems in your for loop.\n\n### Invalid:\n\n```typescript\nfor (const key in obj) {\n foo(obj, key);\n}\n```\n\n### Valid:\n\n```typescript\nfor (const key in obj) {\n if (Object.hasOwn(obj, key)) {\n foo(obj, key);\n }\n}\n```\n\n```typescript\nfor (const key in obj) {\n if (!Object.hasOwn(obj, key)) {\n continue;\n }\n foo(obj, key);\n}\n```\n",
"tags": []
},
{
"code": "jsx-props-no-spread-multi",
"docs": "Spreading the same expression twice is typically a mistake and causes\nunnecessary computations.\n\n### Invalid:\n\n```tsx\n<div {...foo} {...foo} />\n<div {...foo} a {...foo} />\n<Foo {...foo.bar} {...foo.bar} />\n```\n\n### Valid:\n\n```tsx\n<div {...foo} />\n<div {...foo.bar} a />\n<Foo {...foo.bar} />\n```\n",
"tags": [
"recommended",
"react",
"jsx"
]
},
{
"code": "no-array-constructor",
"docs": "Enforce conventional usage of array construction\n\nArray construction is conventionally done via literal notation such as `[]` or\n`[1, 2, 3]`. Using the `new Array()` is discouraged as is `new Array(1, 2, 3)`.\nThere are two reasons for this. The first is that a single supplied argument\ndefines the array length, while multiple arguments instead populate the array of\nno fixed size. This confusion is avoided when pre-populated arrays are only\ncreated using literal notation. The second argument to avoiding the `Array`\nconstructor is that the `Array` global may be redefined.\n\nThe one exception to this rule is when creating a new array of fixed size, e.g.\n`new Array(6)`. This is the conventional way to create arrays of fixed length.\n\n### Invalid:\n\n```typescript\n// This is 4 elements, not a size 100 array of 3 elements\nconst a = new Array(100, 1, 2, 3);\n\nconst b = new Array(); // use [] instead\n```\n\n### Valid:\n\n```typescript\nconst a = new Array(100);\nconst b = [];\nconst c = [1, 2, 3];\n```\n",
Expand Down

0 comments on commit 37d3df9

Please sign in to comment.