biome style/noUselessElse
labelled safe
Analysis Nemotron 3 Super
Flags redundant else blocks after if statements containing breaking statements (return, break, continue, throw).
Fix: Removes the else block and places its statements directly after the if block, preserving their original order and indentation relative to the enclosing scope.
10 hazards
- Moving statements from an else block after a continue in a loop changes which iteration they execute in, altering loop behavior.
- If the else block contains a labeled break, moving it outside may break to a different label or become invalid.
- Statements with side effects in the else block may now execute regardless of the if condition, changing program state.
- If the else block declares a variable with let/const, moving it may alter its scope and cause temporal dead zone errors.
- Comments attached to the else block may be lost or misplaced during the rewrite, affecting readability.
- If the if block contains a return with a value, moving else statements after it creates unreachable code, potentially hiding logic errors.
- In a switch case, moving else statements after a return may fall through to the next case if break is missing.
- If the else block contains a throw, moving it after the if block may change the stack trace or exception handling context.
- When the if block is inside a try block, moving else statements after it may place them outside the try's scope, altering finally execution.
- If the else block contains a debugger statement, its execution context may change, affecting debugging behavior.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fix ok | Hazard 1: Side effects in else block executed unconditionally | Statements with side effects in the else block may now execute regardless of the if condition, changing program state. |
| r1-p002 | fix ok | Hazard 2: Temporal dead zone from moved let/const | If the else block declares a variable with let/const, moving it may alter its scope and cause temporal dead zone errors. |
| r1-p003 | fix ok | Hazard 3: Changed stack trace from moved throw | If the else block contains a throw, moving it after the if block may change the stack trace or exception handling context. |
| r1-p004 | invalid | Hazard 4: Regex lastIndex interference from unconditional execution | regular expressions: escapes, unicode flag, lastIndex with the g flag |
| r1-p005 | fix ok | Hazard 5: Debugger statement execution context changed | If the else block contains a debugger statement, its execution context may change, affecting debugging behavior. |
| r1-p006 | invalid | Hazard 6: Else block moved outside try, finally not run | When the if block is inside a try block, moving else statements after it may place them outside the try's scope, altering finally execution. |
| r1-p007 | fix ok | Hazard 7: Labeled break becomes invalid after move | If the else block contains a labeled break, moving it outside may break to a different label or become invalid. |
| r1-p008 | fix ok | Hazard 8: Var hoisting and re-declaration in loop | var hoisting and re-declaration inside loops or after use |
| r2-p001 | silent prediction off | ASI and semicolon-less code causing expression misinterpretation | ASI and semicolon-less code, leading ( or [ on the next line |
| r2-p002 | silent prediction off | Side effects in else block now execute unconditionally due to missing guard | Statements with side effects in the else block may now execute regardless of the if condition, changing program state. |
| r2-p003 | silent prediction off | Strict mode violation from misplaced this assignment | strict vs sloppy mode semantics |
| r2-p004 | silent prediction off | Block-scoped function declaration hoisted incorrectly across if/else | function declarations inside blocks, switch cases, labeled statements |
| r2-p005 | fix ok | Var re-declaration in loop breaks due to moved else block creating duplicate declaration | var hoisting and re-declaration inside loops or after use |
| r2-p006 | invalid | Finally block skipped because else moved outside try after return in if | When the if block is inside a try block, moving else statements after it may place them outside the try's scope, altering finally execution. |
| r2-p007 | silent prediction off | Comments on else block lost during rewrite reducing code clarity | Comments attached to the else block may be lost or misplaced during the rewrite, affecting readability. |
| r2-p008 | silent prediction off | This binding broken when method extracted from object and else block moved | this binding: arrow vs function, methods extracted from objects, call/apply/bind |
Sources
r1-p001 · Hazard 1: Side effects in else block executed unconditionally
1let x = 0;
2function f() {
3 if (false) { return; }
4 else { x += 1; }
5}
6f();
7console.log(x);
--- orig.js 2026-10-17 15:38:13.067702588 -0400 +++ fixed.js 2026-10-17 15:38:13.341699522 -0400 @@ -1,7 +1,7 @@ let x = 0; function f() { if (false) { return; } - else { x += 1; } + x += 1; } f(); console.log(x);
1
r1-p002 · Hazard 2: Temporal dead zone from moved let/const
1function f() {
2 if (true) { return; }
3 else { let x = 1; console.log(x); }
4}
5try { f(); } catch (e) { console.log(e.name + ': ' + e.message); }
--- orig.js 2026-10-17 15:38:13.506697675 -0400 +++ fixed.js 2026-10-17 15:38:13.743695023 -0400 @@ -1,5 +1,5 @@ function f() { if (true) { return; } - else { let x = 1; console.log(x); } + let x = 1; console.log(x); } try { f(); } catch (e) { console.log(e.name + ': ' + e.message); }
r1-p003 · Hazard 3: Changed stack trace from moved throw
1function inner() {
2 if (false) { return; }
3 else { throw new Error('test'); }
4}
5function outer() {
6 try { inner(); } catch (e) { console.log(e.stack.split('\n')[0]); }
7}
8outer();
--- orig.js 2026-10-17 15:38:13.894693333 -0400 +++ fixed.js 2026-10-17 15:38:14.208689807 -0400 @@ -1,6 +1,6 @@ function inner() { if (false) { return; } - else { throw new Error('test'); } + throw new Error('test'); } function outer() { try { inner(); } catch (e) { console.log(e.stack.split('\n')[0]); }
Error: test
r1-p004 · Hazard 4: Regex lastIndex interference from unconditional execution (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-17T19-38-0)
1let regex = /a/g;
2let str = 'aa';
3if (false) { return; }
4else { regex.exec(str); }
5console.log(regex.lastIndex);
r1-p005 · Hazard 5: Debugger statement execution context changed
1function f() {
2 if (true) { return; }
3 else { debugger; console.log('reached'); }
4}
5f();
6console.log('done');
--- orig.js 2026-10-17 15:38:14.419687432 -0400 +++ fixed.js 2026-10-17 15:38:14.688684406 -0400 @@ -1,6 +1,6 @@ function f() { if (true) { return; } - else { debugger; console.log('reached'); } + debugger; console.log('reached'); } f(); console.log('done');
done
r1-p006 · Hazard 6: Else block moved outside try, finally not run (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-17T19-38-0)
1let finallyRan = false;
2function f() {
3 try {
4 if (false) { return; }
5 } finally {
6 finallyRan = true;
7 }
8 else { console.log('else'); }
9}
10f();
11console.log(finallyRan);
r1-p007 · Hazard 7: Labeled break becomes invalid after move
1outer: {
2 if (false) { break outer; }
3 else { break outer; }
4}
5console.log('reached');
--- orig.js 2026-10-17 15:38:14.902681998 -0400 +++ fixed.js 2026-10-17 15:38:15.169678984 -0400 @@ -1,5 +1,5 @@ outer: { if (false) { break outer; } - else { break outer; } + break outer; } console.log('reached');
reached
r1-p008 · Hazard 8: Var hoisting and re-declaration in loop
1for (let i = 0; i < 2; i++) {
2 if (false) { continue; }
3 else { var x = i; }
4}
5console.log(x);
--- orig.js 2026-10-17 15:38:15.329677175 -0400 +++ fixed.js 2026-10-17 15:38:15.602674087 -0400 @@ -1,5 +1,5 @@ for (let i = 0; i < 2; i++) { if (false) { continue; } - else { var x = i; } + var x = i; } console.log(x);
1
r2-p001 · ASI and semicolon-less code causing expression misinterpretation
1function f() {
2 if (true)
3 return
4 []
5 console.log('unreachable')
6}
7f()
r2-p002 · Side effects in else block now execute unconditionally due to missing guard
1let x = 0;
2function f(flag) {
3 if (flag) return;
4 x += 1;
5}
6f(true);
7f(false);
8f(false);
9console.log(x);
2
r2-p003 · Strict mode violation from misplaced this assignment
1"use strict";
2let obj = { value: 42 };
3function getValue() {
4 if (this === obj) return this.value;
5 this.value = 99; // should error in strict
6 return -1;
7}
8try {
9 console.log(getValue.call(obj));
10 console.log(getValue());
11} catch (e) {
12 console.log(e.name + ': ' + e.message);
13}
42 TypeError: Cannot set properties of undefined (setting 'value')
r2-p004 · Block-scoped function declaration hoisted incorrectly across if/else
1function test() {
2 if (false) {
3 function f() { return 1; }
4 } else {
5 function f() { return 2; }
6 return f();
7 }
8}
9console.log(test());
2
r2-p005 · Var re-declaration in loop breaks due to moved else block creating duplicate declaration
1for (let i = 0; i < 2; i++) {
2 if (i === 0) continue;
3 var x = 10;
4 console.log(x);
5}
6// After fix: var x hoisted and re-declared each loop -> still works but let's make it fail
7// Let's force error by using same var in else that gets moved
8let result = [];
9for (let i = 0; i < 2; i++) {
10 if (i === 0) continue;
11 else {
12 var x = i + 1;
13 result.push(x);
14 }
15}
16console.log(result.join(','));
--- orig.js 2026-10-17 15:38:22.431595431 -0400 +++ fixed.js 2026-10-17 15:38:22.696592325 -0400 @@ -8,9 +8,8 @@ let result = []; for (let i = 0; i < 2; i++) { if (i === 0) continue; - else { + var x = i + 1; result.push(x); - } } console.log(result.join(','));
10 2
r2-p006 · Finally block skipped because else moved outside try after return in if (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-17T19-38-0)
1let log = '';
2try {
3 if (true) {
4 return;
5 } else {
6 log += 'else'; // moved after try, never runs
7 }
8} finally {
9 log += 'finally';
10}
11console.log(log || 'empty');
r2-p007 · Comments on else block lost during rewrite reducing code clarity
1function f(flag) {
2 if (flag) return;
3 // This comment explains why we decrement
4 counter--;
5}
6let counter = 5;
7f(true);
8f(false);
9console.log(counter);
4
r2-p008 · This binding broken when method extracted from object and else block moved
1const obj = {
2 value: 42,
3 getValue() {
4 if (this === obj) return this.value;
5 this.value = 99;
6 return -1;
7 }
8};
9const fn = obj.getValue;
10console.log(fn()); // should return -1 (global this)
11console.log(fn.call(obj)); // should return 42 then 99 on second call
12console.log(fn.call(obj));