move-variable-outside-iteration

Path
lint/performance/move-variable-outside-iteration
Default
Off, opt in
Origin
Falcon
Rule
272 of 512

Reports loop-local final variables whose computation is independent of the loop.

Moving a repeated computation before the loop avoids doing the same work on every iteration. This conservative rule checks direct loop-body declarations: non-throwing SDK numeric addition, subtraction and multiplication, and the SDK List.reversed view of a final local list literal. Dependencies must be final locals declared before the loop, never reassigned, and used with their declaration-time type rather than a loop-local promotion. Numeric expressions must depend on a local, not consist entirely of literals. List sources and reversed views may only be iterated or have read-only properties inspected; aliases, escaping identities, mutations and closure captures are excluded. Calls, constructors, fresh collection literals, user-defined operators and getters, late declarations and conditional declarations are not candidates.

Invalid

example.dartdart
void reversedView() {
  final list = [1, 2, 3];
  for (final element in list) {
    final reversed = list.reversed;
    for (final value in reversed) {
      print(value + element);
    }
  }
}

void addition() {
  final width = 10;
  for (var i = 0; i < 3; i++) {
    final padded = width + 2;
    print(padded + i);
  }
}

void multiplication() {
  final scale = 2.5;
  var remaining = 3;
  while (remaining-- > 0) {
    final doubled = scale * 2;
    print(doubled);
  }
}

void subtraction() {
  final maximum = 100;
  var remaining = 3;
  do {
    final limit = maximum - 1;
    print(limit);
  } while (remaining-- > 0);
}

void compound() {
  final width = 10;
  final height = 20;
  for (final value in [1, 2]) {
    final area = (width + 2) * (height - 1);
    print(area + value);
  }
}

