biome style/useCollapsedElseIf
labelled safe
Analysis Nemotron 3 Super
Flags when an else block contains exactly one if statement as its sole content.
Fix: Replaces 'else { if(condition) {...} }' with 'else if(condition) {...}', preserving inner block structure and comments.
8 hazards
- Moving a leading comment inside the else block to before the else if keyword changes its association from the inner if to the else clause, altering documentation intent.
- If the else block contains a function declaration, hoisting behavior differs between block-scoped (else { function f(){} }) and else-if scoped (else if(...) function f(){}) declarations, potentially causing reference errors.
- An empty else block ({}) after transformation would be invalid syntax, but the rule prevents this by requiring exactly one if statement.
- When the inner if is labeled (e.g., else { label: if(x){} }), the label is lost during rewrite, breaking goto-like control flow via break/label.
- If the else block starts with a directive prologue like 'use strict';, moving it before else if alters its scope from the else block to the else-if construct, potentially changing strict mode behavior.
- Comments placed between the else brace and the inner if (e.g., else { /* comment */ if(){} }) are relocated to precede the else if, which may misalign them with subsequent code in minified outputs.
- In environments with non-standard semantic whitespace sensitivity (e.g., certain DSLs or transpiled languages), removing braces could alter tokenization or parsing despite identical JS semantics.
- If the inner if statement is part of a comma sequence (else { (0, if(x){}) }), the rewrite breaks invalid syntax but such cases are already parse errors; however, the rule assumes valid input where if is a statement, not an expression.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fix ok | Empty Else Block Prevention | An empty else block ({}) after transformation would be invalid syntax, but the rule prevents this by requiring exactly one if statement. |
| r1-p002 | fired, no fix | Comments Attached to Removed/Moved Code | comments attached to the removed or moved code |
| r1-p003 | fix ok | Getters/Setters/defineProperty Side Effects | getters, setters and Object.defineProperty side effects |
| r1-p004 | silent prediction off | Directive Prologue Scope Change | If the else block starts with a directive prologue like 'use strict';, moving it before else if alters its scope from the else block to the else-if construct, potentially changing strict mode behavior |
| r1-p005 | fix ok | Exceptions Inside Removed/Rewritten Code | exceptions thrown inside the code the fix removes or rewrites |
| r1-p006 | fix ok | Function Declaration Hoisting Difference | If the else block contains a function declaration, hoisting behavior differs between block-scoped (else { function f(){} }) and else-if scoped (else if(...) function f(){}) declarations, potentially c |
| r1-p007 | fix ok | Name Collision Across Scopes After Move | name collisions across scopes after a move (shadowing) |
| r1-p008 | silent | Inner If in Comma Sequence (Invalid Assumed Valid Input) | If the inner if statement is part of a comma sequence (else { (0, if(x){}) }), the rewrite breaks invalid syntax but such cases are already parse errors; however, the rule assumes valid input where if |
| r2-p001 | silent prediction off | Function Declaration Hoisting Behavior Differs | Function Declaration Hoisting Difference |
| r2-p002 | silent prediction off | Name Collision Across Scopes After Move (Shadowing) | Name Collision Across Scopes After Move (shadowing) |
| r2-p003 | fired, no fix | Comments Relocated Preceding Else If Misaligning With Subsequent Code | Comments placed between the else brace and the inner if are relocated to precede the else if, which may misalign them with subsequent code in minified outputs. |
| r2-p004 | silent prediction off | This Binding: Arrow vs Function, Methods Extracted, Call/Apply/Bind | this binding: arrow vs function, methods extracted from objects, call/apply/bind |
| r2-p005 | silent prediction off | Class Fields, Static Blocks, Private Names, Super Calls | class fields, static blocks, private names, super calls |
| r2-p006 | silent prediction off | Function Declarations Inside Blocks, Switch Cases, Labeled Statements | function declarations inside blocks, switch cases, labeled statements |
| r2-p007 | invalid | Label/Break/Continue Control Flow, Finally Blocks, Early Returns in Nested Blocks | label/break/continue control flow, finally blocks, early returns in nested blocks |
| r2-p008 | invalid | Strict vs Sloppy Mode Semantics | strict vs sloppy mode semantics |
Sources
r1-p001 · Empty Else Block Prevention
1let x = 1;
2if (false) {} else {
3 if (true) { x += 1; }
4}
5console.log(x); // Should be 2
--- orig.js 2026-10-17 16:59:32.788110634 -0400 +++ fixed.js 2026-10-17 16:59:33.076114328 -0400 @@ -1,5 +1,3 @@ let x = 1; -if (false) {} else { - if (true) { x += 1; } -} +if (false) {} else if (true) { x += 1; } console.log(x); // Should be 2
2
r1-p002 · Comments Attached to Removed/Moved Code
1let y = 0;
2if (true) {} else {/* keep me */
3 if (false) { y = 99; }
4}
5console.log(y); // Should stay 0 due to false condition
0
r1-p003 · Getters/Setters/defineProperty Side Effects
1const obj = {};
2Object.defineProperty(obj, 'prop', {
3 get() { return this._val || 0; },
4 set(v) { this._val = v * 2; }
5});
6if (false) {} else {
7 if (true) { obj.prop = 5; }
8}
9console.log(obj.prop); // Should be 10 due to setter doubling
--- orig.js 2026-10-17 16:59:33.525120084 -0400 +++ fixed.js 2026-10-17 16:59:33.801123622 -0400 @@ -3,7 +3,5 @@ get() { return this._val || 0; }, set(v) { this._val = v * 2; } }); -if (false) {} else { - if (true) { obj.prop = 5; } -} +if (false) {} else if (true) { obj.prop = 5; } console.log(obj.prop); // Should be 10 due to setter doubling
10
r1-p004 · Directive Prologue Scope Change
1(function() {
2 'use strict';
3 let z = 1;
4 if (false) {} else {
5 'use strict';
6 try { eval('z = 2'); } catch(e) { z = 3; }
7 }
8 console.log(z);
9})();
2
r1-p005 · Exceptions Inside Removed/Rewritten Code
1let w = 0;
2try {
3 if (false) {} else {
4 if (true) { throw new Error('test'); }
5 }
6} catch(e) {
7 w = 1;
8}
9console.log(w); // Should be 1 due to caught exception
--- orig.js 2026-10-17 16:59:34.207128826 -0400 +++ fixed.js 2026-10-17 16:59:34.476132273 -0400 @@ -1,8 +1,6 @@ let w = 0; try { - if (false) {} else { - if (true) { throw new Error('test'); } - } + if (false) {} else if (true) { throw new Error('test'); } } catch(e) { w = 1; }
1
r1-p006 · Function Declaration Hoisting Difference
1let v;
2if (false) {} else {
3 if (true) {
4 function inner() { return 42; }
5 v = inner();
6 }
7}
8console.log(typeof v === 'number' ? v : 'undefined'); // Should be undefined in ES2015+ due to TDZ-like block scoping
--- orig.js 2026-10-17 16:59:34.659134618 -0400 +++ fixed.js 2026-10-17 16:59:34.936138167 -0400 @@ -1,8 +1,6 @@ let v; -if (false) {} else { - if (true) { +if (false) {} else if (true) { function inner() { return 42; } v = inner(); } -} console.log(typeof v === 'number' ? v : 'undefined'); // Should be undefined in ES2015+ due to TDZ-like block scoping
42
r1-p007 · Name Collision Across Scopes After Move
1let u = 1;
2for (let i = 0; i < 2; i++) {
3 if (i === 0) {} else {
4 if (true) {
5 let u = i + 10;
6 console.log(u); // Should log 11 only once (i=1)
7 }
8 }
9}
--- orig.js 2026-10-17 16:59:35.106140345 -0400 +++ fixed.js 2026-10-17 16:59:35.387143944 -0400 @@ -1,9 +1,7 @@ let u = 1; for (let i = 0; i < 2; i++) { - if (i === 0) {} else { - if (true) { + if (i === 0) {} else if (true) { let u = i + 10; console.log(u); // Should log 11 only once (i=1) } - } }
11
r1-p008 · Inner If in Comma Sequence (Invalid Assumed Valid Input)
1// This case is a parse error and not valid input; rule assumes if is statement.
2// No trigger expected as per hazard description.
3console.log('no-op');
no-op
r2-p001 · Function Declaration Hoisting Behavior Differs
1let result = [];
2if (false) {
3} else {
4 function f() { return 1; }
5 result.push(typeof f === 'function' ? f() : 'not fn');
6}
7result.push(typeof f === 'function' ? f() : 'global fn');
8console.log(result.join(','));
1,global fn
r2-p002 · Name Collision Across Scopes After Move (Shadowing)
1let x = 1;
2if (false) {
3} else {
4 let x = 2;
5 console.log(x); // should be 2
6}
7console.log(x); // should be 1
8// After fix: else if(false) { let x = 2; ... } -> x is block-scoped to else-if, outer x unchanged
2 1
r2-p003 · Comments Relocated Preceding Else If Misaligning With Subsequent Code
1if (false) {
2} else {
3 /* This comment should stay with the inner if logic */
4 if (true) {
5 console.log('A');
6 }
7}
8console.log('B');
9// After fix: else /* This comment... */ if (true) { ... }
10// In minified output, comment may be detached from logic, but we trace execution:
11// Should print A then B regardless of comment placement.
A B
r2-p004 · This Binding: Arrow vs Function, Methods Extracted, Call/Apply/Bind
1const obj = { val: 42, getVal: function() { return this.val; } };
2const getVal = obj.getVal;
3if (false) {
4} else {
5 const bound = getVal.bind(obj);
6 console.log(bound()); // should be 42
7}
8// After fix: else if(false) { ... } -> bind still happens in same scope, obj captured
9// So output remains 42
42
r2-p005 · Class Fields, Static Blocks, Private Names, Super Calls
1class Base {
2 static staticVal = 100;
3}
4if (false) {
5} else {
6 class Derived extends Base {
7 static staticVal = 200;
8 static {
9 console.log(Base.staticVal + Derived.staticVal); // 100 + 200 = 300
10 }
11 }
12}
13// After fix: else if(false) { ... } -> static block still runs in same scope
14// So output is 300
300
r2-p006 · Function Declarations Inside Blocks, Switch Cases, Labeled Statements
1let fnResult = null;
2if (false) {
3} else {
4 function helper() { return 99; }
5 switch (helper()) {
6 case 99:
7 fnResult = 'hit';
8 break;
9 }
10}
11console.log(fnResult); // should be 'hit'
12// After fix: else if(false) { ... } -> helper() still defined and callable in same scope
hit
r2-p007 · Label/Break/Continue Control Flow, Finally Blocks, Early Returns in Nested Blocks (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-17T20-59-2)
1let count = 0;
2outer:
3if (false) {
4} else {
5 while (count < 2) {
6 if (count === 0) {
7 count++;
8 continue outer;
9 }
10 console.log('inner', count); // should print 'inner 1'
11 break;
12 }
13}
14console.log('done', count); // should be 'done 2'
15// After fix: else if(false) { ... } -> label 'outer' still valid, continue works
r2-p008 · Strict vs Sloppy Mode Semantics (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-17T20-59-2)
1"use strict";
2let err = null;
3try {
4 if (false) {
5 } else {
6 function junk() {}
7 junk();
8 delete junk; // TypeError in strict mode
9 }
10} catch (e) {
11 err = e.name + ': ' + e.message;
12}
13console.log(err || 'no error');
14// After fix: else if(false) { ... } -> delete junk still throws in strict mode
15// So output shows error