fixhunt

eslint prefer-template

labelled safe
Runs / 2026-10-22T18-30-37-797Z-ob8z-eslint-prefer-templateeslint 9.39.52026-10-22 18:30 UTC8 programs × 2 round(s)16 programs, 12 fired, 12 fixed, 0 findings, 2 rejectedmodel cost $0.0084

Analysis Nemotron 3 Super

The rule flags uses of binary '+' where at least one operand is a string literal.

Fix: Replaces the concatenation expression with a template literal where string literals become raw text and non-literal operands become ${expression} placeholders, preserving original grouping and operator precedence via implicit left-associativity.

11 hazards
  • If any operand is a getter with side effects, rewriting may change the number of times the getter is accessed due to altered evaluation order.
  • If an operand contains a comma operator, the template literal may group expressions differently, changing which value is used.
  • If an operand is a function call, the fix may evaluate it in a different temporal order relative to other side effects in the expression.
  • If the concatenation appears in a context expecting exactly two operands (e.g., tagged template with custom parsing), the rewrite breaks the expected argument structure.
  • If an operand is undefined or null, String() conversion in the template may produce 'undefined'/'null' while the original concatenation could throw if the operand was a Symbol (though Symbols are rare in practice).
  • If the code relies on the left-to-right evaluation order of the '+' operator for side effects (e.g., "a" + (b, console.log("side"), c)), the fix may reorder those side effects.
  • If the concatenation is inside a larger expression where operator precedence matters (e.g., "a" + b * c), the fix does not add parentheses, potentially changing meaning if the template literal is misinterpreted (though template literals have high precedence, this is generally safe).
  • If the string literal contains backticks, dollar signs, or curly braces, the fix does not escape them, potentially altering the template literal's meaning (e.g., "`${x}${"}" becomes `` `${x}${}` `` which is invalid or changes interpolation).
  • If the expression includes decorators or metadata comments attached to specific tokens, the rewrite may discard or misplace those comments.
  • If the concatenation is part of a sequence expression where the result is ignored (e.g., 0, "a" + b), removing the '+' operator may drop a deliberate no-op if the linter or build step depends on the syntactic form.
  • If an operand is a class field access that depends on runtime 'this' binding, the fix does not alter binding but may change when 'this' is evaluated if the operand is moved relative to other side-effecting expressions.

Programs

idresulttitlehazard
r1-p001fix okPrecedence in Larger ExpressionIf the concatenation is inside a larger expression where operator precedence matters, the fix does not add parentheses, potentially changing meaning.
r1-p002fix okThis Binding: Arrow vs Function Method Extractionthis binding: arrow vs function, methods extracted from objects, call/apply/bind
r1-p003fix okFunction Declarations Inside Blocksfunction declarations inside blocks, switch cases, labeled statements
r1-p004fix okNumeric/String Coercion Differencesnumeric/string coercion: + vs template literal, objects with toString/valueOf, Symbol, BigInt
r1-p005fix ok prediction offUndefined/null vs Symbol ConversionIf an operand is undefined or null, String() conversion in the template may produce 'undefined'/'null' while the original concatenation could throw if the operand was a Symbol
r1-p006fix okComments Attached to Removed/Moved Codecomments attached to the removed or moved code
r1-p007fix okGetter Side Effects and Evaluation OrderIf any operand is a getter with side effects, rewriting may change the number of times the getter is accessed due to altered evaluation order.
r1-p008fix okTemporal Dead Zone After Hoisting/Reorderingtemporal dead zone after code is hoisted or reordered
r2-p001fix okComma Operator Grouping ChangedIf an operand contains a comma operator, the template literal may group expressions differently, changing which value is used.
r2-p002invalidLabel/Break Control Flow Interferencelabel/break/continue control flow, finally blocks, early returns in nested blocks
r2-p003invalidLeft-to-Right Evaluation Order Side EffectsIf the code relies on the left-to-right evaluation order of the '+' operator for side effects (e.g., "a" + (b, console.log("side"), c)), the fix may reorder those side effects.
r2-p004silent prediction offPrecedence in Larger ExpressionIf the concatenation is inside a larger expression where operator precedence matters (e.g., "a" + b * c), the fix does not add parentheses, potentially changing meaning if the template literal is misi
r2-p005fix okVar Hoisting and Re-declarationvar hoisting and re-declaration inside loops or after use
r2-p006fix okOrder of Evaluation - Side Effects Run Twice/Neverorder of evaluation when expressions are merged, duplicated or deleted (side effects run twice or never)
r2-p007silentSequence Expression with Ignored ResultIf the concatenation is part of a sequence expression where the result is ignored (e.g., 0, "a" + b), removing the '+' operator may drop a deliberate no-op if the linter or build step depends on the s
r2-p008fix okGetter Side Effects and Evaluation Ordergetters, setters and Object.defineProperty side effects

