Skip to content

Conversation

@Netail
Copy link
Member

@Netail Netail commented Nov 15, 2025

Summary

Implement Eslint React's jsx-props-no-spread-multi

Closes #7658

Test Plan

Docs

@changeset-bot
Copy link

changeset-bot bot commented Nov 15, 2025

🦋 Changeset detected

Latest commit: 92df86e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@biomejs/biome Patch
@biomejs/cli-win32-x64 Patch
@biomejs/cli-win32-arm64 Patch
@biomejs/cli-darwin-x64 Patch
@biomejs/cli-darwin-arm64 Patch
@biomejs/cli-linux-x64 Patch
@biomejs/cli-linux-arm64 Patch
@biomejs/cli-linux-x64-musl Patch
@biomejs/cli-linux-arm64-musl Patch
@biomejs/wasm-web Patch
@biomejs/wasm-bundler Patch
@biomejs/wasm-nodejs Patch
@biomejs/backend-jsonrpc Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions bot added A-CLI Area: CLI A-Project Area: project A-Linter Area: linter L-JavaScript Language: JavaScript and super languages A-Diagnostic Area: diagnostocis labels Nov 15, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 15, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds a new nursery lint rule noDuplicatedSpreadProps that flags repeated JSX spread of the same identifier on a single element. Implements the rule in the analyzer with a query over JsxOpeningElement and JsxSelfClosingElement, a validate_attributes helper to detect duplicate spread identifiers, and a Rule implementation that emits diagnostics. Adds NoDuplicatedSpreadPropsOptions and exposes the option in the rule options crate. Includes valid and invalid test cases and a changeset for a patch release.

Suggested reviewers

  • ematipico

Pre-merge checks and finishing touches

✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: implementing the noDuplicatedSpreadProps lint rule in the biome_js_analyze crate.
Description check ✅ Passed The description references the ESLint rule being ported and links the issue, which is related to the changeset.
Linked Issues check ✅ Passed The PR implements the noDuplicatedSpreadProps rule as requested in issue #7658, porting jsx-props-no-spread-multi from eslint-plugin-react.
Out of Scope Changes check ✅ Passed All changes are within scope: rule implementation, options module, test cases, and changeset entry all relate to the noDuplicatedSpreadProps rule.

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 96dc7bd and 92df86e.

⛔ Files ignored due to path filters (9)
  • crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs is excluded by !**/migrate/eslint_any_rule_to_biome.rs and included by **
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_configuration/src/generated/domain_selector.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
  • crates/biome_js_analyze/src/lint/nursery.rs is excluded by !**/nursery.rs and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/invalid.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/valid.jsx.snap is excluded by !**/*.snap and included by **
  • packages/@biomejs/backend-jsonrpc/src/workspace.ts is excluded by !**/backend-jsonrpc/src/workspace.ts and included by **
  • packages/@biomejs/biome/configuration_schema.json is excluded by !**/configuration_schema.json and included by **
