eslint no-extra-boolean-cast
labelled safe
Analysis Nemotron 3 Super
The rule flags unnecessary Boolean casts (via !! or Boolean()) in contexts where values are automatically coerced to Boolean.
Fix: Removes the redundant Boolean cast (!! or Boolean()) around the expression, preserving the inner expression exactly as written.
6 hazards
- Removing !! from a comma-separated expression like !!(a, b) changes evaluation to yield b instead of !!b, altering truthiness if b is falsy but a is truthy.
- Removing Boolean() from a new Boolean(...) expression like new Boolean(!!foo) changes the result from an object wrapper to a primitive boolean, breaking code that expects an object instance.
- Removing !! from a logical OR operand like !!foo || bar changes short-circuit evaluation: if foo is falsy, the original evaluates bar for its truthiness, but after fix it evaluates bar directly, potentially executing bar when it should have been skipped due to foo's truthiness after double negation.
- Removing !! from a logical AND operand like !!foo && bar changes evaluation: if foo is truthy, original evaluates bar for its truthiness; after fix, it evaluates bar directly, which may execute bar even when foo is falsy (since !!foo would be false) or skip it when foo is truthy but bar has side effects needed only when foo is truthy.
- Removing !! from a conditional operator like !!foo ? bar : baz alters the condition: when foo is falsy, !!foo is false so baz runs; after fix, foo is falsy so baz still runs—but when foo is truthy but !!foo is true (same), wait—actually !!foo and foo have same truthiness, so this is safe? No: wait, !!foo and foo are always same truthiness, so ternary is safe. But the hazard is if the inner expression has side effects: !!foo evaluates foo once, same as foo, so no issue. Wait—rethink: actually !! and Boolean() are idempotent for truthiness, so removing them never changes truthiness. Therefore, the only hazards come from cases where the cast is not just about truthiness but about type or object wrapping or evaluation count. So: new Boolean(...) to Boolean(...) or primitive is a hazard because it changes type from object to primitive. Also, if the inner expression has side effects and is duplicated? No, the fix doesn't duplicate. Another hazard: if the expression is get x() { ... }, !!x calls getter once, same as x, so safe. But if it's something like !!(a=b), removing !! doesn't change that a=b happens once. So maybe the real hazards are only about new Boolean and comma operator? Let me check the docs: the rule does not flag !! in places like const foo = !!bar; when enforceForInnerExpressions is false, but when true, it does flag in logical operands. So for firing_shapes like if (!!foo || bar), the fix would remove !! from !!foo, making it if (foo || bar). Now, is !!foo || bar equivalent to foo || bar? Yes, because !!foo and foo have identical truthiness. So why would it be a hazard? Because of short-circuiting: if foo is an expression with side effects, !!foo evaluates foo once, and if foo is truthy (!!foo true), then bar is not evaluated. In foo || bar, if foo is truthy, bar is not evaluated. Same behavior. If foo is falsy, !!foo is false, so bar is evaluated; in foo || bar, if foo is falsy, bar is evaluated. Same. So actually, removing !! from a logical operand does not change behavior because !! preserves truthiness and does not alter short-circuiting—it just adds an extra operation that doesn't change the boolean outcome. Therefore, the fix is safe for logical operands too? But wait, the rule's documentation says with enforceForInnerExpressions: true, it flags !!foo || bar as incorrect. And the fix would be to remove !!. And according to truthiness equivalence, it should be safe. However, consider: what if foo is undefined? !!undefined is false, same as undefined in ||. What if foo is 0? !!0 is false, same as 0. So yes, truthiness preserved. Then why would there be a hazard? Maybe there isn't for logical operands? But the user asked for hazards specific to this rewrite. Perhaps the only real hazard is when the cast is part of a new Boolean() expression, because that creates an object vs primitive. Or when the cast is used in a way that the resulting value is used non-booleanly later? But the rule only removes the cast when it's unnecessary for boolean coercion, meaning the surrounding context already coerces to boolean. So if we remove the cast, the value is still used in a boolean context, so truthiness is what matters. Therefore, the fix should always preserve behavior in terms of truthiness. Except: new Boolean(...) produces an object, which is truthy, but if you remove the outer Boolean() from new Boolean(!!foo), you get !!foo which is primitive boolean. But wait: the incorrect example is const foo2 = Boolean(!!bar); and the fix would be const foo2 = !!bar; — both are primitive boolean, so safe. Another incorrect: const foo3 = new Boolean(!!bar); fix would be const foo3 = new Boolean(bar); ??? No, wait: the rule flags new Boolean(!!bar) as incorrect because !! is unnecessary inside Boolean or new Boolean? Actually, looking at incorrect examples: const foo2 = Boolean(!!bar); and const foo3 = new Boolean(!!bar); are flagged. The fix for Boolean(!!bar) would be Boolean(bar). For new Boolean(!!bar), the fix would be new Boolean(bar). So in that case, we are removing the inner !!, not the outer cast. So the fix is about removing redundant inner casts, not the outer Boolean or new Boolean. I think I misread. Let me re-examine: the rule flags unnecessary boolean casts. Examples of incorrect: const foo2 = Boolean(!!bar); — here, the outer Boolean is necessary? No, wait: the rule says
- enforceForInnerExpressions": true
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | silent prediction off | Var Hoisting Redefinition Inside Loop | var hoisting and re-declaration inside loops or after use |
| r1-p002 | invalid | Strict Mode Throws on Duplicate Param Name | strict vs sloppy mode semantics |
| r1-p003 | fix ok | __proto__ Key Mutation Affects Prototype Chain | __proto__ keys, prototype access, Object.assign vs spread |
| r1-p004 | fix ok | Getter Side Effect in !! Expression | getters, setters and Object.defineProperty side effects |
| r1-p005 | silent prediction off | Arguments Object Captures !! Expression | arguments object, function.length, default parameters, rest parameters |
| r1-p006 | fix ok | Name Collision After Block Scope Move | name collisions across scopes after a move (shadowing) |
Sources
r1-p001 · Var Hoisting Redefinition Inside Loop
1function f() {
2 var x = false;
3 for (var i = 0; i < 2; i++) {
4 var x = !!(i % 2); // redeclaration + !! triggers rule
5 console.log(x);
6 }
7}
8f();
false true
r1-p002 · Strict Mode Throws on Duplicate Param Name (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-22T18-04-3)
1"use strict";
2function g(a, a) { // duplicate param: SyntaxError in strict
3 try {
4 return !!a; // triggers rule
5 } catch (e) {
6 console.log(e.name + ': ' + e.message);
7 }
8}
9g(1, 2);
r1-p003 · __proto__ Key Mutation Affects Prototype Chain
1let obj = { __proto__: { value: 1 } };
2if (!!obj.__proto__) { // triggers rule: !! on object
3 console.log(obj.value); // reads from prototype
4}
5obj.__proto__ = null;
6console.log(!obj.__proto__); // now falsy
--- orig.js 2026-10-22 14:04:51.037021621 -0400 +++ fixed.js 2026-10-22 14:04:55.233059042 -0400 @@ -1,5 +1,5 @@ let obj = { __proto__: { value: 1 } }; -if (!!obj.__proto__) { // triggers rule: !! on object +if (obj.__proto__) { // triggers rule: !! on object console.log(obj.value); // reads from prototype } obj.__proto__ = null;
1 true
r1-p004 · Getter Side Effect in !! Expression
1let obj = {};
2Object.defineProperty(obj, 'prop', {
3 get() {
4 console.log('getter called');
5 return 0;
6 }
7});
8if (!!obj.prop) { // triggers rule, calls getter once
9 console.log('truthy');
10} else {
11 console.log('falsy');
12}
--- orig.js 2026-10-22 14:04:51.038021629 -0400 +++ fixed.js 2026-10-22 14:04:55.234059052 -0400 @@ -5,7 +5,7 @@ return 0; } }); -if (!!obj.prop) { // triggers rule, calls getter once +if (obj.prop) { // triggers rule, calls getter once console.log('truthy'); } else { console.log('falsy');
getter called falsy
r1-p005 · Arguments Object Captures !! Expression
1function h() {
2 console.log(arguments[0]); // captures !!x before change
3 return !!x; // triggers rule
4}
5let x = 0;
6h(x);
7x = 1;
8console.log(h(x));
0 1 true
r1-p006 · Name Collision After Block Scope Move
1{
2 let flag = 0;
3 if (!!flag) { // triggers rule
4 let flag = 1; // shadowing
5 console.log(flag);
6 }
7 console.log(flag);
8}
--- orig.js 2026-10-22 14:04:51.040021646 -0400 +++ fixed.js 2026-10-22 14:04:55.264059346 -0400 @@ -1,6 +1,6 @@ { let flag = 0; - if (!!flag) { // triggers rule + if (flag) { // triggers rule let flag = 1; // shadowing console.log(flag); }
0