Sources

r1-p001 · Precedence in Larger Expression
1function f() {
2 return "result:" + 2 * 3;
3}
4console.log(f());
--- orig.js	2026-10-22 14:31:24.400152237 -0400
+++ fixed.js	2026-10-22 14:31:28.327171922 -0400
@@ -1,4 +1,4 @@
 function f() {
-  return "result:" + 2 * 3;
+  return `result:${  2 * 3}`;
 }
 console.log(f());
result:6
r1-p002 · This Binding: Arrow vs Function Method Extraction
1const obj = {
2 value: 42,
3 getValue: function() { return this.value; }
4};
5const fn = obj.getValue;
6console.log("Value is " + fn());
--- orig.js	2026-10-22 14:31:24.401152242 -0400
+++ fixed.js	2026-10-22 14:31:27.619168299 -0400
@@ -3,4 +3,4 @@
   getValue: function() { return this.value; }
 };
 const fn = obj.getValue;
-console.log("Value is " + fn());
+console.log(`Value is ${  fn()}`);
Value is undefined
r1-p003 · Function Declarations Inside Blocks
1if (true) {
2 function helper() { return "ok"; }
3 console.log("Status: " + helper());
4}
--- orig.js	2026-10-22 14:31:24.402152247 -0400
+++ fixed.js	2026-10-22 14:31:27.067165491 -0400
@@ -1,4 +1,4 @@
 if (true) {
   function helper() { return "ok"; }
-  console.log("Status: " + helper());
+  console.log(`Status: ${  helper()}`);
 }
Status: ok
r1-p004 · Numeric/String Coercion Differences
1const obj = {
2 toString() { return "obj"; }
3};
4console.log("Value: " + obj);
--- orig.js	2026-10-22 14:31:24.403152252 -0400
+++ fixed.js	2026-10-22 14:31:27.602168213 -0400
@@ -1,4 +1,4 @@
 const obj = {
   toString() { return "obj"; }
 };
-console.log("Value: " + obj);
+console.log(`Value: ${  obj}`);
Value: obj
r1-p005 · Undefined/null vs Symbol Conversion
1let x = undefined;
2try {
3 console.log("Result: " + x);
4} catch (e) {
5 console.log("Caught: " + e.name + ": " + e.message);
6}
--- orig.js	2026-10-22 14:31:24.403152252 -0400
+++ fixed.js	2026-10-22 14:31:27.601168208 -0400
@@ -1,6 +1,6 @@
 let x = undefined;
 try {
-  console.log("Result: " + x);
+  console.log(`Result: ${  x}`);
 } catch (e) {
-  console.log("Caught: " + e.name + ": " + e.message);
+  console.log(`Caught: ${  e.name  }: ${  e.message}`);
 }
Result: undefined
r1-p006 · Comments Attached to Removed/Moved Code
1let msg = "Hello" // comment
2 + world;
3console.log(msg);
--- orig.js	2026-10-22 14:31:24.404152256 -0400
+++ fixed.js	2026-10-22 14:31:28.293171747 -0400
@@ -1,3 +1,3 @@
-let msg = "Hello" // comment
-          + world;
+let msg = `Hello${ // comment
+           world}`;
 console.log(msg);
r1-p007 · Getter Side Effects and Evaluation Order
1let count = 0;
2const obj = {
3 get value() {
4 count++;
5 return "val";
6 }
7};
8console.log("Start" + obj.value + "End");
9console.log("Getter called " + count + " times");
--- orig.js	2026-10-22 14:31:28.737174035 -0400
+++ fixed.js	2026-10-22 14:31:31.942190926 -0400
@@ -5,5 +5,5 @@
     return "val";
   }
 };
-console.log("Start" + obj.value + "End");
-console.log("Getter called " + count + " times");
+console.log(`Start${  obj.value  }End`);
+console.log(`Getter called ${  count  } times`);
StartvalEnd
Getter called 1 times
r1-p008 · Temporal Dead Zone After Hoisting/Reordering
1console.log("Value: " + tmp);
2let tmp = "initialized";
--- orig.js	2026-10-22 14:31:29.227176574 -0400
+++ fixed.js	2026-10-22 14:31:32.465193753 -0400
@@ -1,2 +1,2 @@
-console.log("Value: " + tmp);
+console.log(`Value: ${  tmp}`);
 let tmp = "initialized";
