oxc-project/oxc · error · OxcDiagnostic
'{name}' is not modified in this loop.
Error message
'{name}' is not modified in this loop. What it means
`no-unmodified-loop-condition` reports loop conditions (`while`/`do-while`, and `for` test expressions) that reference variables which are never modified inside the loop body — meaning the loop either never runs, runs exactly one iteration via break, or never terminates. The message names the stale variable and carries only a label, no help text. Like ESLint's version it is heuristic: modifications hidden behind closures, `await` side effects, or `in` operator checks can be missed, and it may also produce false positives when the loop body intentionally mutates via called functions (the rule tries to account for some of this by tracking references).
Source
Thrown at crates/oxc_linter/src/rules/eslint/no_unmodified_loop_condition.rs:17
use rustc_hash::{FxHashMap, FxHashSet};
use oxc_allocator::GetAddress;
use oxc_ast::{
AstKind,
ast::{Expression, IdentifierReference},
};
use oxc_ast_visit::{VisitJs, walk_js};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_semantic::{NodeId, Reference, SymbolId};
use oxc_span::{GetSpan, Span};
use crate::{AstNode, context::LintContext, rule::Rule};
fn no_unmodified_loop_condition_diagnostic(name: &str, span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn(format!("'{name}' is not modified in this loop.")).with_label(span)
}
#[derive(Debug, Default, Clone)]
pub struct NoUnmodifiedLoopCondition;
declare_oxc_lint!(
/// ### What it does
///
/// Disallow references in loop conditions that are never modified within the loop.
///
/// ### Why is this bad?
///
/// A loop condition that depends on values that never change within the loop body
/// can cause infinite loops or logic bugs.
///
/// ### Examples
///
/// Examples of **incorrect** code for this rule:View on GitHub (pinned to e1e7af627c)
Solutions
- Add the missing mutation inside the loop (`node = node.next;`).
- If mutation happens via a helper, make it explicit in the loop (return the next value and assign it).
- If the loop is intentionally infinite (`while (true)` with internal breaks), rewrite the condition as `true` so the rule stops tracking the variable.
- For polling on external state, restructure with events/async primitives instead of a spin loop.
Example fix
// before
while (current) {
visit(current);
}
// after
while (current) {
visit(current);
current = current.next;
} Defensive patterns
Strategy: validation
Validate before calling
// self-check for hand-rolled traversals: assert the cursor advances
function walkSafe(node) {
let cur = node;
let steps = 0;
while (cur) {
visit(cur);
cur = cur.next;
if (++steps > 1e6) throw new Error('loop condition never modified: runaway traversal');
}
} Prevention
- Double-check every while/for you write names the variable it mutates in the body.
- Prefer for...of / iterator protocols over manual cursor loops where possible.
- Treat this diagnostic as an infinite-loop candidate, not a style nit.
When it happens
Trigger: `while (node) { doSomething(node); }` where nothing assigns `node` — the reference in the condition resolves to a symbol with no writes inside the body; also `for (; queue.length;)` when the body never pops the queue.
Common situations: Forgetting the advancing statement (`node = node.next`, `i++`) when hand-rolling traversal; condition variables mutated only inside a helper function called from the loop; while-poll loops waiting on external state that never changes locally.
Related errors
- Invalid loop. Its body allows only one iteration.
- Lacked a call of `super()` in some code paths.
- The update clause in this loop moves the variable in the wro
- Expected a `break` statement before `case`.
- Expected a `break` statement before `default`.
AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20).
Data as JSON: /api/errors/159a48b9f30c4ae0.
Report an issue: GitHub.