void nested() {
  for (final value in [1, 2]) {
    final size = value + 1;
    for (var i = 0; i < 3; i++) {
      final doubled = size * 2;
      print(doubled + i);
    }
  }
}
This computation is loop-invariant. Move the final variable before the loop.
3 for (final element in list) {
4 final reversed = list.reversed;
This computation is loop-invariant. Move the final variable before the loop.
13 for (var i = 0; i < 3; i++) {
14 final padded = width + 2;
This computation is loop-invariant. Move the final variable before the loop.
22 while (remaining-- > 0) {
23 final doubled = scale * 2;
This computation is loop-invariant. Move the final variable before the loop.
31 do {
32 final limit = maximum - 1;
This computation is loop-invariant. Move the final variable before the loop.
40 for (final value in [1, 2]) {
41 final area = (width + 2) * (height - 1);
This computation is loop-invariant. Move the final variable before the loop.
49 for (var i = 0; i < 3; i++) {
50 final doubled = size * 2;

Valid

example.dartdart
void loopBound() {
  for (final element in [1, 2, 3]) {
    final doubled = element * 2;
    print(doubled);
  }
}

void mutableBinding() {
  var width = 10;
  for (var i = 0; i < 3; i++) {
    final padded = width + 2;
    print(padded);
    width++;
  }
}

void unwrittenMutableBinding() {
  var width = 10;
  for (var i = 0; i < 3; i++) {
    final padded = width + 2;
    print(padded);
  }
}

void mutableResult() {
  final width = 10;
  for (var i = 0; i < 3; i++) {
    var padded = width + 2;
    print(padded++);
  }
}

void mutatedSource() {
  final list = [1, 2, 3];
  for (var i = 0; i < 3; i++) {
    final reversed = list.reversed;
    print(reversed.length);
    list.add(i);
  }
}

void mutatedLength() {
  final list = [1, 2, 3];
  for (var i = 0; i < 3; i++) {
    final reversed = list.reversed;
    print(reversed.length);
    list.length--;
  }
}

void escapingSource() {
  final list = [1, 2, 3];
  final alias = list;
  for (var i = 0; i < 3; i++) {
    final reversed = list.reversed;
    print(reversed.length);
    alias.add(i);
  }
}

void escapingView(List<Iterable<int>> results) {
  final list = [1, 2, 3];
  for (var i = 0; i < 3; i++) {
    final reversed = list.reversed;
    results.add(reversed);
  }
}

void freshIdentity(List<List<int>> results) {
  for (var i = 0; i < 3; i++) {
    final list = <int>[];
    results.add(list);
  }
}

void sideEffects(int Function() next) {
  for (var i = 0; i < 3; i++) {
    final value = next();
    print(value);
  }
}

void closures(List<int Function()> callbacks) {
  final width = 10;
  for (var i = 0; i < 3; i++) {
    final callback = () => width + i;
    callbacks.add(callback);
  }
}

void capturedSource(List<void Function()> callbacks) {
  final list = [1, 2, 3];
  callbacks.add(() => list.add(4));
  for (var i = 0; i < 3; i++) {
    final reversed = list.reversed;
    print(reversed.length);
  }
}

void unknownCollection(List<int> list) {
  for (var i = 0; i < 3; i++) {
    final reversed = list.reversed;
    print(reversed.length);
  }
}

void possibleThrow() {
  final numerator = 10;
  final denominator = 0;
  for (var i = 0; i < 3; i++) {
    final quotient = numerator ~/ denominator;
    print(quotient);
  }
}

void conditionalDeclaration(bool enabled) {
  final width = 10;
  for (var i = 0; i < 3; i++) {
    if (enabled) {
      final padded = width + 2;
      print(padded);
    }
  }
}

class Custom {
  Custom operator +(Custom other) => Custom();
  Iterable<int> get reversed => [DateTime.now().microsecond];
}

void customOperations() {
  final value = Custom();
  for (var i = 0; i < 3; i++) {
    final sum = value + value;
    final reversed = value.reversed;
    print(sum);
    print(reversed.length);
  }
}

void alreadyHoisted() {
  final list = [1, 2, 3];
  final reversed = list.reversed;
  for (final element in list) {
    print(reversed.length + element);
  }
}

void promotedNullable(int? input) {
  final width = input;
  for (var i = 0; i < 3; i++) {
    if (width == null) continue;
    final padded = width + 1;
    print(padded);
  }
}

void promotedCollection() {
  final Object list = [1, 2, 3];
  for (var i = 0; i < 3; i++) {
    if (list is! List<int>) continue;
    final reversed = list.reversed;
    print(reversed.length);
  }
}

void literalArithmetic() {
  for (var i = 0; i < 3; i++) {
    final value = (2 + 3) * 4;
    print(value);
  }
}

void indexMutation() {
  final list = [1, 2, 3];
  for (var i = 0; i < 3; i++) {
    final reversed = list.reversed;
    print(reversed.length);
    list[0] = i;
  }
}

void passingSource(void Function(List<int>) mutate) {
  final list = [1, 2, 3];
  for (var i = 0; i < 3; i++) {
    final reversed = list.reversed;
    print(reversed.length);
    mutate(list);
  }
}

Configure move-variable-outside-iteration

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

falcon.jsonjson
{
  "linter": {
    "rules": {
      "performance": {
        "move-variable-outside-iteration": "error"
      }
    }
  }
}

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

example.dartdart
// falcon-ignore lint/performance/move-variable-outside-iteration: <reason>
  • noop-primitive-operationsnoop_primitive_operationsrecommended

    Flags primitive operations that return their receiver unchanged.

  • prefer-const-border-radiusprefer-const-border-radiusrecommendedflutter

    Flags BorderRadius.only(...) whose four corner radii are all equal.

  • prefer-containsprefer_containsrecommended

    Prefers contains to comparing the result of indexOf with a constant.

  • prefer-correct-edge-insets-constructorprefer-correct-edge-insets-constructorrecommendedflutter

    Flags an EdgeInsets.only(...) that a more specific constructor expresses better.

  • prefer-declaring-const-constructorprefer_declaring_const_constructorflutter

    Flags a generative constructor that is legally eligible to be const.

  • prefer-foreachprefer_foreach

    Prefers forEach to a for-in loop that only passes each element to a function.

  • prefer-return-await

    Await returned SDK Futures inside a try body protected by a catch clause.

  • prefer-single-setstateflutter

    Merge consecutive Flutter State.setState statements in the same block.