eslint no-lonely-if
labelled safe
Analysis Nemotron 3 Super
The rule flags when an `if` statement is the sole content of an `else` block.
Fix: Rewrites the lonely `if` inside an `else` block into an `else if` chain by removing the outer `else` braces and placing the inner `if` directly after `else`, preserving all inner statement content and formatting.
12 hazards
- Moving comments attached to the outer `else` brace or the inner `if` line may alter comment association or readability.
- If the inner `if` contains a `break` or `continue`, the rewrite changes which loop or switch construct it affects due to altered nesting depth.
- Labels placed on the outer `else` block become unreachable or misapplied after the rewrite, breaking `goto`-like control flow.
- In strict mode, if the inner `if` declares a function, hoisting behavior shifts because the block scope is removed.
- If the `else` block is preceded by a trailing comment on the same line, the rewrite may detach or reattach that comment incorrectly.
- When the inner `if` is part of a larger ternary expression via implicit return (e.g., arrow functions), the rewrite breaks expression validity.
- If the outer `if` lacks braces and the `else` block is a single-line lonely `if`, the fix produces invalid syntax by creating an `else if` without proper delimiting.
- In environments with automatic semicolon insertion, removing braces may cause the following statement to be incorrectly bound to the `else if`.
- If the inner `if` is immediately followed by a comment-only line that relies on the block for visual grouping, the rewrite may affect code maintenance but not execution.
- Destructuring parameters in the outer function scope that shadow variables declared in the lonely `if` may experience altered temporal dead zone interactions due to scope flattening.
- If the lonely `if` contains a `yield` or `await`, the rewrite does not change behavior, but tooling that expects block-scoped async generators may misanalyze the construct.
- In JSX or template literal contexts embedded within the statement, the rewrite does not apply, but if misapplied by a plugin, it would break HTML or string structure.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | silent prediction off | Label reachability broken by else-if rewrite | Labels placed on the outer `else` block become unreachable or misapplied after the rewrite, breaking `goto`-like control flow. |
| r1-p002 | fix ok | __proto__ key handling in lonely if under else | __proto__ keys, prototype access, Object.assign vs spread |
| r1-p003 | fired, no fix | Comment attachment altered by brace removal | Moving comments attached to the outer `else` brace or the inner `if` line may alter comment association or readability. |
| r1-p004 | silent prediction off | Missing braces in outer if with lonely else-if | If the outer `if` lacks braces and the `else` block is a single-line lonely `if`, the fix produces invalid syntax by creating an `else if` without proper delimiting. |
| r1-p005 | fix ok | Null vs undefined in condition of lonely if | null/undefined distinction, optional chaining short-circuit, nullish vs falsy |
| r1-p006 | fix ok | Async await ordering affected by else-if rewrite | async/await ordering, generators and yield position |
| r1-p007 | fix ok | Yield in lonely if under else - generator analysis | If the lonely `if` contains a `yield` or `await`, the rewrite does not change behavior, but tooling that expects block-scoped async generators may misanalyze the construct. |
| r1-p008 | fix ok | Sparse array access in lonely if under else | sparse arrays, holes, Array(n) vs [n], array-likes |
| r2-p001 | fix ok | Coercion: + vs template literal | numeric/string coercion: + vs template literal, objects with toString/valueOf, Symbol, BigInt |
| r2-p002 | fix ok | Var hoisting redeclaration | var hoisting and re-declaration inside loops or after use |
| r2-p003 | silent prediction off | ASI binding hazard | In environments with automatic semicolon insertion, removing braces may cause the following statement to be incorrectly bound to the `else if`. |
| r2-p004 | silent | Ternary in arrow function | When the inner `if` is part of a larger ternary expression via implicit return (e.g., arrow functions), the rewrite breaks expression validity. |
| r2-p005 | fix ok | Temporal dead zone | temporal dead zone after code is hoisted or reordered |
| r2-p006 | fired, no fix | Trailing comment detachment | If the `else` block is preceded by a trailing comment on the same line, the rewrite may detach or reattach that comment incorrectly. |
| r2-p007 | fix ok | Async await ordering | async/await ordering, generators and yield position |
| r2-p008 | silent | JSX/template literal context | In JSX or template literal contexts embedded within the statement, the rewrite does not apply, but if misapplied by a plugin, it would break HTML or string structure. |
Sources
r1-p001 · Label reachability broken by else-if rewrite
1function test() {
2 let x = 0;
3 outer: if (false) {
4 x = 1;
5 } else {
6 inner: if (true) {
7 x += 2;
8 break inner;
9 }
10 }
11 if (x === 2) console.log('label worked');
12 else console.log('label failed:', x);
13}
14test();
label worked
r1-p002 · __proto__ key handling in lonely if under else
1const obj = {};
2if ('a' in obj) {
3 obj.a = 1;
4} else {
5 if ('__proto__' in obj) {
6 obj.__proto__ = {toString:()=>'proto'};
7 console.log(obj.toString());
8 }
9}
10console.log('__proto__ present:', '__proto__' in obj);
--- orig.js 2026-10-22 14:18:21.354748569 -0400 +++ fixed.js 2026-10-22 14:18:29.125034921 -0400 @@ -1,10 +1,8 @@ const obj = {}; if ('a' in obj) { obj.a = 1; -} else { - if ('__proto__' in obj) { +} else if ('__proto__' in obj) { obj.__proto__ = {toString:()=>'proto'}; console.log(obj.toString()); } -} console.log('__proto__ present:', '__proto__' in obj);
proto __proto__ present: true
r1-p003 · Comment attachment altered by brace removal
1function f() {
2 let flag = false;
3 // outer-else-brace-comment
4 if (!flag) {
5 flag = true;
6 } else {
7 // inner-if-line-comment
8 if (flag) {
9 console.log('inner taken');
10 }
11 }
12 console.log('flag:', flag);
13}
14f();
flag: true
r1-p004 · Missing braces in outer if with lonely else-if
1if (false)
2 console.log('not here');
3else
4 if (true) {
5 console.log('lonely if in else');
6 console.log('second line');
7 }
8console.log('done');
lonely if in else second line done
r1-p005 · Null vs undefined in condition of lonely if
1let val = null;
2if (val !== undefined) {
3 console.log('defined');
4} else {
5 if (val == null) {
6 console.log('nullish');
7 }
8}
9// Change val to undefined to see difference
10val = undefined;
11if (val !== undefined) {
12 console.log('defined');
13} else {
14 if (val == null) {
15 console.log('nullish');
16 }
17}
--- orig.js 2026-10-22 14:18:21.357748679 -0400 +++ fixed.js 2026-10-22 14:18:23.446825446 -0400 @@ -1,17 +1,13 @@ let val = null; if (val !== undefined) { console.log('defined'); -} else { - if (val == null) { +} else if (val == null) { console.log('nullish'); } -} // Change val to undefined to see difference val = undefined; if (val !== undefined) { console.log('defined'); -} else { - if (val == null) { +} else if (val == null) { console.log('nullish'); } -}
defined nullish
r1-p006 · Async await ordering affected by else-if rewrite
1async function test() {
2 let ok = false;
3 if (ok) {
4 await Promise.resolve(1);
5 } else {
6 if (!ok) {
7 const res = await Promise.resolve(2);
8 console.log('got', res);
9 }
10 }
11 console.log('end');
12}
13test().catch(e => console.error(e));
--- orig.js 2026-10-22 14:18:21.358748716 -0400 +++ fixed.js 2026-10-22 14:18:25.364896072 -0400 @@ -2,12 +2,10 @@ let ok = false; if (ok) { await Promise.resolve(1); - } else { - if (!ok) { + } else if (!ok) { const res = await Promise.resolve(2); console.log('got', res); } - } console.log('end'); } test().catch(e => console.error(e));
got 2 end
r1-p007 · Yield in lonely if under else - generator analysis
1function* gen() {
2 yield 1;
3 if (false) {
4 yield 2;
5 } else {
6 if (true) {
7 yield 3;
8 }
9 }
10 yield 4;
11}
12const it = gen();
13console.log([...it].join(','));
--- orig.js 2026-10-22 14:18:24.514864756 -0400 +++ fixed.js 2026-10-22 14:18:26.600941659 -0400 @@ -2,11 +2,9 @@ yield 1; if (false) { yield 2; - } else { - if (true) { + } else if (true) { yield 3; } - } yield 4; } const it = gen();
1,3,4
r1-p008 · Sparse array access in lonely if under else
1const arr = Array(3);
2arr[1] = 'present';
3for (let i = 0; i < 3; i++) {
4 if (i in arr) {
5 console.log('has', arr[i]);
6 } else {
7 if (i === 0) {
8 console.log('hole at', i);
9 }
10 }
11}
--- orig.js 2026-10-22 14:18:25.236891354 -0400 +++ fixed.js 2026-10-22 14:18:28.378007297 -0400 @@ -3,9 +3,7 @@ for (let i = 0; i < 3; i++) { if (i in arr) { console.log('has', arr[i]); - } else { - if (i === 0) { + } else if (i === 0) { console.log('hole at', i); } - } }
hole at 0 has present
r2-p001 · Coercion: + vs template literal
1let x = { valueOf: () => 1 };
2let y = { toString: () => '2' };
3function test(a, b) {
4 if (a == 0) {
5 return String(a) + String(b);
6 } else {
7 if (b == '2') {
8 return `${a}` + `${b}`;
9 }
10 }
11}
12console.log(test(0, y));
13console.log(test(x, y));
--- orig.js 2026-10-22 14:18:37.510346425 -0400 +++ fixed.js 2026-10-22 14:18:41.233485537 -0400 @@ -3,11 +3,9 @@ function test(a, b) { if (a == 0) { return String(a) + String(b); - } else { - if (b == '2') { + } else if (b == '2') { return `${a}` + `${b}`; } - } } console.log(test(0, y)); console.log(test(x, y));
02 [object Object]2
r2-p002 · Var hoisting redeclaration
1for (let i = 0; i < 2; i++) {
2 if (i === 0) {
3 console.log('first');
4 } else {
5 if (true) {
6 var x = i;
7 console.log(x);
8 }
9 }
10}
11// After loop, x should be 1 due to hoisting
12console.log(typeof x === 'number' ? x : 'undef');
--- orig.js 2026-10-22 14:18:37.511346463 -0400 +++ fixed.js 2026-10-22 14:18:40.138444569 -0400 @@ -1,12 +1,10 @@ for (let i = 0; i < 2; i++) { if (i === 0) { console.log('first'); - } else { - if (true) { + } else if (true) { var x = i; console.log(x); } - } } // After loop, x should be 1 due to hoisting console.log(typeof x === 'number' ? x : 'undef');
first 1 1
r2-p003 · ASI binding hazard
1let flag = 0;
2if (flag) {
3 console.log('a');
4} else {
5 if (flag === 0) console.log('b')
6 console.log('c');
7}
8// Expected: logs 'b' then 'c'.
9// If ASI binds 'c' to else if, 'c' might not print.
b c
r2-p004 · Ternary in arrow function
1const f = (a, b) =>
2 a > 0
3 ? a + b
4 : (() => {
5 if (b === 0) return 0;
6 if (b > 0) return b;
7 })();
8try {
9 console.log(f(-1, 5));
10} catch (e) {
11 console.log(e.name + ': ' + e.message);
12}
5
r2-p005 · Temporal dead zone
1function test() {
2 if (false) {
3 let x = 1;
4 } else {
5 if (true) {
6 console.log(typeof x);
7 let x = 2;
8 }
9 }
10}
11try {
12 test();
13} catch (e) {
14 console.log(e.name + ': ' + e.message);
15}
--- orig.js 2026-10-22 14:18:37.512346500 -0400 +++ fixed.js 2026-10-22 14:18:40.752467536 -0400 @@ -1,12 +1,10 @@ function test() { if (false) { let x = 1; - } else { - if (true) { + } else if (true) { console.log(typeof x); let x = 2; } - } } try { test();
ReferenceError: Cannot access 'x' before initialization
r2-p006 · Trailing comment detachment
1let a = 0;
2if (a) {/* unreachable */} else { // this comment should stay
3 if (a === 0) console.log('zero');
4}
5// Output should show comment proximity affects nothing, but we log to see if
6// formatting change alters execution (it shouldn't, but we check)
7console.log('done');
zero done
r2-p007 · Async await ordering
1async function fn() {
2 if (false) {
3 return 1;
4 } else {
5 if (true) {
6 await Promise.resolve(2);
7 return 3;
8 }
9 }
10}
11fn().then(v => console.log('result:', v));
--- orig.js 2026-10-22 14:18:40.020440155 -0400 +++ fixed.js 2026-10-22 14:18:43.414567258 -0400 @@ -1,11 +1,9 @@ async function fn() { if (false) { return 1; - } else { - if (true) { + } else if (true) { await Promise.resolve(2); return 3; } - } } fn().then(v => console.log('result:', v));
result: 3
r2-p008 · JSX/template literal context
1const cond = false;
2const html = cond
3 ? '<div>yes</div>'
4 : (() => {
5 if (true) return '<span>no</span>';
6 return '';
7 })();
8console.log(html);
<span>no</span>