fixhunt

eslint logical-assignment-operators

labelled safe
Runs / 2026-10-22T18-13-31-048Z-vkt6-eslint-logical-assignment-operatorseslint 9.39.52026-10-22 18:13 UTC8 programs × 2 round(s)16 programs, 10 fired, 6 fixed, 0 findings, 3 rejectedmodel cost $0.0032

Analysis Nemotron 3 Super

Flags assignments where the LHS matches the left operand of a logical OR, AND, or nullish coalescing operation.

Fix: Rewrites 'LHS = LHS op RHS' into 'LHS op= RHS', preserving whitespace and semicolons.

8 hazards
  • Changing 'x = x || y;' to 'x ||= y;' alters evaluation when x is a getter with side effects, as the logical assignment evaluates LHS only once.
  • Rewriting 'y || (y = z);' to 'y ||= z;' moves the assignment inside the logical expression, changing when y is assigned if y is falsy but z throws.
  • If LHS is a complex expression like 'obj.prop', the rewrite may differ in evaluation count if obj.prop has a getter with side effects.
  • When LHS is an array element access like 'arr[i]', the rewrite changes behavior if i has side effects (e.g., function call) because logical assignment evaluates LHS once vs twice.
  • In strict mode, if LHS refers to an undeclared variable, both forms throw ReferenceError, but the timing differs slightly due to evaluation count.
  • Comments between LHS and '=' or between '=' and RHS may be lost or repositioned during the rewrite, depending on formatter behavior.
  • If RHS contains a yield or await, moving it inside the logical assignment shortcircuit could alter async/generator suspension points.
  • When LHS is a pattern like '[a]' or '{b}', the rewrite is invalid syntax and will cause a parse error, though the rule should not flag such cases.

Programs

