biome complexity/noUselessStringConcat
labelled safe
Analysis Nemotron 3 Super
Flags unnecessary concatenation of two or more string or template literals (including numbers converted via ToString) that can be safely merged into a single literal.
Fix: Replaces the concatenation expression with a single string or template literal where all parts are evaluated at compile time via ToString conversion and joined without alteration.
10 hazards
- Rewriting `"" + obj` to `"` + String(obj)` changes behavior if obj has a custom toString or valueOf with side effects.
- Concatenation involving a getter like `"" + obj.prop` may evaluate the getter once instead of twice if the property access is duplicated in the original.
- If any part of the concatenation is a function call with side effects (e.g., `"" + f()`), moving it into a literal context does not remove the call, but the rule does not fire on such cases so this is not a direct hazard of the fix.
- The fix assumes all operands are primitive-compatible at compile time; if an operand is a symbol, the original throws a TypeError but the fix would produce invalid output (though the rule does not flag Symbol concatenation).
- In strict mode, evaluating `"" + undefined` produces the string "undefined", same as the fix, so no behavioral change there.
- Template literals in the original may contain escaped characters (e.g., `"a\\b"`) that are preserved correctly in the fix, so no hazard from escaping.
- If the concatenation is part of a larger expression (e.g., `("a" + "b") + c`), replacing only the inner part preserves grouping and evaluation order.
- Comments adjacent to or inside the concatenation (e.g., `"a" /* comment */ + "b"`) are not moved by the fix and may be lost if the tool strips whitespace during rewrite.
- Automatic semicolon insertion is unaffected because the fix does not alter statement structure or line breaks.
- The fix does not introduce new variables or change scope, so hoisting and temporal dead zone behavior is preserved.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | invalid | Comments Adjacent/Inside Concat Lost | comments adjacent to or inside the concatenation (e.g., \"a\" /* comment */ + \"b\") are not moved by the fix and may be lost if the tool strips whitespace during rewrite. |
| r1-p002 | invalid | Comments Attached to Removed/Moved Code | comments attached to the removed or moved code |
| r1-p003 | silent prediction off | Getter Evaluated Once Instead of Twice | Concatenation involving a getter like \"" + obj.prop\" may evaluate the getter once instead of twice if the property access is duplicated in the original. |
| r1-p004 | fix ok prediction off | Template Literal Escaping Preserved | Template literals in the original may contain escaped characters (e.g., \"a\\b\") that are preserved correctly in the fix, so no hazard from escaping. |
| r1-p005 | invalid | Exceptions Inside Removed/Rewritten Code | exceptions thrown inside the code the fix removes or rewrites |
| r1-p006 | invalid | ASI and Semicolon-less Code Leading ( or [ | ASI and semicolon-less code, leading ( or [ on the next line |
| r1-p007 | invalid | No New Variables Introduced (Hoisting/TDZ Preserved) | The fix does not introduce new variables or change scope, so hoisting and temporal dead zone behavior is preserved. |
| r1-p008 | invalid | Class Fields, Static Blocks, Private Names, Super Calls | class fields, static blocks, private names, super calls |
| r2-p001 | silent prediction off | Getter Side Effects | getters, setters and Object.defineProperty side effects |
| r2-p002 | silent | Function Call Side Effects Not Triggered | If any part of the concatenation is a function call with side effects (e.g., `` + f()), moving it into a literal context does not remove the call, but the rule does not fire on such cases so this is n |
| r2-p003 | fix ok | ASI Unaffected by Fix Structure | Automatic semicolon insertion is unaffected because the fix does not alter statement structure or line breaks. |
| r2-p004 | silent prediction off | Strict Mode Undefined Coercion | In strict mode, evaluating `` + undefined produces the string "undefined", same as the fix, so no behavioral change there. |
| r2-p005 | silent prediction off | Var Hoisting in Loop | var hoisting and re-declaration inside loops or after use |
| r2-p006 | fix ok | Strict vs Sloppy Mode Semantics | strict vs sloppy mode semantics |
| r2-p007 | silent prediction off | Grouping Preserved in Larger Expression | If the concatenation is part of a larger expression (e.g., ("a" + "b") + c), replacing only the inner part preserves grouping and evaluation order. |
| r2-p008 | silent prediction off | This Binding in Arrow vs Function | this binding: arrow vs function, methods extracted from objects, call/apply/bind |
Sources
r1-p001 · Comments Adjacent/Inside Concat Lost (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-17T20-08-0)
1// Hazard 1: Comment inside concat lost if stripped
2const s = \"start\" /* middle */ + \"end\";
3console.log(s);
r1-p002 · Comments Attached to Removed/Moved Code (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-17T20-08-0)
1// Hazard 2: Comment on first part should remain after fix
2let msg = \"Hello\"; // greeting
3msg += \" \" + \"World\"; // space + world
4console.log(msg);
r1-p003 · Getter Evaluated Once Instead of Twice
1// Hazard 3: Getter called once vs twice
2const obj = {
3 get prop() {
4 console.log('getter called');
5 return 'value';
6 }
7};
8console.log('' + obj.prop + obj.prop);
getter called getter called valuevalue
r1-p004 · Template Literal Escaping Preserved
1// Hazard 4: Escaping in template literals - no issue expected
2const path = `C:\\Users\\name` + `\\file.txt`;
3console.log(path);
--- orig.js 2026-10-17 16:08:18.271817923 -0400 +++ fixed.js 2026-10-17 16:08:18.551807013 -0400 @@ -1,3 +1,3 @@ // Hazard 4: Escaping in template literals - no issue expected -const path = `C:\\Users\\name` + `\\file.txt`; +const path = "C:\\Users\\name\\file.txt"; console.log(path);
C:\Users\name\file.txt
r1-p005 · Exceptions Inside Removed/Rewritten Code (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-17T20-08-0)
1// Hazard 5: Exception in concatenation subexpression
2function risky() { throw new Error('fail'); }
3try {
4 const result = \"before\" + risky() + \"after\";
5} catch (e) {
6 console.log(e.constructor.name + ': ' + e.message);
7}
r1-p006 · ASI and Semicolon-less Code Leading ( or [ (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-17T20-08-0)
1// Hazard 6: ASI risk with leading (
2let x = 1
3let y = \"a\" + \"b\" + \"c\"
4(function(){ console.log('IIFE ran'); })()
5console.log(x + y)
r1-p007 · No New Variables Introduced (Hoisting/TDZ Preserved) (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-17T20-08-0)
1// Hazard 7: TDZ check - using let before declaration in same block
2if (true) {
3 console.log(typeof temp); // should be undefined due to TDZ
4 let temp = \"\" + \"value\";
5 console.log(temp);
6}
r1-p008 · Class Fields, Static Blocks, Private Names, Super Calls (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-17T20-08-0)
1// Hazard 8: Class with private field and static block
2class Test {
3 #private = \"secret\";
4 static {
5 console.log(\"static\" + \" block\");
6 }
7 constructor() {
8 console.log(\"instance\" + \" created\");
9 }
10}
11new Test();
r2-p001 · Getter Side Effects
1const obj = { get prop() { console.log('getter'); return 'X'; } };
2let result = '' + obj.prop + '' + obj.prop;
3console.log(result);
getter getter XX
r2-p002 · Function Call Side Effects Not Triggered
1function f() { console.log('called'); return 'Y'; }
2let s = '' + f();
3console.log(s);
called Y
r2-p003 · ASI Unaffected by Fix Structure
1let x = '\n'
2+ 'a' + 'b'
3console.log(x);
--- orig.js 2026-10-17 16:08:22.981635223 -0400 +++ fixed.js 2026-10-17 16:08:23.242625160 -0400 @@ -1,3 +1,2 @@ -let x = '\n' -+ 'a' + 'b' +let x = "\nab" console.log(x);
ab
r2-p004 · Strict Mode Undefined Coercion
1"use strict";
2let u = undefined;
3let s = '' + u;
4console.log(s);
undefined
r2-p005 · Var Hoisting in Loop
1for (var i = 0; i < 2; i++) {
2 var msg = 'i:' + i;
3 console.log(msg);
4}
5console.log(typeof msg);
i:0 i:1 string
r2-p006 · Strict vs Sloppy Mode Semantics
1"use strict";
2let s1 = 'a' + 'b';
3(function() {
4 'use sloppy';
5 let s2 = 'a' + 'b';
6 console.log(s1 === s2);
7})();
--- orig.js 2026-10-17 16:08:23.853601606 -0400 +++ fixed.js 2026-10-17 16:08:24.105591900 -0400 @@ -1,7 +1,7 @@ "use strict"; -let s1 = 'a' + 'b'; +let s1 = "ab"; (function() { 'use sloppy'; - let s2 = 'a' + 'b'; + let s2 = "ab"; console.log(s1 === s2); })();
true
r2-p007 · Grouping Preserved in Larger Expression
1let x = 'a';
2let y = 'b';
3let z = 'c';
4let r = (x + y) + z;
5console.log(r);
abc
r2-p008 · This Binding in Arrow vs Function
1const obj = { name: 'obj', getName: function() { return this.name; } };
2const fn = obj.getName;
3const arrowFn = () => this.name;
4let s1 = 'Hello ' + fn.call(obj);
5let s2 = 'Hello ' + arrowFn();
6console.log(s1, s2);