oxc-project/oxc · warning · OxcDiagnostic
Invalid loop. Its body allows only one iteration.
Error message
Invalid loop. Its body allows only one iteration.
What it means
Diagnostic from oxlint's no-unreachable-loop rule. It reports loops whose body always exits (break/return/throw) on every path of the first iteration, so the loop can never complete a second iteration; the loop construct is pointless or the unconditional exit is a bug. It uses the shared CFG helper effective_unreachable_blocks.
Source
Thrown at crates/oxc_linter/src/rules/eslint/no_unreachable_loop.rs:30
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_ecmascript::{
GlobalContext,
side_effects::{MayHaveSideEffects, MayHaveSideEffectsContext, PropertyReadSideEffects},
};
use oxc_macros::declare_oxc_lint;
use oxc_semantic::{IsGlobalReference, NodeId};
use oxc_span::{GetSpan, Span};
use crate::{
AstNode,
context::LintContext,
rule::{DefaultRuleConfig, Rule},
utils::effective_unreachable_blocks,
};
fn no_unreachable_loop_diagnostic(span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("Invalid loop. Its body allows only one iteration.")
.with_help("Remove the loop or make at least one path continue to the next iteration.")
.with_label(span)
}
#[derive(Debug, Default, Clone, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", default, deny_unknown_fields)]
struct NoUnreachableLoopConfig {
ignore: Vec<LoopType>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, JsonSchema)]
enum LoopType {
#[serde(rename = "WhileStatement")]
While,
#[serde(rename = "DoWhileStatement")]
DoWhile,
#[serde(rename = "ForStatement")]
For,View on GitHub (pinned to e1e7af627c)
Solutions
- Remove the loop wrapper and keep the body once if single-pass behavior is intended.
- Make the break/return/throw conditional so at least one path continues to the next iteration.
- Remove the stray break/return left over from debugging.
- Add the intentional loop kind to the rule's ignore option (e.g. "WhileLoop", "ForOfLoop", "ForLoop", "ForInLoop", "DoWhileLoop").
Example fix
// before
for (const item of items) {
handle(item);
break;
}
// after
for (const item of items) {
if (!shouldHandle(item)) break;
handle(item);
} Defensive patterns
Strategy: validation
Prevention
- Prefer array methods (map/forEach/every) over loops with manual breaks to make single-pass intent obvious.
- Re-read loop bodies after changing conditional guards to unconditional exits.
- List intentional single-iteration loops in the rule's ignore option instead of inline disables.
When it happens
Trigger: for (const x of xs) { break; }, while (cond) { return; }, loop bodies that unconditionally throw, or a labeled break that always fires on the first pass.
Common situations: A conditional if (x) break; is refactored into an unconditional break; a debugging break is left in; a loop is kept as a scoping wrapper after the body became single-pass; generated code wraps single-item iteration.
Related errors
- '{name}' is not modified in this loop.
- Unreachable code.
- Lacked a call of `super()` in some code paths.
- Redundant Boolean call
- This label '{label_name}' is unnecessary
AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20).
Data as JSON: /api/errors/d40fceaac152b60b.
Report an issue: GitHub.