avoid-unmodified-loop-condition

Path
lint/correctness/avoid-unmodified-loop-condition
Default
On (recommended)
Origin
Falcon
Rule
154 of 512
recommended

Flags loop conditions whose local scalar inputs are never modified by the loop.

A forgotten update can make a while, do or condition-bearing for loop repeat forever. Conditions must consist of resolved local scalar bindings, literals, operators, and immutable String length/emptiness properties. Any write to a referenced declaration in the body, condition or for-update avoids a report; initialization and writes after the loop do not count. Shadowed names are distinct bindings. Literal-only conditions belong to other rules.

Unknown types, mutable-object properties, calls in conditions, captured writes, and loops containing a possible break, labeled continue, return, throw, yield, await, or resolved Never-returning call are deliberately ignored. Exit checks include nested constructs; this is not an infinite-loop proof.

Invalid

example.dartdart
void unchangedBool(bool running) {
  while (running) {
    print(running);
  }
}

void unchangedComparison(int count, int limit) {
  while (count < limit) {
    print(count);
  }
  count++;
}

void unchangedString(String text) {
  while (text.isNotEmpty) {
    print(text);
  }
}

void unchangedDo(bool running) {
  do {
    print(running);
  } while (running);
}

void unrelatedUpdate(int count) {
  for (var index = 0; count > 0; index++) {
    print(index);
  }
}

void shadowedWrite(int count) {
  while (count > 0) {
    var count = 1;
    count--;
    print(count);
  }
}

void initializationIsNotUpdate() {
  for (var index = 0; index < 10;) {
    print(index);
  }
}

void multipleInputs(int left, int right, bool active) {
  while (active && left != right) {
    print(left);
  }
}

void inferredLiteral() {
  var running = true;
  while (!(!running)) {
    print(running);
  }
}
None of the local variables in this condition are modified by the loop. Check for a missing update.
1void unchangedBool(bool running) {
2 while (running) {
None of the local variables in this condition are modified by the loop. Check for a missing update.
7void unchangedComparison(int count, int limit) {
8 while (count < limit) {
None of the local variables in this condition are modified by the loop. Check for a missing update.
14void unchangedString(String text) {
15 while (text.isNotEmpty) {
None of the local variables in this condition are modified by the loop. Check for a missing update.
22 print(running);
23 } while (running);
None of the local variables in this condition are modified by the loop. Check for a missing update.
26void unrelatedUpdate(int count) {
27 for (var index = 0; count > 0; index++) {
None of the local variables in this condition are modified by the loop. Check for a missing update.
32void shadowedWrite(int count) {
33 while (count > 0) {
None of the local variables in this condition are modified by the loop. Check for a missing update.
40void initializationIsNotUpdate() {
41 for (var index = 0; index < 10;) {
None of the local variables in this condition are modified by the loop. Check for a missing update.
46void multipleInputs(int left, int right, bool active) {
47 while (active && left != right) {
None of the local variables in this condition are modified by the loop. Check for a missing update.
53 var running = true;
54 while (!(!running)) {

Valid

example.dartdart
void updatedWhile(int count) {
  while (count > 0) {
    count--;
  }
}

void updatedDo(int count) {
  do {
    count -= 1;
  } while (count > 0);
}

void updatedFor() {
  for (var index = 0; index < 10; index++) {
    print(index);
  }
}

void oneInputChanges(int left, int right) {
  while (left < right) {
    right--;
  }
}

void explicitExit(bool running, bool stop) {
  while (running) {
    if (stop) break;
  }
}

void returns(bool running) {
  while (running) {
    return;
  }
}

void throws(bool running) {
  while (running) {
    throw StateError('stopped');
  }
}

void mutatingCondition(int count) {
  while (count-- > 0) {
    print(count);
  }
}

void unknownCondition(bool Function() next) {
  while (next()) {
    print('next');
  }
}

void mutableProperty(List<int> items) {
  while (items.isNotEmpty) {
    items.removeLast();
  }
}

void dynamicCondition(dynamic running) {
  while (running) {
    print(running);
  }
}

bool globalRunning = true;
void globalCondition() {
  while (globalRunning) {
    print(globalRunning);
  }
}

void intentionalInfinite() {
  while (true) {}
  for (;;) {}
}

void closureWrite() {
  var running = true;
  final stop = () { running = false; };
  while (running) {
    stop();
  }
}

void localFunctionWrite() {
  var running = true;
  void stop() { running = false; }
  while (running) {
    stop();
  }
}

Future<void> asynchronousExit(bool running, Future<void> Function() wait) async {
  while (running) {
    await wait();
  }
}

void patternUpdate(int left, int right) {
  while (left < right) {
    (left, right) = (right, left);
  }
}

void nestedWrite(int count, List<int> items) {
  while (count > 0) {
    for (count in items) {
      print(count);
    }
  }
}

void labeledContinue(bool ready) {
  outer: for (var index = 0; index < 10; index++) {
    while (ready) {
      continue outer;
    }
  }
}

Never stop() => throw StateError('stopped');

void neverReturningCall(bool ready) {
  while (ready) {
    stop();
  }
}

class Terminator {
  Never terminate() => throw StateError('stopped');
}

void neverReturningMethod(bool ready, Terminator terminator) {
  while (ready) {
    terminator.terminate();
  }
}

Configure avoid-unmodified-loop-condition

Set its severity in falcon.json. See the configuration guide.

falcon.jsonjson
{
  "linter": {
    "rules": {
      "correctness": {
        "avoid-unmodified-loop-condition": "error"
      }
    }
  }
}

Or silence one occurrence with a suppression comment; the reason is required.

example.dartdart
// falcon-ignore lint/correctness/avoid-unmodified-loop-condition: <reason>