idresulttitlehazard
r1-p001silent prediction offnull/undefined distinction, optional chaining short-circuit, nullish vs falsyLogical assignment with nullish coalescing can mask undefined vs null differences; rewriting may alter evaluation order or short-circuit behavior.
r1-p002invalidASI and semicolon-less code, leading ( or [ on the next lineAutomatic semicolon insertion can cause misparsing when logical assignment is followed by a line starting with ( or [, and fixing may disrupt ASI boundaries.
r1-p003invalidIf RHS contains a yield or await, moving it inside the logical assignment shortcircuit could alter async/generator suspeRewriting `x = x || (await y)` to `x ||= await y` changes when await is executed—only if x is falsy—affecting suspension timing in async functions.
r1-p004fix ok prediction offfunction declarations inside blocks, switch cases, labeled statementsFunction declarations in blocks are scoped; rewriting logical assignment may interfere with hoisting or TDZ if LHS is a function name in a conditional block.
r1-p005fired, no fixChanging 'x = x || y;' to 'x ||= y;' alters evaluation when x is a getter with side effects, as the logical assignment eGetters with side effects (e.g., logging) are evaluated once in `x ||= y` but twice in `x = x || y`; fixing changes observable behavior.
r1-p006silent prediction offWhen LHS is an array element access like 'arr[i]', the rewrite changes behavior if i has side effects (e.g., function caIf `i` is a function call with side effects, `arr[i] = arr[i] || y` calls it twice, but `arr[i] ||= y` calls it once—fixing alters call count.
r1-p007fix okregular expressions: escapes, unicode flag, lastIndex with the g flagLogical assignment with regexps may misbehave due to shared lastIndex state; fixing may alter how many times test/exec is called.
r1-p008fired, no fixComments between LHS and '=' or between '=' and RHS may be lost or repositioned during the rewrite, depending on formattComments attached to the assignment operator may be stripped or moved during autofix, potentially altering readability or tooling expectations.
r2-p001fired, no fixComments around assignmentComments between LHS and '=' or between '=' and RHS may be lost or repositioned during the rewrite, depending on formatter behavior.
r2-p002fired, no fix__proto__ and prototype mutation__proto__ keys, prototype access, Object.assign vs spread
r2-p003fix okFunction declaration in blockfunction declarations inside blocks, switch cases, labeled statements
r2-p004fix okVar hoisting and redeclarationvar hoisting and re-declaration inside loops or after use
r2-p005fix ok prediction offShort-circuit with throw in RHSRewriting 'y || (y = z);' to 'y ||= z;' moves the assignment inside the logical expression, changing when y is assigned if y is falsy but z throws.
r2-p006silent prediction offTemporal dead zone interactiontemporal dead zone after code is hoisted or reordered
r2-p007invalidPattern LHS invalid syntaxWhen LHS is a pattern like '[a]' or '{b}', the rewrite is invalid syntax and will cause a parse error, though the rule should not flag such cases.
r2-p008fix okThis binding in extracted methodsthis binding: arrow vs function, methods extracted from objects, call/apply/bind

Sources

r1-p001 · null/undefined distinction, optional chaining short-circuit, nullish vs falsy
1let x = undefined;
2let y = null;
3console.log(x ??= y);
4console.log(x);
null
null
r1-p002 · ASI and semicolon-less code, leading ( or [ on the next line (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-22T18-13-3)
1let a
2= 5
3a = a || [1, 2].pop()
4console.log(a)
5[].push(0)
r1-p003 · If RHS contains a yield or await, moving it inside the logical assignment shortcircuit could alter async/generator suspe (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-22T18-13-3)
1async function f() {
2 let x = false;
3 console.log(await x ||= Promise.resolve(1));
4 console.log(x);
5}
6f().then(() => console.log('done'));
r1-p004 · function declarations inside blocks, switch cases, labeled statements
1if (true) {
2 function foo() { return 1; }
3 let x = typeof foo;
4 foo = foo || function() { return 2; };
5 console.log(typeof foo, x);
6}
--- orig.js	2026-10-22 14:13:40.896672850 -0400
+++ fixed.js	2026-10-22 14:13:43.749723024 -0400
@@ -1,6 +1,6 @@
 if (true) {
   function foo() { return 1; }
   let x = typeof foo;
-  foo = foo || function() { return 2; };
+  foo ||= function() { return 2; };
   console.log(typeof foo, x);
 }
function function
r1-p005 · Changing 'x = x || y;' to 'x ||= y;' alters evaluation when x is a getter with side effects, as the logical assignment e
1const obj = {
2 get x() {
3 console.log('getter');
4 return this._x;
5 },
6 _x: 0
7};
8obj.x = obj.x || 5;
9console.log(obj._x);
getter
0
r1-p006 · When LHS is an array element access like 'arr[i]', the rewrite changes behavior if i has side effects (e.g., function ca
1let i = 0;
2function next() { return i++; }
3const arr = [0, 0];
4arr[next()] = arr[next()] || 42;
5console.log('i:', i, 'arr:', arr);
i: 2 arr: [ 42, 0 ]
r1-p007 · regular expressions: escapes, unicode flag, lastIndex with the g flag
1let regex = /a/g;
2let str = 'ab';
3console.log(regex.test(str));
4regex = regex || /b/g;
5console.log(regex.test(str));
6console.log(regex.lastIndex);
--- orig.js	2026-10-22 14:13:40.939673598 -0400
+++ fixed.js	2026-10-22 14:13:45.503754323 -0400
@@ -1,6 +1,6 @@
 let regex = /a/g;
 let str = 'ab';
 console.log(regex.test(str));
-regex = regex || /b/g;
+regex ||= /b/g;
 console.log(regex.test(str));
 console.log(regex.lastIndex);
true
false
0
r1-p008 · Comments between LHS and '=' or between '=' and RHS may be lost or repositioned during the rewrite, depending on formatt
1let x = 0; // comment before
2x /* inline */ = x /* middle */ || 1 /* after */;
3console.log(x);
1
r2-p001 · Comments around assignment
1// comment before
2let x = 0;
3// x = x /* mid */ || 1; // after
4x = x /* LHS comment */ || /* RHS comment */ 1;
5console.log(x);
1
r2-p002 · __proto__ and prototype mutation
1let obj = {};
2obj.__proto__ = obj.__proto__ || { toString() { return 'proto'; } };
3console.log(obj.toString());
4obj.__proto__ = obj.__proto__ && { valueOf() { return 42; } };
5console.log(obj.valueOf());
[object Object]
42
r2-p003 · Function declaration in block
1{ let f = 0; if (true) { function g() { return 1; } f = f || g(); } console.log(f); }
2{ let h = 0; switch (1) { case 1: function k() { return 2; } h = h && k(); break; } console.log(h); }
--- orig.js	2026-10-22 14:13:52.337879502 -0400
+++ fixed.js	2026-10-22 14:13:55.200933453 -0400
@@ -1,2 +1,2 @@
-{ let f = 0; if (true) { function g() { return 1; } f = f || g(); } console.log(f); }
-{ let h = 0; switch (1) { case 1: function k() { return 2; } h = h && k(); break; } console.log(h); }
+{ let f = 0; if (true) { function g() { return 1; } f ||= g(); } console.log(f); }
+{ let h = 0; switch (1) { case 1: function k() { return 2; } h &&= k(); break; } console.log(h); }
1
0
r2-p004 · Var hoisting and redeclaration
1for (var i = 0; i < 2; i++) {
2 var x = 0;
3 x = x || i;
4 console.log('loop:', x);
5}
6console.log('after:', x);
7var x = 99;
8console.log('redeclared:', x);
--- orig.js	2026-10-22 14:13:52.338879521 -0400
+++ fixed.js	2026-10-22 14:13:55.215933738 -0400
@@ -1,6 +1,6 @@
 for (var i = 0; i < 2; i++) {
   var x = 0;
-  x = x || i;
+  x ||= i;
   console.log('loop:', x);
 }
 console.log('after:', x);
loop: 0
loop: 1
after: 1
redeclared: 99
r2-p005 · Short-circuit with throw in RHS
1let y = 0;
2try {
3 y = y || (() => { throw new Error('side effect'); })();
4 console.log('assigned:', y);
5} catch (e) {
6 console.log('caught:', e.name, e.message);
7}
8y = 0;
9try {
10 y ||= (() => { throw new Error('side effect'); })();
11 console.log('assigned:', y);
12} catch (e) {
13 console.log('caught:', e.name, e.message);
14}
--- orig.js	2026-10-22 14:13:52.338879521 -0400
+++ fixed.js	2026-10-22 14:13:56.475957768 -0400
@@ -1,6 +1,6 @@
 let y = 0;
 try {
-  y = y || (() => { throw new Error('side effect'); })();
+  y ||= (() => { throw new Error('side effect'); })();
   console.log('assigned:', y);
 } catch (e) {
   console.log('caught:', e.name, e.message);
caught: Error side effect
caught: Error side effect
r2-p006 · Temporal dead zone interaction
1if (true) {
2 console.log(typeof tmp);
3 let tmp = tmp || 'initialized';
4 console.log(tmp);
5}
r2-p007 · Pattern LHS invalid syntax (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-22T18-13-3)
1let arr = [1];
2let obj = { a: 2 };
3try {
4 [arr[0]] = [arr[0]] || [3];
5 console.log(arr);
6} catch (e) {
7 console.log('catch pattern:', e.name);
8}
9try {
10 { obj.a } = { obj.a } || { a: 4 };
11 console.log(obj);
12} catch (e) {
13 console.log('catch pattern:', e.name);
14}
r2-p008 · This binding in extracted methods
1const obj = { value: 1, getValue() { return this.value; } };
2let f = obj.getValue;
3let result = f || (f = obj.getValue.bind(obj));
4console.log('bound result:', result());
5f = obj.getValue;
6f && (f = obj.getValue.bind(obj));
7console.log('bound result2:', f());
--- orig.js	2026-10-22 14:13:55.040930407 -0400
+++ fixed.js	2026-10-22 14:13:58.564997973 -0400
@@ -1,7 +1,7 @@
 const obj = { value: 1, getValue() { return this.value; } };
 let f = obj.getValue;
-let result = f || (f = obj.getValue.bind(obj));
+let result = (f ||= obj.getValue.bind(obj));
 console.log('bound result:', result());
 f = obj.getValue;
-f && (f = obj.getValue.bind(obj));
+f &&= obj.getValue.bind(obj);
 console.log('bound result2:', f());
bound result: undefined
bound result2: 1