Skip to content

Conversation

@Bertie690
Copy link
Contributor

@Bertie690 Bertie690 commented Oct 25, 2025

Summary

Fixes #7230

For why this ended up as more than a simple 3-loc change:
I initially added a very simple boolean for the + operator having leading whitespace inside is_stylistic_concatenation.
I then realized that the function was being checked before the (woefully named) is_concatenation actually checked that it was in fact a valid concatenation.
I also moved around a bit of code so that is_concatenation (now rechristened get_concatenation_range) now calls is_stylistic_concatenation after verifying that the operators involved are in fact ones that would otherwise trigger the rule.

Note

Whether this was actually necessary for the rule to work is up for debate, but the prior code flow and naming weirdness was a code smell that I didn't really appreciate.
Also this would make it easier to resolve future issues/configurations with this stuff

(P.S: I could've denested more but chose not to due to time constraints/laziness).

Test Plan

I added a few tests to valid.js (seen as all we're doing is adding more valid constructs, there's no need to edit invalid.js.

Docs

Added a few extra rustdocs for completeness' sake, but other than that nothing else much

…catenation operators from `operatorLinebreak=before`

I initially added a simple or check for the `+` operator having leading whitespace.
I realized that check could and will fire on regular addition, so it needed to check that the thing actually was a string.
`
@changeset-bot
Copy link

changeset-bot bot commented Oct 25, 2025

🦋 Changeset detected

Latest commit: 5ef9571

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-Linter Area: linter L-JavaScript Language: JavaScript and super languages labels Oct 25, 2025
@Bertie690 Bertie690 changed the title fix:(lint): noUselessStringConcat now correctly detects leading concatenation operators from operatorLinebreak=before fix(lint): noUselessStringConcat now correctly detects leading concatenation operators from operatorLinebreak=before Oct 25, 2025
@Bertie690 Bertie690 marked this pull request as ready for review October 25, 2025 22:48
we love it when you move 18-month old code into a new function and it trips all the linters again...
(not salty just funny)
ffs man since when
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 25, 2025

Walkthrough

Reworks the noUselessStringConcat lint rule to correctly handle multi-line concatenations where the + operator begins a line (operatorLinebreak="before"), avoiding false positives. Detection was broadened to include numeric operands and nested/parenthesised concatenations. A new get_concatenation_range extractor and is_stylistic_concatenation check drive diagnostic range computation, skipping stylistic multi-line cases. Tests add leading-operator multi-line concatenation cases. No public/exported API signatures were changed.

Suggested reviewers

  • ematipico
  • arendjr

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly and specifically describes the main fix: enabling the noUselessStringConcat rule to correctly handle leading concatenation operators when operatorLinebreak is set to "before". It's concise, avoids vague terms, and directly relates to the primary change addressing issue #7230. The title accurately reflects what a teammate would find if scanning the commit history.
Linked Issues Check ✅ Passed The changes directly address issue #7230's core requirement: ensuring the noUselessStringConcat rule operates consistently regardless of operator linebreak position. The fix introduces is_stylistic_concatenation to correctly identify and exclude multiline string concatenations (whether operators are trailing or leading) from being flagged as useless. Test cases added to valid.js confirm that both trailing and leading operator styles are now treated consistently as valid stylistic choices. The refactoring of the rule logic ensures valid concatenations are verified before applying stylistic checks, resolving the root cause.
Out of Scope Changes Check ✅ Passed All changes are squarely in-scope for fixing the reported issue. The rule implementation changes (no_useless_string_concat.rs) directly address the operator position detection problem, test additions (valid.js) validate the fix's correctness, and the changeset documentation properly records the patch. The refactoring of function names and logic flow is justified as addressing code smell and enabling future configuration improvements, which is a reasonable companion to the core fix.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 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 abb959c and 5ef9571.

📒 Files selected for processing (1)
  • .changeset/quick-apples-count.md (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • .changeset/quick-apples-count.md
⏰ 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). (11)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Documentation
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Check Dependencies
  • GitHub Check: Test Node.js API
  • GitHub Check: autofix

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: 0

🧹 Nitpick comments (9)
crates/biome_js_analyze/tests/specs/complexity/noUselessStringConcat/valid.js (1)

16-20: Nice additions; consider two extra edge cases.

Add leading-operator cases with a parenthesised left and with a chained group to lock in the fix:

  • ('foo' + 'bar')
    • 'baz'
  • (foo + bar)
    • baz

Keeps parity with trailing-operator tests and guards regressions.

Please run the suite for this spec to confirm these pass alongside the new logic.

.changeset/quick-apples-count.md (1)

5-5: Typos and header style.

  • “detectes” → “detects”.
  • Prefer a colon after the issue per guidelines.

Apply:

-Fixed [#7230](https://github.com/biomejs/biome/issues/7230) - [`noUselessStringConcat`](https://biomejs.dev/linter/rules/no-useless-string-concat/) now correctly detectes multiline strings with leading `+` operators (such as those from `operatorLinebreak="before"`).
+Fixed [#7230](https://github.com/biomejs/biome/issues/7230): [`noUselessStringConcat`](https://biomejs.dev/linter/rules/no-useless-string-concat/) now correctly detects multiline strings with leading `+` operators (such as those from `operatorLinebreak="before"`).
crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs (7)

63-71: Docs: call out leading-operator multiline explicitly.

You added a leading-operator example (great). Consider adding a short line stating that both trailing- and leading-operator multiline concatenations are exempt, so readers don’t infer it’s an accident.


95-100: Avoid ? inside boolean guard for clarity.

The parent_binary_expression? within the if condition is unconventional and risks accidental early-returns if refactored. Prefer a straight if let:

-        if parent_binary_expression.is_some()
-            && get_concatenation_range(&parent_binary_expression?).is_some()
-        {
+        if let Some(parent) = parent_binary_expression {
+            if get_concatenation_range(&parent).is_some() {
+                return None;
+            }
+        }
-            return None;
-        }

Please ensure no duplicate diagnostics appear for chains like "a" + "b" + "c" after this tweak.


183-212: Behaviour vs docs: numeric+string now considered concatenation.

is_string_concatenation treats "a" + 1 and 1 + "a" as concatenations (and action can fold them when both sides resolve to constants). The top-of-file docs currently say “2 literals … strings or template literals”. Either:

  • update docs to note numeric literals also fold into strings, or
  • constrain detection to string/template on both sides.

Your call, but please align docs/tests with behaviour.


240-248: Unary numeric expressions are not recognised as pure numeric calculations.

is_numeric_calculation only matches binary expressions whose children are numeric literals; cases like 1 + +2 or 1 + -2 won’t be considered “numeric calculation” here. If you intend to suppress diagnostics/fixes when the numeric side is a constant-but-unary expression, extend detection to simple unary +/- over number literals.

Would you like a small follow-up to handle JsUnaryExpression with +/- over JsNumberLiteralExpression?


275-304: Range start for parenthesised left-hand side.

When the left is a parenthesised concatenation, range_start uses the ( token rather than the inner first string. That’s fine for highlighting, but it makes the underline a tad broader than needed. If you prefer tighter spans, consider extracting from within the parentheses similarly to extract_concat_range.


306-312: Stylistic check looks solid; consider CRLF/comments adjacency.

has_leading_newline() should handle CRLF, but cases like + /* c */\n "bar" may place the newline on the comment rather than the operator/right token. If you want to exempt those too, you could treat “operator followed by only trivia up to a newline” as stylistic as well. Optional polish.

If you have bandwidth, add a test with an inline comment between the operator and the right literal on the next line.

Also applies to: 332-332


118-180: Fix output: quote style preservation.

js_string_literal(...) may choose a default quote style. If we want stable diffs with the formatter, consider preserving the left-most string’s quote style where possible.

Please confirm current output matches house style (single vs double quotes) after formatting.

📜 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 fe90c78 and 9ff99d6.

⛔ Files ignored due to path filters (1)
  • crates/biome_js_analyze/tests/specs/complexity/noUselessStringConcat/valid.js.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (3)
  • .changeset/quick-apples-count.md (1 hunks)
  • crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs (6 hunks)
  • crates/biome_js_analyze/tests/specs/complexity/noUselessStringConcat/valid.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
.changeset/*.md

📄 CodeRabbit inference engine (CONTRIBUTING.md)

.changeset/*.md: In changesets, only use #### or ##### headers; other header levels are not allowed
Changesets should cover user-facing changes only; internal changes do not need changesets
Use past tense for what you did and present tense for current Biome behavior in changesets
When fixing a bug in a changeset, start with an issue link (e.g., “Fixed #1234: …”)
When referencing a rule or assist in a changeset, include a link to its page on the website
Include code blocks in changesets when applicable to illustrate changes
End every sentence in a changeset with a period

Files:

  • .changeset/quick-apples-count.md
crates/biome_*_{syntax,parser,formatter,analyze,factory,semantic}/**

📄 CodeRabbit inference engine (CLAUDE.md)

Maintain the per-language crate structure: biome_{lang}_{syntax,parser,formatter,analyze,factory,semantic}

Files:

  • crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs
  • crates/biome_js_analyze/tests/specs/complexity/noUselessStringConcat/valid.js
crates/biome_*/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place core crates under /crates/biome_*/

Files:

  • crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs
  • crates/biome_js_analyze/tests/specs/complexity/noUselessStringConcat/valid.js
**/*.rs

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.rs: Format Rust files before committing (e.g., via just f which formats Rust)
Document rules, assists, and options with inline rustdoc in source

Files:

  • crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs
**/tests/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place test files under a tests/ directory in each crate

Files:

  • crates/biome_js_analyze/tests/specs/complexity/noUselessStringConcat/valid.js
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs (1)
crates/biome_js_parser/src/syntax/stmt.rs (1)
  • parenthesized_expression (933-943)
⏰ 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). (10)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Check Dependencies
  • GitHub Check: Documentation
  • GitHub Check: End-to-end tests
  • GitHub Check: Test Node.js API
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: autofix

@codspeed-hq
Copy link

codspeed-hq bot commented Oct 25, 2025

CodSpeed Performance Report

Merging #7874 will not alter performance

Comparing Bertie690:leading-concat (5ef9571) with main (fe90c78)

Summary

✅ 53 untouched
⏩ 85 skipped1

Footnotes

  1. 85 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.

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

📜 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 9ff99d6 and d86f95e.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs (6 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
crates/biome_*_{syntax,parser,formatter,analyze,factory,semantic}/**

📄 CodeRabbit inference engine (CLAUDE.md)

Maintain the per-language crate structure: biome_{lang}_{syntax,parser,formatter,analyze,factory,semantic}

Files:

  • crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs
crates/biome_*/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place core crates under /crates/biome_*/

Files:

  • crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs
**/*.rs

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.rs: Format Rust files before committing (e.g., via just f which formats Rust)
Document rules, assists, and options with inline rustdoc in source

Files:

  • crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs (3)
crates/biome_js_parser/src/syntax/stmt.rs (1)
  • parenthesized_expression (933-943)
packages/@biomejs/backend-jsonrpc/src/workspace.ts (1)
  • TextRange (9327-9327)
crates/biome_suppression/src/lib.rs (1)
  • range_start (512-527)
🪛 GitHub Actions: autofix.ci
crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs

[error] 277-303: mismatched types: expected Option<TextRange>, found (); the function likely returns Option<TextRange> but ends with an implicit () due to a stray semicolon. Remove the semicolon to return the value.

🪛 GitHub Actions: Benchmarks JS
crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs

[error] 277-303: mismatched types: get_concatenation_range() is declared to return Option but returns (). The log suggests removing a stray semicolon to return the value (e.g., remove the semicolon after the value).

🪛 GitHub Actions: Lint rule docs
crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs

[error] 277-277: mismatched types: expected Option<TextRange>, found () in get_concatenation_range. Implicitly returns (); likely a return/semicolon issue. See note at line 303 suggesting removing the semicolon to return the value.

🪛 GitHub Actions: Pull request
crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs

[error] 277-277: mismatched types: get_concatenation_range is expected to return Option but currently returns () (likely due to a stray semicolon). This caused the build in biome_js_analyze to fail during 'cargo documentation'.

🪛 GitHub Actions: Pull request Node.js
crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs

[error] 277-277: mismatched types: expected Option<TextRange>, found () in get_concatenation_range. The function implicitly returns () due to an empty body; consider returning Some(TextRange) or remove the trailing semicolon to return the value. (E0308)


[error] 303-303: help: remove this semicolon to return this value


[error] 277-277: Note: this is part of a compilation failure for biome_js_analyze (library). Fixing the above type mismatch should allow the build to proceed.

🔇 Additional comments (4)
crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs (4)

21-71: LGTM! Clear documentation of multiline concatenation styles.

The new examples effectively demonstrate both trailing and leading operator styles, aligning well with the fix for issue #7230.


183-212: LGTM! Comprehensive string concatenation detection.

The refactored predicate correctly handles string literals, template literals, nested binary expressions, parenthesised forms, and numeric coercion cases. The inline examples in the match arms are helpful.


265-273: LGTM! Clean helper for parenthesised concatenations.

The logic correctly unwraps parenthesised expressions and delegates to the existing predicate.


306-333: LGTM! Correctly detects leading-operator multiline concatenations.

The addition of has_newline_in_operator properly supports the operatorLinebreak=before style, addressing the core issue from #7230.

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/src/lint/complexity/no_useless_string_concat.rs (2)

272-280: Consider using is_string_concatenation for more robust validation.

is_binary_expression_with_literal_string checks a narrow pattern (non-identifier left + string right). For parenthesised concatenations, you should verify the expression is a valid string concatenation using is_string_concatenation, which handles all the cases including numeric coercion and nested concatenations.

Apply this diff to improve the validation:

 fn is_parenthesized_concatenation(expression: &Option<AnyJsExpression>) -> bool {
     if let Some(AnyJsExpression::JsParenthesizedExpression(parenthesized_expression)) = expression {
-        return is_binary_expression_with_literal_string(
-            &parenthesized_expression.expression().ok(),
-        );
+        if let Ok(AnyJsExpression::JsBinaryExpression(binary_expr)) = parenthesized_expression.expression() {
+            return is_string_concatenation(&binary_expr);
+        }
     }
 
     false
 }

316-342: LGTM! The TODO comment is worth addressing in future work.

The function correctly detects multiline concatenations with leading operators (operatorLinebreak=before), which resolves issue #7230. The TODO at Line 317 raises a valid point about numeric first operands—consider tracking this as a follow-up if the current behaviour isn't ideal.

Would you like me to open an issue to track the TODO about reviewing the behaviour when the first operand is numeric?

📜 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 d86f95e and dfcec77.

📒 Files selected for processing (3)
  • .changeset/quick-apples-count.md (1 hunks)
  • crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs (7 hunks)
  • crates/biome_js_analyze/tests/specs/complexity/noUselessStringConcat/valid.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/biome_js_analyze/tests/specs/complexity/noUselessStringConcat/valid.js
  • .changeset/quick-apples-count.md
🧰 Additional context used
📓 Path-based instructions (3)
crates/biome_*_{syntax,parser,formatter,analyze,factory,semantic}/**

📄 CodeRabbit inference engine (CLAUDE.md)

Maintain the per-language crate structure: biome_{lang}_{syntax,parser,formatter,analyze,factory,semantic}

Files:

  • crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs
crates/biome_*/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place core crates under /crates/biome_*/

Files:

  • crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs
**/*.rs

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.rs: Format Rust files before committing (e.g., via just f which formats Rust)
Document rules, assists, and options with inline rustdoc in source

Files:

  • crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs (2)
crates/biome_js_parser/src/syntax/stmt.rs (1)
  • parenthesized_expression (933-943)
packages/@biomejs/backend-jsonrpc/src/workspace.ts (1)
  • TextRange (9327-9327)
🪛 GitHub Actions: Lint rule docs
crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs

[error] 102-102: E0308: mismatched types. expected &JsBinaryExpression, found &Option. This occurs in the condition using get_concatenation_range(&parent_binary_expression).is_some().

🪛 GitHub Actions: Pull request Node.js
crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs

[error] 102-102: cargo build failed during step 'cargo build -p biome_cli --release': mismatched types: expected &JsBinaryExpression, found &Option in get_concatenation_range (line 102). This occurs in crate biome_js_analyze during type checking (E0308).

⏰ 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). (5)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: autofix
  • GitHub Check: Check Dependencies
🔇 Additional comments (3)
crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs (3)

191-219: LGTM!

The predicate correctly identifies string concatenations, including cases where one operand is numeric (e.g. 1 + "a" or "a" + 1), which aligns with the enhanced rule behaviour described in the PR objectives.


284-311: LGTM!

The stray semicolon issue from the previous review has been resolved. The function now correctly returns Option<TextRange> without a type mismatch, and the logic properly verifies string concatenation before extracting the diagnostic range.


20-22: LGTM!

The documentation updates clearly describe the enhanced behaviour, including numeric concatenation and the multiline leading-operator style. The examples align perfectly with the PR objectives and the code changes.

Also applies to: 34-36, 67-78

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 (2)
crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs (2)

23-23: Consider clarifying the multi-line exemption wording.

"Allowed" might imply the rule permits or encourages multi-line concatenations. Consider "ignored" or "not flagged" for clarity.

-/// Concatenation of multiple strings is allowed for multi-line strings (such as ones used to prevent exceeding the maximum line width).
+/// Multi-line string concatenations are not flagged by this rule (such as ones used to prevent exceeding the maximum line width).

415-427: Simplify the control flow.

The else if let Some(c) on line 421 is redundant—the outer while already ensures current_node.is_some().

 fn get_parent_binary_expression(node: &JsBinaryExpression) -> Option<JsBinaryExpression> {
     let mut current_node = node.parent();
 
-    while current_node.is_some() {
-        if let Some(AnyJsExpression::JsBinaryExpression(binary_expression)) = current_node {
+    while let Some(node) = current_node {
+        if let AnyJsExpression::JsBinaryExpression(binary_expression) = node {
             return Some(binary_expression);
-        } else if let Some(c) = current_node {
-            current_node = c.parent()
         }
+        current_node = node.parent();
     }
 
     None
 }
📜 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 5b95065 and 77d0eb3.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs (7 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
crates/biome_*_{syntax,parser,formatter,analyze,factory,semantic}/**

📄 CodeRabbit inference engine (CLAUDE.md)

Maintain the per-language crate structure: biome_{lang}_{syntax,parser,formatter,analyze,factory,semantic}

Files:

  • crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs
crates/biome_*/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place core crates under /crates/biome_*/

Files:

  • crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs
**/*.rs

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.rs: Format Rust files before committing (e.g., via just f which formats Rust)
Document rules, assists, and options with inline rustdoc in source

Files:

  • crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs
⏰ 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). (11)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Documentation
  • GitHub Check: End-to-end tests
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Check Dependencies
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Test Node.js API
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_js_formatter)
🔇 Additional comments (8)
crates/biome_js_analyze/src/lint/complexity/no_useless_string_concat.rs (8)

96-108: LGTM!

The suppression logic correctly prevents duplicate reports by checking the parent expression, ensuring only the outermost concatenation is flagged.


190-219: LGTM!

The logic correctly identifies string concatenations including mixed string+number cases, whilst avoiding false positives on pure numeric operations.


272-280: LGTM!

The new predicate correctly identifies parenthesised concatenations, matching the expanded scope of the rule.


282-311: LGTM!

The function correctly checks for string concatenation before testing stylistic exemptions, and the range calculation properly handles nested expressions. This successfully addresses issue #7230 by detecting leading-operator multi-line concatenations.


313-342: LGTM!

The operator newline check (line 318-320) correctly implements the fix for issue #7230. The TODO on line 317 is a reasonable flag for future review, though the current behaviour (treating numeric+string multi-line concatenations as stylistic) seems sensible.


344-400: LGTM!

The expanded extract_string_value correctly handles number literals (lines 353-357), parenthesised expressions, and templates, enabling the fix action for mixed string+number concatenations.


402-413: LGTM!

The recursive range extraction correctly identifies the leftmost operand in nested concatenations.


429-469: LGTM!

The recursive concatenation builder correctly handles nested expressions, though its complexity is justified by the requirement to flatten multi-level concatenations.

@Bertie690 Bertie690 requested a review from ematipico October 26, 2025 15:09
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 (1)
.changeset/quick-apples-count.md (1)

15-15: Clarify rule behaviour in code comment.

The comment "the rule correctly flags this as a stylistic concatenation and ignores it" is slightly contradictory—"flags" typically implies reporting an error, whilst "ignores" implies not reporting. For clarity, consider rewording to "the rule now correctly recognises this as a stylistic concatenation and does not flag it" or similar.

Applied to line 15:

-// Now, the rule correctly flags this as a stylistic concatenation and ignores it.
+// Now, the rule correctly recognises this as a stylistic concatenation and does not flag 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 77d0eb3 and abb959c.

📒 Files selected for processing (1)
  • .changeset/quick-apples-count.md (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
.changeset/*.md

📄 CodeRabbit inference engine (CONTRIBUTING.md)

.changeset/*.md: In changesets, only use #### or ##### headers; other header levels are not allowed
Changesets should cover user-facing changes only; internal changes do not need changesets
Use past tense for what you did and present tense for current Biome behavior in changesets
When fixing a bug in a changeset, start with an issue link (e.g., “Fixed #1234: …”)
When referencing a rule or assist in a changeset, include a link to its page on the website
Include code blocks in changesets when applicable to illustrate changes
End every sentence in a changeset with a period

Files:

  • .changeset/quick-apples-count.md
⏰ 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). (11)
  • GitHub Check: Test Node.js API
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Documentation
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Check Dependencies
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: autofix

@ematipico ematipico merged commit e617d36 into biomejs:main Oct 27, 2025
19 checks passed
@github-actions github-actions bot mentioned this pull request Oct 27, 2025
@Bertie690 Bertie690 deleted the leading-concat branch October 27, 2025 18:22
Conaclos pushed a commit that referenced this pull request Nov 1, 2025
…atenation operators from `operatorLinebreak=before` (#7874)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Linter Area: linter L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

💅 noUselessStringConcat unable to detect multiline strings when operatorLinebreak is set to before

2 participants