eslint no-unneeded-ternary
labelled safe
Analysis Nemotron 3 Super
Flags ternary expressions where the condition selects between identical literals/variables or redundant boolean conversions.
Fix: Rewrites ternary to simpler equivalent: replaces `cond ? true : false` with `!!cond` or `cond` (if already boolean), `cond ? false : true` with `!cond`, `var ? var : expr` with `var || expr`, and `cond ? var : var` with `var`.
8 hazards
- Replacing `x ? true : false` with `x` fails if `x` is not already a boolean (e.g., `x=0` yields `false` vs `0`).
- Replacing `x ? false : true` with `!x` fails if `x` is not boolean (e.g., `x=0` yields `true` vs `1`).
- Replacing `x ? x : 1` with `x || 1` changes behavior when `x` is `-0` or `NaN` (both falsy but `x||1` gives `1` while ternary gives `-0`/`NaN`).
- Replacing `x ? y : x` with `x` fails if `x` and `y` are different values (e.g., `x=0,y=1` yields `0` vs `1`).
- The fix may break if the ternary is inside a larger expression where operator precedence changes after rewrite (e.g., `a + (b ? b : 0)` vs `a + (b || 0)` are equivalent, but `a * (b ? b : 0)` vs `a * (b || 0)` differ when `b=0`).
- If the ternary's consequent or alternate has side effects (e.g., function calls), removing them alters behavior (though rule typically doesn't fire there).
- Comments attached to the ternary operator or its branches may be lost during rewrite.
- In strict mode, if `x` is `undefined` and used in `x ? x : 1`, the rewrite `x || 1` is safe, but if `x` is `null`, both behave same; however, if `x` is a non-standard falsy object like `document.all`, behavior may differ in legacy browsers.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | silent | Hazard 1: Ternary with Side Effects | If the ternary's consequent or alternate has side effects (e.g., function calls), removing them alters behavior. |
| r1-p002 | fix ok | Hazard 2: Non-Boolean Condition Rewrite | Replacing `x ? true : false` with `x` fails if `x` is not already a boolean (e.g., `x=0` yields `false` vs `0`). |
| r1-p003 | fix ok | Hazard 3: Arguments Object Interaction | arguments object, function.length, default parameters, rest parameters |
| r1-p004 | fix ok | Hazard 4: Temporal Dead Zone | temporal dead zone after code is hoisted or reordered |
| r1-p005 | fix ok | Hazard 5: Prototype and __proto__ Access | __proto__ keys, prototype access, Object.assign vs spread |
| r1-p006 | fix ok | Hazard 6: This Binding | this binding: arrow vs function, methods extracted from objects, call/apply/bind |
| r1-p007 | silent prediction off | Hazard 7: document.all and Non-Standard Falsy Objects | In strict mode, if `x` is `undefined` and used in `x ? x : 1`, the rewrite `x || 1` is safe, but if `x` is `null`, both behave same; however, if `x` is a non-standard falsy object like `document.all`, |
| r1-p008 | fix ok | Hazard 8: Control Flow and Finally | label/break/continue control flow, finally blocks, early returns in nested blocks |
| r2-p001 | silent prediction off | Hazard 1: -0 and NaN preservation in x||1 | Replacing `x ? x : 1` with `x || 1` changes behavior when `x` is `-0` or `NaN` |
| r2-p002 | silent prediction off | Hazard 2: Regex flags and lastIndex interaction | regular expressions: escapes, unicode flag, lastIndex with the g flag |
| r2-p003 | silent prediction off | Hazard 3: Var hoisting and redeclaration | var hoisting and re-declaration inside loops or after use |
| r2-p004 | silent prediction off | Hazard 4: Precedence change in multiplication | The fix may break if the ternary is inside a larger expression where operator precedence changes after rewrite |
| r2-p005 | silent prediction off | Hazard 5: This binding in extracted method | this binding: arrow vs function, methods extracted from objects, call/apply/bind |
| r2-p006 | silent prediction off | Hazard 6: Null vs undefined in optional chaining | null/undefined distinction, optional chaining short-circuit, nullish vs falsy |
| r2-p007 | silent prediction off | Hazard 7: Sparse array hole preservation | sparse arrays, holes, Array(n) vs [n], array-likes |
| r2-p008 | silent prediction off | Hazard 8: Arguments object interaction | arguments object, function.length, default parameters, rest parameters |
Sources
r1-p001 · Hazard 1: Ternary with Side Effects
1let result;
2try {
3 const fn = () => { console.log('side'); return 42; };
4 // This pattern does NOT trigger no-unneeded-ternary because branches differ
5 result = true ? fn() : 0;
6 console.log('result:', result);
7} catch (e) {
8 console.log(e.constructor.name + ': ' + e.message);
9}
side result: 42
r1-p002 · Hazard 2: Non-Boolean Condition Rewrite
1let x = 0;
2let a = x ? true : false; // triggers rule: x?true:false -> should become x
3console.log('a:', a, '(type:', typeof a + ')');
4// If rewritten as 'let a = x;' by unsafe fix, a would be 0, not false
--- orig.js 2026-10-22 14:20:35.452958311 -0400 +++ fixed.js 2026-10-22 14:20:39.185110177 -0400 @@ -1,4 +1,4 @@ let x = 0; -let a = x ? true : false; // triggers rule: x?true:false -> should become x +let a = !!x; // triggers rule: x?true:false -> should become x console.log('a:', a, '(type:', typeof a + ')'); // If rewritten as 'let a = x;' by unsafe fix, a would be 0, not false
a: false (type: boolean)
r1-p003 · Hazard 3: Arguments Object Interaction
1function test(a, b = 1, ...rest) {
2 let x = arguments[0];
3 let y = x ? true : false; // triggers rule
4 console.log('y:', y, '| args:', [].slice.call(arguments));
5}
6test(0, 2, 3); // x=0 -> y=false; if rewritten as y=x, y=0 (differs)
--- orig.js 2026-10-22 14:20:35.453958352 -0400 +++ fixed.js 2026-10-22 14:20:39.187110258 -0400 @@ -1,6 +1,6 @@ function test(a, b = 1, ...rest) { let x = arguments[0]; - let y = x ? true : false; // triggers rule + let y = !!x; // triggers rule console.log('y:', y, '| args:', [].slice.call(arguments)); } test(0, 2, 3); // x=0 -> y=false; if rewritten as y=x, y=0 (differs)
y: false | args: [ 0, 2, 3 ]
r1-p004 · Hazard 4: Temporal Dead Zone
1(() => {
2 console.log(typeof x); // TDZ: ReferenceError if let x not yet initialized
3 let x = 0;
4 let y = x ? true : false; // triggers rule
5 console.log('y:', y);
6})();
--- orig.js 2026-10-22 14:20:35.454958393 -0400 +++ fixed.js 2026-10-22 14:20:37.554043770 -0400 @@ -1,6 +1,6 @@ (() => { console.log(typeof x); // TDZ: ReferenceError if let x not yet initialized let x = 0; - let y = x ? true : false; // triggers rule + let y = !!x; // triggers rule console.log('y:', y); })();
r1-p005 · Hazard 5: Prototype and __proto__ Access
1const obj = { __proto__: { toString: () => 'proto' }, toString: () => 'obj' };
2let x = obj.__proto__;
3let str = x ? x.toString() : 'fallback'; // x is truthy object -> calls obj.__proto__.toString()
4console.log('str:', str);
5// Rule does NOT fire here (x?expr:fallback not x?true:false form)
6// But if we had: let y = !!x; that would be different hazard
7// Adjusting to hit rule: need x?true:false
8let flag = x ? true : false; // triggers rule: x is truthy -> flag=true
9console.log('flag:', flag);
--- orig.js 2026-10-22 14:20:35.454958393 -0400 +++ fixed.js 2026-10-22 14:20:37.566044258 -0400 @@ -5,5 +5,5 @@ // Rule does NOT fire here (x?expr:fallback not x?true:false form) // But if we had: let y = !!x; that would be different hazard // Adjusting to hit rule: need x?true:false -let flag = x ? true : false; // triggers rule: x is truthy -> flag=true +let flag = !!x; // triggers rule: x is truthy -> flag=true console.log('flag:', flag);
str: proto flag: true
r1-p006 · Hazard 6: This Binding
1const obj = {
2 value: 42,
3 check: function() { return this.value; }
4};
5const func = obj.check;
6let x = func(); // x = undefined (in strict mode? but we're not strict)
7// Actually in non-strict, func() -> this=global, value undefined -> x=undefined
8// Let's make it return 0 to falsify
9obj.value = 0;
10x = obj.check(); // returns 0
11let y = x ? true : false; // triggers rule
12console.log('y:', y); // should be false
13// If rewritten as y = x, y=0 (still falsy, but 0 !== false)
14// We'll see difference in type
--- orig.js 2026-10-22 14:20:35.455958433 -0400 +++ fixed.js 2026-10-22 14:20:39.805135437 -0400 @@ -8,7 +8,7 @@ // Let's make it return 0 to falsify obj.value = 0; x = obj.check(); // returns 0 -let y = x ? true : false; // triggers rule +let y = !!x; // triggers rule console.log('y:', y); // should be false // If rewritten as y = x, y=0 (still falsy, but 0 !== false) // We'll see difference in type
y: false
r1-p007 · Hazard 7: document.all and Non-Standard Falsy Objects
1(() => {
2 'use strict';
3 let x = undefined;
4 let a = x ? x : 1; // triggers rule: x?x:1 -> should become x||1
5 console.log('a:', a); // 1
6 // Now test with null
7 x = null;
8 a = x ? x : 1;
9 console.log('a (null):', a); // 1
10 // Both x||1 and x?x:1 give 1 for null/undefined
11 // To see difference, need a falsy object that is not document.all (unavailable)
12 // Instead, use 0: but 0?0:1 -> 1, 0||1 -> 1 same
13 // Use '' -> same
14 // Actually no difference for primitives. Hazard is theoretical for host objects.
15 // We show that rewrite is safe for undefined/null
16 x = 0;
17 a = x ? x : 1;
18 console.log('a (0):', a); // 1
19})();
a: 1 a (null): 1 a (0): 1
r1-p008 · Hazard 8: Control Flow and Finally
1let log = '';
2outer: for (let i = 0; i < 2; i++) {
3 try {
4 if (i === 0) continue outer;
5 let x = i;
6 let y = x ? true : false; // triggers rule only when i=1 (x=1 -> y=true)
7 log += 'y:' + y + '|';
8 } finally {
9 log += 'f';
10 }
11}
12console.log(log);
13// Expected: for i=0: continue -> skip body, finally runs -> log+='f'
14// for i=1: y=true -> log+='y:true|', finally -> +'f' -> total 'fy:true|f'
15// If rule rewrites y = x ? true : false to y = x, then for i=1: y=1 -> log+='y:1|'
16// Output would be 'fy:1|f' -> different}
--- orig.js 2026-10-22 14:20:39.755133400 -0400 +++ fixed.js 2026-10-22 14:20:42.970264513 -0400 @@ -3,7 +3,7 @@ try { if (i === 0) continue outer; let x = i; - let y = x ? true : false; // triggers rule only when i=1 (x=1 -> y=true) + let y = !!x; // triggers rule only when i=1 (x=1 -> y=true) log += 'y:' + y + '|'; } finally { log += 'f';
fy:true|f
r2-p001 · Hazard 1: -0 and NaN preservation in x||1
1function test() {
2 const vals = [-0, NaN];
3 for (const x of vals) {
4 const orig = x ? x : 1;
5 const fixed = x || 1;
6 console.log(`x=${x}: orig=${orig}, fixed=${fixed}`);
7 if (Object.is(orig, fixed)) continue;
8 throw new Error(`Mismatch: ${orig} !== ${fixed}`);
9 }
10}
11test();
x=0: orig=1, fixed=1 x=NaN: orig=1, fixed=1
r2-p002 · Hazard 2: Regex flags and lastIndex interaction
1let pattern = /a/g;
2let str = 'aa';
3let match1 = pattern.test(str) ? pattern : /b/g;
4console.log('match1 lastIndex:', match1.lastIndex);
5pattern.lastIndex = 0;
6let match2 = pattern.test(str) ? pattern : /b/g;
7console.log('match2 lastIndex:', match2.lastIndex);
8if (match1.lastIndex !== match2.lastIndex) {
9 throw new Error('lastIndex divergence');
10}
match1 lastIndex: 1 match2 lastIndex: 1
r2-p003 · Hazard 3: Var hoisting and redeclaration
1function test() {
2 for (var i = 0; i < 2; i++) {
3 var x = i ? i : 1;
4 if (i === 0) {
5 var x = 99;
6 }
7 console.log(`i=${i}, x=${x}`);
8 }
9 console.log('final x:', x);
10}
11test();
i=0, x=99 i=1, x=1 final x: 1
r2-p004 · Hazard 4: Precedence change in multiplication
1let b = 0;
2let orig = 5 * (b ? b : 0);
3let fixed = 5 * (b || 0);
4console.log(`b=${b}: orig=${orig}, fixed=${fixed}`);
5if (orig !== fixed) {
6 throw new Error(`Precedence failure: ${orig} !== ${fixed}`);
7}
8b = 2;
9orig = 5 * (b ? b : 0);
10fixed = 5 * (b || 0);
11console.log(`b=${b}: orig=${orig}, fixed=${fixed}`);
12if (orig !== fixed) {
13 throw new Error(`Precedence failure: ${orig} !== ${fixed}`);
14}
b=0: orig=0, fixed=0 b=2: orig=10, fixed=10
r2-p005 · Hazard 5: This binding in extracted method
1const obj = {
2 value: 42,
3 getValue: function() { return this.value; }
4};
5const fn = obj.getValue;
6const x = undefined;
7const result = x ? x : fn.call(obj);
8console.log('result:', result);
9if (result !== 42) {
10 throw new Error(`Expected 42, got ${result}`);
11}
result: 42
r2-p006 · Hazard 6: Null vs undefined in optional chaining
1let obj = { prop: { value: 5 } };
2let maybeNull = null;
3let orig = maybeNull ? maybeNull.prop?.value : 99;
4let fixed = maybeNull ?? maybeNull.prop?.value;
5console.log(`maybeNull=${maybeNull}: orig=${orig}, fixed=${fixed}`);
6if (orig !== fixed) {
7 throw new Error(`Nullish coalescing mismatch: ${orig} !== ${fixed}`);
8}
9maybeNull = undefined;
10orig = maybeNull ? maybeNull.prop?.value : 99;
11fixed = maybeNull ?? maybeNull.prop?.value;
12console.log(`maybeNull=${maybeNull}: orig=${orig}, fixed=${fixed}`);
13if (orig !== fixed) {
14 throw new Error(`Nullish coalescing mismatch: ${orig} !== ${fixed}`);
15}
r2-p007 · Hazard 7: Sparse array hole preservation
1const arr = [1, , 3];
2let sumOrig = 0;
3let sumFixed = 0;
4for (let i = 0; i < arr.length; i++) {
5 const val = arr[i] ? arr[i] : 0;
6 sumOrig += val;
7 const val2 = arr[i] || 0;
8 sumFixed += val2;
9}
10console.log(`sumOrig=${sumOrig}, sumFixed=${sumFixed}`);
11if (sumOrig !== sumFixed) {
12 throw new Error(`Sparse array mismatch: ${sumOrig} !== ${sumFixed}`);
13}
sumOrig=4, sumFixed=4
r2-p008 · Hazard 8: Arguments object interaction
1function test(a, b = 1) {
2 console.log('args.length:', arguments.length);
3 console.log('function.length:', test.length);
4 const x = arguments[0];
5 const orig = x ? x : 2;
6 const fixed = x || 2;
7 console.log(`x=${x}: orig=${orig}, fixed=${fixed}`);
8 if (!Object.is(orig, fixed)) {
9 throw new Error(`Arguments mismatch: ${orig} !== ${fixed}`);
10 }
11 return orig;
12}
13test();
14test(0);
15test(-0);
args.length: 0 function.length: 1 x=undefined: orig=2, fixed=2 args.length: 1 function.length: 1 x=0: orig=2, fixed=2 args.length: 1 function.length: 1 x=0: orig=2, fixed=2