biome style/noNegationElse
labelled safe
Analysis Nemotron 3 Super
Disallows using logical NOT (`!`) as the top-level condition in an `if-else` statement when both branches exist.
Fix: Removes the logical NOT operator from the condition and swaps the `if` and `else` blocks so the original `else` block becomes the `if` body and vice versa.
8 hazards
- The condition may contain side effects (e.g., function calls) whose evaluation order or number of executions changes when moved, altering program state.
- If the condition declares variables via `let` or `const` inside its scope (in some implementations), moving it may break due to TDZ or scope mismatch.
- The rewritten condition may be harder to read or debug, especially if the original negation was intentional for clarity (e.g., guarding against null/undefined).
- If the `if` block contains a `break`, `continue`, or `return`, swapping blocks can change control flow in loops or functions.
- Comments attached to the `!` token or condition may be lost or misplaced during the rewrite, affecting code documentation.
- In rare cases involving getters with side effects, evaluating the condition twice (if duplicated during transformation) could cause unintended behavior.
- If the condition is part of a larger expression via operator precedence (though unlikely at top level), removing `!` without proper grouping could misinterpret intent.
- The fix assumes the condition is a simple boolean expression; if it's a complex expression where `!` is not the outermost operator (but the rule only flags when it is), the logic still holds—but extreme nesting could confuse the transform.
Findings
| program | oracle | label | judge | status | summary |
|---|---|---|---|---|---|
| r2-p001 | fix-breaks-syntax | labelled safe | real · high | related | The fix incorrectly rewrites an if-else statement without braces by negating the condition and swapping branches, but produces invalid syntax 'if (foo) result … |
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | invalid | TDZ in let declaration within negated condition | If the condition declares variables via `let` or `const`, moving it out of the condition breaks due to Temporal Dead Zone. |
| r1-p002 | invalid | Name collision after swap due to shadowing | Moving the condition may cause variable capture or shadowing when identifiers clash across scopes. |
| r1-p003 | fix ok | Strict mode semantics change via 'this' in condition | In strict mode, 'this' in condition may be undefined; after fix, evaluation context changes meaningfully. |
| r1-p004 | fix ok | RegExp lastIndex side effect with g flag | Regex with g flag mutates lastIndex; moving condition may alter number of matches due to shifted evaluation timing. |
| r1-p005 | fix ok | Function declaration in block scoped incorrectly after move | Function declarations inside blocks are not hoisted consistently; moving condition may expose or hide them. |
| r1-p006 | invalid | Readability loss: intentional negation for null guard | Rewritten condition may be harder to read, especially when negation guards against null/undefined. |
| r2-p001 | fix-breaks-syntax | ASI Hazard: Leading Parentheses | ASI and semicolon-less code, leading ( or [ on the next line |
| r2-p002 | fix ok | Comment Attachment Loss | Comments attached to the `!` token or condition may be lost or misplaced during the rewrite, affecting code documentation. |
| r2-p003 | silent prediction off | Side Effect Evaluation Order Change | The condition may contain side effects (e.g., function calls) whose evaluation order or number of executions changes when moved, altering program state. |
| r2-p004 | fix ok | Name Collision via Shadowing | name collisions across scopes after a move (shadowing) |
| r2-p005 | fix ok | Complex Condition Misgrouping | The fix assumes the condition is a simple boolean expression; if it's a complex expression where `!` is not the outermost operator (but the rule only flags when it is), the logic still holds—but extre |
| r2-p006 | fix ok | Class Field and Private Name Interaction | class fields, static blocks, private names, super calls |
| r2-p007 | silent prediction off | Operator Precedence Grouping Hazard | If the condition is part of a larger expression via operator precedence (though unlikely at top level), removing `!` without proper grouping could misinterpret intent. |
Sources
r1-p001 · TDZ in let declaration within negated condition (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-17T20-56-4)
1(() => {
2 const log = [];
3 try {
4 // Hazard: 'let' declared in condition scope
5 if (!let x = 1) { log.push('if'); } else { log.push(x); }
6 } catch (e) {
7 log.push(e.name + ': ' + e.message);
8 }
9 console.log(log.join('|'));
10})();
r1-p002 · Name collision after swap due to shadowing (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-17T20-56-4)
1(function(){
2 var x = 99;
3 // Hazard: outer 'x' shadows inner if swapped
4 if (!var x = 5) { /* unreachable */ } else { /* uses outer x */ return x; }
5 // This line won't run due to syntax error above, but we adjust:
6})();
7// Let's fix syntax by moving decl to init-part with comma
8(() => {
9 var x = 99;
10 // Hazard: if condition declares var, which is hoisted
11 if (! (x = 5, false) ) { /* if */ } else { log.push(x); }
12 var log = [];
13 if (! (x = 5, false) ) { log.push('if'); } else { log.push(x); }
14 console.log(log.join('|')); // Should be 5, not 99
15})();
r1-p003 · Strict mode semantics change via 'this' in condition
1"use strict";
2(() => {
3 const log = [];
4 // Hazard: 'this' in condition is undefined in strict mode
5 if (!this) { log.push('a'); } else { log.push(typeof this); }
6 console.log(log.join('|')); // Expected: 'a'
7})();
--- orig.js 2026-10-17 16:57:10.437210904 -0400 +++ fixed.js 2026-10-17 16:57:10.703214605 -0400 @@ -2,6 +2,6 @@ (() => { const log = []; // Hazard: 'this' in condition is undefined in strict mode - if (!this) { log.push('a'); } else { log.push(typeof this); } + if (this) { log.push(typeof this); } else { log.push('a'); } console.log(log.join('|')); // Expected: 'a' })();
a
r1-p004 · RegExp lastIndex side effect with g flag
1(() => {
2 const log = [];
3 const regex = /a/g;
4 regex.lastIndex = 0;
5 // Hazard: condition uses regex.test() which advances lastIndex
6 if (!regex.test('aa')) { log.push('if'); } else { log.push(regex.lastIndex); }
7 // After fix: condition becomes regex.test('aa') -> true, so else runs
8 console.log(log.join('|')); // Before fix: 'if'; after fix: '1'
9})();
--- orig.js 2026-10-17 16:57:10.870216929 -0400 +++ fixed.js 2026-10-17 16:57:11.136220630 -0400 @@ -3,7 +3,7 @@ const regex = /a/g; regex.lastIndex = 0; // Hazard: condition uses regex.test() which advances lastIndex - if (!regex.test('aa')) { log.push('if'); } else { log.push(regex.lastIndex); } + if (regex.test('aa')) { log.push(regex.lastIndex); } else { log.push('if'); } // After fix: condition becomes regex.test('aa') -> true, so else runs console.log(log.join('|')); // Before fix: 'if'; after fix: '1' })();
1
r1-p005 · Function declaration in block scoped incorrectly after move
1(() => {
2 const log = [];
3 // Hazard: if condition contains function decl? Not directly.
4 // Instead: make condition rely on a function whose decl is affected by block scoping
5 if (true) { function f() { return 1; } }
6 // Now use f in condition - but f is not accessible outside block in strict mode
7 "use strict";
8 try {
9 if (! (typeof f === 'function') ) { log.push('if'); } else { log.push(f()); }
10 } catch (e) {
11 log.push(e.name);
12 }
13 console.log(log.join('|')); // In non-strict: f is hoisted -> '1'; in strict: ReferenceError
14})();
--- orig.js 2026-10-17 16:57:11.309223036 -0400 +++ fixed.js 2026-10-17 16:57:11.584226860 -0400 @@ -6,7 +6,7 @@ // Now use f in condition - but f is not accessible outside block in strict mode "use strict"; try { - if (! (typeof f === 'function') ) { log.push('if'); } else { log.push(f()); } + if ( (typeof f === 'function') ) { log.push(f()); } else { log.push('if'); } } catch (e) { log.push(e.name); }
if
r1-p006 · Readability loss: intentional negation for null guard (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-17T20-56-4)
1(() => {
2 const obj = { prop: null };
3 const log = [];
4 // Hazard: !obj.prop reads as 'if missing' - clearer than !!obj.prop
5 if (!obj.prop) { log.push('missing'); } else { log.push(obj.prop); }
6 console.log(log.join('|')); // Should be 'missing'
7 // After fix: condition becomes obj.prop -> null (falsy), so if block runs -> same output?
8 // Wait: after fix, we swap blocks!
9 // Original: if (!obj.prop) { A } else { B }
10 // Fixed: if (obj.prop) { B } else { A }
11 // So now: if (null) -> false -> else runs -> A: 'missing'
12 // Still same output. Need side effect.
13 // Let's make else block have side effect
14})();
15// Redo with mutation
16(() => {
17 let val = 'start';
18 const obj = { prop: null };
19 if (!obj.prop) { val = 'if'; } else { val = 'else'; }
20 console.log(val); // 'if'
21 // After fix: if (obj.prop) { val = 'else'; } else { val = 'if'; } -> still 'if'
22 // Need the condition to be truthy after fix to trigger else->if swap visibly
23 // Use obj.prop = undefined initially, then change it
24})();
25// Final attempt: make condition depend on mutable state
26(() => {
27 let state = 0;
28 const obj = { prop: () => state++ };
29 // First eval: obj.prop() = 0 -> !0 = true -> if block
30 // After fix: condition becomes obj.prop() -> 0 -> falsy -> else block
31 // But we swap blocks too!
32 // Original: if (!obj.prop()) { A } else { B }
33 // Fixed: if (obj.prop()) { B } else { A }
34 // First call: state=0 -> obj.prop()=0 -> !0=true -> A runs
35 // After fix: first call to obj.prop() in condition -> returns 0 -> falsy -> else runs -> A still?
36 // We need to log which block ran
37})();
38// Correct version:
39{"programs":[{"title":"Readability loss: intentional negation for null guard","hazard":"Rewritten condition may be harder to read, especially when negation guards against null/undefined.","expect_trigger":true,"code":"(() => {\n let log = '';\n const obj = { value: 42 };\n // Hazard: negation makes intent clear: 'if not present'\n if (!obj.value) { log = 'zero'; } else { log = obj.value.toString(); }\n console.log(log); // '42'\n // After fix: if (obj.value) { log = obj.value.toString(); } else { log = 'zero'; } -> still '42'\n // No change. Need the negation to be semantically critical.\n // Use case: guarding access where falsy means invalid\n}})]}
r2-p001 · ASI Hazard: Leading Parentheses
1let result = ''
2
3(() => {
4 if (!foo)
5 result += 'A'
6 else
7 result += 'B'
8})()
9
10function foo() { return false }
11console.log(result)
--- orig.js 2026-10-17 16:57:17.310306347 -0400 +++ fixed.js 2026-10-17 16:57:17.583310129 -0400 @@ -1,10 +1,9 @@ let result = '' (() => { - if (!foo) + if (foo) + result += 'B' else result += 'A' - else - result += 'B' })() function foo() { return false }
r2-p002 · Comment Attachment Loss
1let log = []
2if (/* skip if */ !enabled /* feature flag */) {
3 log.push('disabled')
4} else {
5 log.push('enabled')
6}
7enabled = true
8log.push(enabled ? 'post' : 'pre')
9console.log(log.join('|'))
--- orig.js 2026-10-17 16:57:17.641310932 -0400 +++ fixed.js 2026-10-17 16:57:17.921314812 -0400 @@ -1,8 +1,8 @@ let log = [] -if (/* skip if */ !enabled /* feature flag */) { - log.push('disabled') -} else { +if (/* skip if */ enabled /* feature flag */) { log.push('enabled') +} else { + log.push('disabled') } enabled = true log.push(enabled ? 'post' : 'pre')
r2-p003 · Side Effect Evaluation Order Change
1let count = 0
2function inc() { return ++count }
3
4if (!inc() > 0) {
5 console.log('first:', count)
6} else {
7 console.log('second:', count)
8}
9console.log('final:', count)
second: 1 final: 1
r2-p004 · Name Collision via Shadowing
1let scope = 'outer'
2if (!scope) {
3 let scope = 'inner'
4 console.log(scope)
5} else {
6 console.log(scope)
7}
8scope = null
9console.log(scope)
--- orig.js 2026-10-17 16:57:18.324320393 -0400 +++ fixed.js 2026-10-17 16:57:18.594324131 -0400 @@ -1,8 +1,8 @@ let scope = 'outer' -if (!scope) { - let scope = 'inner' +if (scope) { console.log(scope) } else { + let scope = 'inner' console.log(scope) } scope = null
outer null
r2-p005 · Complex Condition Misgrouping
1let val = 0
2if (!(val === 0 || val === 1)) {
3 val = 2
4} else {
5 val = 3
6}
7console.log(val)
--- orig.js 2026-10-17 16:57:18.761326444 -0400 +++ fixed.js 2026-10-17 16:57:19.043330348 -0400 @@ -1,7 +1,7 @@ let val = 0 -if (!(val === 0 || val === 1)) { - val = 2 -} else { +if ((val === 0 || val === 1)) { val = 3 +} else { + val = 2 } console.log(val)
3
r2-p006 · Class Field and Private Name Interaction
1class Test {
2 #flag = false
3 static get value() { return 1 }
4 check() {
5 if (!Test.value) {
6 return this.#flag ? 'a' : 'b'
7 } else {
8 return this.#flag ? 'c' : 'd'
9 }
10 }
11}
12console.log(new Test().check())
--- orig.js 2026-10-17 16:57:19.209332645 -0400 +++ fixed.js 2026-10-17 16:57:19.479336381 -0400 @@ -2,10 +2,10 @@ #flag = false static get value() { return 1 } check() { - if (!Test.value) { - return this.#flag ? 'a' : 'b' - } else { + if (Test.value) { return this.#flag ? 'c' : 'd' + } else { + return this.#flag ? 'a' : 'b' } } }
d
r2-p007 · Operator Precedence Grouping Hazard
1let x = 1, y = 2
2if (!x + y > 2) {
3 console.log('less')
4} else {
5 console.log('greater or equal')
6}
7x = 3
8console.log(!x + y > 2 ? 'static' : 'dynamic')
greater or equal dynamic