avoid_unmodified_loop_condition
v1.0.0 Warning Control Flow
This rule flags a while or do/while loop whose condition reads only variables that the body never assigns. The condition evaluates the same way forever, so the loop either never runs or never stops.
Why use this rule
Section titled “Why use this rule”An infinite loop hangs the isolate and freezes the UI. The cause is usually a forgotten i++, or an increment applied to the wrong variable.
If no variable the condition reads is ever written in the body, no execution can change the outcome — which is what makes this detectable.
See also: Dart: loops
void countdown(int total) { var remaining = total; while (remaining > 0) { print(remaining); // LINT: `remaining` is never decremented }}Advancing the wrong variable is the same bug wearing a disguise:
void countdown(int total) { var remaining = total; var printed = 0; while (remaining > 0) { printed++; // LINT: `remaining` still never changes }}void countdown(int total) { var remaining = total; while (remaining > 0) { print(remaining); remaining--; }}Or use a construct that advances for you:
void countdown(int total) { for (var remaining = total; remaining > 0; remaining--) { print(remaining); }}do/while is covered too
Section titled “do/while is covered too”Here the body runs once and then spins forever, which makes the bug harder to spot in a log:
void drain(int available) { var left = available; do { print(left); // LINT: `left` is never decremented } while (left > 0);}Advancing a copy is not advancing the condition
Section titled “Advancing a copy is not advancing the condition”Assigning to a variable the condition does not read leaves it unchanged, whatever the body appears to be doing:
void poll(int attempts) { var remaining = attempts; var next = remaining; while (remaining > 0) { next = next - 1; // LINT: `remaining` is what the condition reads }}Known limitations
Section titled “Known limitations”The rule is deliberately narrow, because the cost of a false positive here is high.
while (true) is not reported — it is the idiomatic infinite loop, ended by a break. Any break, return or throw in the body suppresses the report for the same reason: the loop has an exit the condition does not control.
Only locals and parameters are tracked. A condition that reads a field, calls a method, accesses a property, or awaits is treated as opaque: those can change without an assignment in the body, so no conclusion is safe. A closure anywhere in the body also suppresses the report, since it may mutate a captured variable when invoked.
Configuration
Section titled “Configuration”This rule is in the core preset, so it is on with preset: core,
preset: recommended or preset: opinionated.
To turn it off:
rules: avoid_unmodified_loop_condition: falseTo keep the rule on but skip certain paths, use per-rule exclude.
Related rules
Section titled “Related rules”prefer_returning_condition— Return the condition instead of true/false branches.avoid_cascade_after_if_null— Detect cascades after if-null operators without parentheses.avoid_collapsible_if— Merge nested if statements with &&.avoid_constant_conditions— Detect comparisons where both sides are constants.