📒 Files selected for processing (6)
  • .changeset/clean-swans-act.md (1 hunks)
  • crates/biome_js_analyze/src/lint/nursery/no_duplicated_spread_props.rs (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/invalid.jsx (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/valid.jsx (1 hunks)
  • crates/biome_rule_options/src/lib.rs (1 hunks)
  • crates/biome_rule_options/src/no_duplicated_spread_props.rs (1 hunks)

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
crates/biome_js_analyze/tests/specs/nursery/noDuplicateSpread/valid.jsx (1)

1-12: Valid fixtures cover basics; consider a couple of extra shapes

The three cases nicely exercise “single spread” and “multiple different spreads”. If you feel like padding coverage, you could add:

  • A non–self-closing element (<div>...</div>) to hit the JsxOpeningElement branch explicitly.
  • A case mixing identifier and non-identifier spreads to ensure they peacefully coexist once the core logic is adjusted.

Not blocking, just belt-and-braces.

crates/biome_js_analyze/src/lint/nursery/no_duplicate_spread.rs (1)

89-101: Diagnostic text is clear; tiny wording nit (optional)

“The expression X has spread more than once” is understandable, but “has been spread more than once” might read more naturally. Totally optional, the current wording is perfectly usable.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fb8e3e7 and db08a4c.

⛔ Files ignored due to path filters (9)
  • crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs is excluded by !**/migrate/eslint_any_rule_to_biome.rs and included by **
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_configuration/src/generated/domain_selector.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
  • crates/biome_js_analyze/src/lint/nursery.rs is excluded by !**/nursery.rs and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicateSpread/invalid.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicateSpread/valid.jsx.snap is excluded by !**/*.snap and included by **
  • packages/@biomejs/backend-jsonrpc/src/workspace.ts is excluded by !**/backend-jsonrpc/src/workspace.ts and included by **
  • packages/@biomejs/biome/configuration_schema.json is excluded by !**/configuration_schema.json and included by **
📒 Files selected for processing (6)
  • .changeset/clean-swans-act.md (1 hunks)
  • crates/biome_js_analyze/src/lint/nursery/no_duplicate_spread.rs (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicateSpread/invalid.jsx (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicateSpread/valid.jsx (1 hunks)
  • crates/biome_rule_options/src/lib.rs (1 hunks)
  • crates/biome_rule_options/src/no_duplicate_spread.rs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/lint/nursery/no_duplicate_spread.rs (1)
crates/biome_analyze/src/rule.rs (4)
  • recommended (602-605)
  • sources (617-620)
  • same (246-251)
  • domains (632-635)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Check JS Files
  • GitHub Check: autofix
  • GitHub Check: Test Node.js API
  • GitHub Check: Documentation
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: End-to-end tests
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Check Dependencies
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_js_parser)
🔇 Additional comments (5)
.changeset/clean-swans-act.md (1)

1-11: Changeset description matches the implemented rule

Text and example line up with noDuplicateSpread’s current behaviour (blocking duplicate identifier spreads). Nothing to tweak here.

crates/biome_rule_options/src/lib.rs (1)

66-66: Options module wiring looks good

The no_duplicate_spread module is exported consistently with the rest of the rule options; no issues from my side.

crates/biome_js_analyze/tests/specs/nursery/noDuplicateSpread/invalid.jsx (1)

1-3: Invalid fixture is focused and fine as-is

This is a nice minimal repro for the duplicate identifier case; the free props identifier is acceptable for a syntactic lint fixture.

crates/biome_rule_options/src/no_duplicate_spread.rs (1)

1-6: Options container matches existing patterns

NoDuplicateSpreadOptions mirrors other empty options structs (derives, serde attrs, optional schema), so this should plug into the config story cleanly.

If you want extra peace of mind, run the existing rule-options tests (or codegen checks) to ensure the new module is picked up everywhere.

crates/biome_js_analyze/src/lint/nursery/no_duplicate_spread.rs (1)

11-40: Double‑check alignment with ESLint’s jsx-props-no-spread-multi

This rule only flags duplicate spreads of the same identifier, whereas the original ESLint rule’s name suggests “more than one spread at all”. You also classify it as same() logic in sources.

If the intent is to be a narrower variant (“no duplicate spreads”) rather than a strict port, it might be worth:

  • Confirming against the upstream rule docs; and
  • Either adjusting the description / sources classification, or clarifying in the docs that this is intentionally more permissive.

Just want to avoid surprising users who come in expecting ESLint‑identical behaviour.

@codspeed-hq
Copy link

codspeed-hq bot commented Nov 15, 2025

CodSpeed Performance Report

Merging #8116 will not alter performance

Comparing Netail:feat/no-duplicate-spread (92df86e) with main (91484d1)

Summary

✅ 58 untouched
⏩ 95 skipped1

Footnotes

  1. 95 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@ematipico
Copy link
Member

ematipico commented Nov 15, 2025

This rule should be called noReactDuplicateSpread. For these reasons:

  • the suggested name is too generic and it could be misunderstood for something related to the spread operator of the JavaScript language
  • it's a rule that belongs to a domain, so new rules should carry the domain name in their name

EDIT:

Alternatively, consider if this function should be added to the Solid domain too.

Some other suggestions: noDuplicatedSpreadProps

@Netail
Copy link
Member Author

Netail commented Nov 15, 2025

This rule should be called noReactDuplicateSpread. For these reasons:

  • the suggested name is too generic and it could be misunderstood for something related to the spread operator of the JavaScript language
  • it's a rule that belongs to a domain, so new rules should carry the domain name in their name

EDIT:

Alternatively, consider if this function should be added to the Solid domain too.

Some other suggestions: noDuplicatedSpreadProps

Yup, I think adding to the solid domain is valid, both jsx. Would go for the non-react name

@Netail Netail changed the title feat(js_biome_analyze): implement noDuplicateSpread feat(js_biome_analyze): implement noDuplicatedSpreadProps Nov 17, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
crates/biome_js_analyze/src/lint/nursery/no_duplicated_spread_props.rs (1)

48-66: Fix the logic to skip non-identifier spreads instead of abandoning the entire check.

This is the same critical bug identified in the previous review. The ? operators on lines 55-57 cause early return when a non-identifier spread is encountered (e.g., {...foo.bar}), preventing detection of subsequent duplicate identifier spreads.

Please apply the fix suggested in the previous review comment, which replaces the ? chain with explicit pattern matching and continue statements. Also add the regression test case to invalid.jsx as requested earlier in this review.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between db08a4c and e90352a.

⛔ Files ignored due to path filters (9)
  • crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs is excluded by !**/migrate/eslint_any_rule_to_biome.rs and included by **
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_configuration/src/generated/domain_selector.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
  • crates/biome_js_analyze/src/lint/nursery.rs is excluded by !**/nursery.rs and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/invalid.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/valid.jsx.snap is excluded by !**/*.snap and included by **
  • packages/@biomejs/backend-jsonrpc/src/workspace.ts is excluded by !**/backend-jsonrpc/src/workspace.ts and included by **
  • packages/@biomejs/biome/configuration_schema.json is excluded by !**/configuration_schema.json and included by **
📒 Files selected for processing (6)
  • .changeset/clean-swans-act.md (1 hunks)
  • crates/biome_js_analyze/src/lint/nursery/no_duplicated_spread_props.rs (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/invalid.jsx (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/valid.jsx (1 hunks)
  • crates/biome_rule_options/src/lib.rs (1 hunks)
  • crates/biome_rule_options/src/no_duplicated_spread_props.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/biome_rule_options/src/lib.rs
🧰 Additional context used
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/lint/nursery/no_duplicated_spread_props.rs (1)
crates/biome_analyze/src/rule.rs (4)
  • recommended (602-605)
  • sources (617-620)
  • same (246-251)
  • domains (632-635)
🔇 Additional comments (7)
crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/valid.jsx (1)

1-12: LGTM! Valid test cases are well-structured.

The three test cases properly cover scenarios where duplicate spreads don't occur: single spread with props, different spreads, and proper separation.

.changeset/clean-swans-act.md (1)

5-5: Verify the rule name with the maintainer.

The PR comments indicate that @ematipico recommended naming this rule noReactDuplicateSpread rather than noDuplicatedSpreadProps, arguing that domain-specific rules should include the domain name to avoid confusion. Please confirm which name should be used before finalising the changeset.

crates/biome_rule_options/src/no_duplicated_spread_props.rs (1)

1-6: LGTM! Options struct is properly scaffolded.

Empty options struct with all the right derives and serde attributes for future extensibility.

crates/biome_js_analyze/src/lint/nursery/no_duplicated_spread_props.rs (4)

1-10: LGTM! Imports are appropriate.

All necessary dependencies are present for the rule implementation.


32-38: Confirm the rule name with maintainer feedback.

The PR discussion indicates @ematipico recommended noReactDuplicateSpread to clearly indicate this is React-specific and avoid confusion with JavaScript spread. The current name noDuplicatedSpreadProps doesn't include the domain prefix. Please confirm which naming convention to follow.

On line 38, you've correctly included both RuleDomain::React and RuleDomain::Solid as discussed.


42-46: LGTM! Node union covers both JSX element types.

Properly handles both opening and self-closing elements.


68-104: LGTM! Rule implementation is clean.

The trait implementation correctly handles both JSX element types, and the diagnostic message clearly explains the issue and suggests a fix. The logic is sound once the validate_attributes function is corrected.

@Netail Netail force-pushed the feat/no-duplicate-spread branch 3 times, most recently from 040af8d to a65cf3a Compare November 17, 2025 10:08
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
crates/biome_js_analyze/src/lint/nursery/no_duplicated_spread_props.rs (1)

48-66: Critical bug: early return prevents detection of subsequent duplicates.

Lines 56-57 use the ? operator, which returns None from the entire function if name() or value_token() fail. This means if you encounter a malformed spread attribute after valid ones, you'll abandon the entire check instead of continuing to validate remaining attributes.

The past review comment correctly identified this issue and suggested using let-else with continue, but the fix was never applied.

Apply this diff to fix the logic:

 fn validate_attributes(list: &JsxAttributeList) -> Option<String> {
     let mut seen_spreads = HashSet::new();

     for attribute in list {
-        if let AnyJsxAttribute::JsxSpreadAttribute(spread) = attribute
-            && let Some(argument) = spread.argument().ok()
-            && let Some(express) = argument.as_js_identifier_expression()
-        {
-            let name = express.name().ok()?;
-            let value_token = name.value_token().ok()?;
-            let text = value_token.text_trimmed().to_string();
-            if !seen_spreads.insert(text.clone()) {
-                return Some(text);
-            }
+        if let AnyJsxAttribute::JsxSpreadAttribute(spread) = attribute {
+            let Ok(argument) = spread.argument() else {
+                continue;
+            };
+
+            let Some(express) = argument.as_js_identifier_expression() else {
+                continue;
+            };
+
+            let Ok(name) = express.name() else {
+                continue;
+            };
+
+            let Ok(value_token) = name.value_token() else {
+                continue;
+            };
+
+            let text = value_token.text_trimmed().to_string();
+            if !seen_spreads.insert(text) {
+                return Some(text);
+            }
         }
     }

     None
 }
🧹 Nitpick comments (1)
crates/biome_js_analyze/src/lint/nursery/no_duplicated_spread_props.rs (1)

59-59: Remove unnecessary clone.

The text value isn't used after insert, so cloning is wasteful. The diff above removes it.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 040af8d and a65cf3a.

⛔ Files ignored due to path filters (9)
  • crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs is excluded by !**/migrate/eslint_any_rule_to_biome.rs and included by **
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_configuration/src/generated/domain_selector.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
  • crates/biome_js_analyze/src/lint/nursery.rs is excluded by !**/nursery.rs and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/invalid.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/valid.jsx.snap is excluded by !**/*.snap and included by **
  • packages/@biomejs/backend-jsonrpc/src/workspace.ts is excluded by !**/backend-jsonrpc/src/workspace.ts and included by **
  • packages/@biomejs/biome/configuration_schema.json is excluded by !**/configuration_schema.json and included by **
📒 Files selected for processing (6)
  • .changeset/clean-swans-act.md (1 hunks)
  • crates/biome_js_analyze/src/lint/nursery/no_duplicated_spread_props.rs (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/invalid.jsx (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/valid.jsx (1 hunks)
  • crates/biome_rule_options/src/lib.rs (1 hunks)
  • crates/biome_rule_options/src/no_duplicated_spread_props.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/valid.jsx
🧰 Additional context used
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/lint/nursery/no_duplicated_spread_props.rs (1)
crates/biome_analyze/src/rule.rs (4)
  • recommended (602-605)
  • sources (617-620)
  • same (246-251)
  • domains (632-635)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: End-to-end tests
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Check Dependencies
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Documentation
  • GitHub Check: Test Node.js API
  • GitHub Check: Check JS Files
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: autofix
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_js_formatter)
🔇 Additional comments (4)
crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/invalid.jsx (1)

1-7: Test coverage looks good.

Both test cases appropriately cover duplicate spread detection, including the regression case with non-identifier spreads mixed with duplicates.

.changeset/clean-swans-act.md (1)

1-11: Changeset is clear and well-documented.

The description and example appropriately communicate the new rule to users.

crates/biome_rule_options/src/no_duplicated_spread_props.rs (1)

1-6: Options struct is properly configured.

The derives and serde attributes are appropriate for a rule options type, even though it's currently empty.

crates/biome_js_analyze/src/lint/nursery/no_duplicated_spread_props.rs (1)

68-103: Rule implementation and diagnostic are well-structured.

The trait implementation correctly dispatches to both JSX element types, and the diagnostic message is clear and actionable.

@Netail Netail force-pushed the feat/no-duplicate-spread branch from a65cf3a to 7efaf59 Compare November 19, 2025 15:55
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/biome_js_analyze/src/lint/nursery/no_duplicated_spread_props.rs (1)

59-59: Minor: Unnecessary allocation

The .clone() is unnecessary here. Consider using a HashSet<&str> instead to avoid allocating strings for comparison, or simply use text directly without cloning.

-            if !seen_spreads.insert(text.clone()) {
-                return Some(text);
+            if !seen_spreads.insert(text.as_str()) {
+                return Some(text);
             }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a65cf3a and 7efaf59.

⛔ Files ignored due to path filters (9)
  • crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs is excluded by !**/migrate/eslint_any_rule_to_biome.rs and included by **
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_configuration/src/generated/domain_selector.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
  • crates/biome_js_analyze/src/lint/nursery.rs is excluded by !**/nursery.rs and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/invalid.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/valid.jsx.snap is excluded by !**/*.snap and included by **
  • packages/@biomejs/backend-jsonrpc/src/workspace.ts is excluded by !**/backend-jsonrpc/src/workspace.ts and included by **
  • packages/@biomejs/biome/configuration_schema.json is excluded by !**/configuration_schema.json and included by **
📒 Files selected for processing (6)
  • .changeset/clean-swans-act.md (1 hunks)
  • crates/biome_js_analyze/src/lint/nursery/no_duplicated_spread_props.rs (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/invalid.jsx (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/valid.jsx (1 hunks)
  • crates/biome_rule_options/src/lib.rs (1 hunks)
  • crates/biome_rule_options/src/no_duplicated_spread_props.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/invalid.jsx
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/valid.jsx
  • .changeset/clean-swans-act.md
🧰 Additional context used
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/lint/nursery/no_duplicated_spread_props.rs (1)
crates/biome_analyze/src/rule.rs (4)
  • recommended (602-605)
  • sources (617-620)
  • same (246-251)
  • domains (632-635)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: Check JS Files
  • GitHub Check: Documentation
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: End-to-end tests
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Check Dependencies
  • GitHub Check: Test Node.js API
  • GitHub Check: autofix
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_configuration)
🔇 Additional comments (6)
crates/biome_rule_options/src/no_duplicated_spread_props.rs (1)

1-6: LGTM!

Empty options struct follows the standard pattern for rules without configurable options. All necessary derives and attributes are present.

crates/biome_js_analyze/src/lint/nursery/no_duplicated_spread_props.rs (5)

1-10: LGTM!

All imports are necessary and correctly organised.


11-40: LGTM!

Rule declaration is well-documented with clear examples. The domains (React and Solid) and sources correctly reflect the PR discussion and original eslint-react rule.


42-46: LGTM!

Union correctly includes both opening and self-closing JSX elements.


68-87: LGTM!

Rule implementation correctly handles both JSX element variants.


89-103: LGTM!

Diagnostic message is clear and includes helpful guidance.

Comment on lines 42 to 66
declare_node_union! {
pub NoDuplicatedSpreadPropsQuery =
JsxOpeningElement
| JsxSelfClosingElement
}

fn validate_attributes(list: &JsxAttributeList) -> Option<String> {
let mut seen_spreads = HashSet::new();

for attribute in list {
if let AnyJsxAttribute::JsxSpreadAttribute(spread) = attribute
&& let Some(argument) = spread.argument().ok()
{
let express = argument.as_js_identifier_expression()?;
let name = express.name().ok()?;
let value_token = name.value_token().ok()?;
let text = value_token.text_trimmed().to_string();
if !seen_spreads.insert(text.clone()) {
return Some(text);
}
}
}

None
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a general rule, we tend to have the implementation of the rule after the impl Rule trait block

Comment on lines 42 to 66
declare_node_union! {
pub NoDuplicatedSpreadPropsQuery =
JsxOpeningElement
| JsxSelfClosingElement
}

fn validate_attributes(list: &JsxAttributeList) -> Option<String> {
let mut seen_spreads = HashSet::new();

for attribute in list {
if let AnyJsxAttribute::JsxSpreadAttribute(spread) = attribute
&& let Some(argument) = spread.argument().ok()
&& let Some(express) = argument.as_js_identifier_expression()
{
let name = express.name().ok()?;
let value_token = name.value_token().ok()?;
let text = value_token.text_trimmed().to_string();
if !seen_spreads.insert(text.clone()) {
return Some(text);
}
}
}

None
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move this code after impl Rule block?

Comment on lines 1 to 28
/* should not generate diagnostics */
const Valid1 = () => {
return <div {...props} something="else" />
}

const Valid2 = () => {
return <div something="else" {...props} />
}

const Valid3 = () => {
return <div {...props} something="else" {...otherProps} />
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We aren't testing opening elements. Can you please add more tests? e.g. <p {...s} {...s}></p>

@Netail Netail force-pushed the feat/no-duplicate-spread branch from 6a3094c to 1820f21 Compare November 19, 2025 23:17
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/valid.jsx (1)

1-32: Valid coverage looks good; optional extra sibling-element case

These cases nicely cover both <div></div> and self-closing JSX, with different spread positions and the empty-object spread. If you fancy one more edge case, a component with two sibling elements both spreading the same identifier (each only once) would make it explicit the rule is per-element, not per-component — but what you have is already solid.

crates/biome_js_analyze/src/lint/nursery/no_duplicated_spread_props.rs (2)

12-40: Docs and diagnostic read well; a couple of tiny wording nits

The rule description and example look clear, and the diagnostic is on point. If you want to polish the English a touch:

  • Line 14: “Enforces that each unique expression is only spread once.”
  • Line 76: “has been spread more than once.”
  • Line 80: maybe “…will lead to unnecessary work. Reduce spreads of this expression to one.” reads a bit smoother.

Totally cosmetic, feel free to ignore if you like the current phrasing better.

Also applies to: 69-82


86-103: Attribute scanning logic looks correct; current scope is identifiers-only

The validate_attributes loop is tidy and now correctly skips malformed spreads instead of bailing early, which fixes the earlier ? issue. As implemented, duplicates are only detected when the spread argument is a plain identifier expression (e.g. props), and anything more complex (this.props, foo.bar, calls, etc.) is intentionally ignored.

That’s a perfectly reasonable conservative first cut; if you ever decide you want parity with more expression shapes, this is the one place to extend. From a correctness perspective for identifier spreads, this looks good to ship.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7efaf59 and 6a3094c.

⛔ Files ignored due to path filters (2)
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/invalid.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/valid.jsx.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (3)
  • crates/biome_js_analyze/src/lint/nursery/no_duplicated_spread_props.rs (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/invalid.jsx (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/valid.jsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/biome_js_analyze/tests/specs/nursery/noDuplicatedSpreadProps/invalid.jsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: Documentation
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Test Node.js API
  • GitHub Check: End-to-end tests
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: Check Dependencies
  • GitHub Check: autofix
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Check JS Files

@Netail Netail force-pushed the feat/no-duplicate-spread branch from 1820f21 to 96dc7bd Compare November 21, 2025 18:37
Copy link
Member

@ematipico ematipico left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! I leave the merging to you 🖖

@Netail
Copy link
Member Author

Netail commented Nov 22, 2025

Awesome! I leave the merging to you 🖖

No computer for the weekend, but in case you guys wanna merge it already, be my guest :)

@dyc3 dyc3 force-pushed the feat/no-duplicate-spread branch from 96dc7bd to 92df86e Compare November 22, 2025 17:30
@dyc3 dyc3 added this pull request to the merge queue Nov 22, 2025
Merged via the queue into biomejs:main with commit b537918 Nov 22, 2025
18 of 19 checks passed
@github-actions github-actions bot mentioned this pull request Nov 22, 2025
@Netail Netail deleted the feat/no-duplicate-spread branch November 22, 2025 19:17
@github-actions github-actions bot mentioned this pull request Nov 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-CLI Area: CLI A-Diagnostic Area: diagnostocis A-Linter Area: linter A-Project Area: project L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

📎 Port jsx-props-no-spread-multi from react rules

3 participants