r2-p001 · Comma Operator Grouping Changed
1let r = '';
2const obj = { toString: () => { r += 'x'; return 42; } };
3console.log(('start' + (obj, obj)) === ('start' + `${obj}${obj}`));
--- orig.js	2026-10-22 14:31:40.059236679 -0400
+++ fixed.js	2026-10-22 14:31:42.143249108 -0400
@@ -1,3 +1,3 @@
 let r = '';
 const obj = { toString: () => { r += 'x'; return 42; } };
-console.log(('start' + (obj, obj)) === ('start' + `${obj}${obj}`));
+console.log((`start${  obj, obj}`) === ('start' + `${obj}${obj}`));
false
r2-p002 · Label/Break Control Flow Interference (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-22T18-30-3)
1outer: {
2 try {
3 if (true) break outer;
4 console.log('unreachable');
5 } finally {
6 console.log('finally ran');
7 }
8}
9console.log('done' + (() => { break outer; })());
r2-p003 · Left-to-Right Evaluation Order Side Effects (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-22T18-30-3)
1let log = '';
2const getA = () => { log += 'a'; return 'A'; };
3const getB = () => { log += 'b'; return 'B'; };
4console.log(getA() + (getB(), log += 'side', getB()) + '': log);
r2-p004 · Precedence in Larger Expression
1console.log(1 + 2 * '3' + 4); // Before: 1+(2*'3')+4 = 1+6+4=11
2 // After fix would be same due to precedence, but we test coercion:
3console.log(typeof (1 + 2 * '3' + 4));
11
number
r2-p005 · Var Hoisting and Re-declaration
1for (var i = 0; i < 2; i++) {
2 var msg = 'i:' + i;
3 console.log(msg);
4 if (i === 0) var msg = 'redeclared'; // hoisted, but block-scoped effect
5}
6console.log('final i:', i);
--- orig.js	2026-10-22 14:31:40.062236697 -0400
+++ fixed.js	2026-10-22 14:31:44.524263645 -0400
@@ -1,5 +1,5 @@
 for (var i = 0; i < 2; i++) {
-  var msg = 'i:' + i;
+  var msg = `i:${  i}`;
   console.log(msg);
   if (i === 0) var msg = 'redeclared'; // hoisted, but block-scoped effect
 }
i:0
i:1
final i: 2
r2-p006 · Order of Evaluation - Side Effects Run Twice/Never
1let count = 0;
2const inc = () => ++count;
3console.log(('a' + inc()) + ('b' + inc()), 'count=' + count);
--- orig.js	2026-10-22 14:31:40.062236697 -0400
+++ fixed.js	2026-10-22 14:31:44.456263225 -0400
@@ -1,3 +1,3 @@
 let count = 0;
 const inc = () => ++count;
-console.log(('a' + inc()) + ('b' + inc()), 'count=' + count);
+console.log(`a${  inc()  }b${  inc()}`, `count=${  count}`);
a1b2 count=2
r2-p007 · Sequence Expression with Ignored Result
1let used = 0;
20, used = ('x' + 'y'), console.log(used); // Sequence: assigns then logs
3// Rule does not fire on 'x'+'y' here because it's inside sequence? Actually it should.
4// But we want near-miss: make it so rule doesn't fire due to context.
5// Let's try: if the '+' is in a sequence but the whole thing is ignored...
60, ('ignored' + 'text'); // This should trigger rule but result ignored
7console.log('side effect only if assignment');
xy
side effect only if assignment
r2-p008 · Getter Side Effects and Evaluation Order
1const obj = {
2 get a() { return console.log('get a'), 1; },
3 get b() { return console.log('get b'), 2; }
4};
5console.log('Result: ' + obj.a + obj.b);
--- orig.js	2026-10-22 14:31:40.092236875 -0400
+++ fixed.js	2026-10-22 14:31:43.862259565 -0400
@@ -2,4 +2,4 @@
   get a() { return console.log('get a'), 1; },
   get b() { return console.log('get b'), 2; }
 };
-console.log('Result: ' + obj.a + obj.b);
+console.log(`Result: ${  obj.a  }${obj.b}`);
get a
get b
Result: 12