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

  1. Add the missing mutation inside the loop (`node = node.next;`).
  2. If mutation happens via a helper, make it explicit in the loop (return the next value and assign it).
  3. If the loop is intentionally infinite (`while (true)` with internal breaks), rewrite the condition as `true` so the rule stops tracking the variable.
  4. 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

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


AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20). Data as JSON: /api/errors/159a48b9f30c4ae0. Report an issue: GitHub.