prefer-final-fields

Same as prefer_final_fields in Dart lints

Group
Style
Path
lint/style/prefer-final-fields
Default
On (recommended)
Origin
Dart lints
Rule
354 of 512
recommended

Flags a private field that is initialized but never reassigned, so it could be final (prefer-final-fields, adopted from package:lints).

Marking such fields final documents that they never change after construction and lets the compiler enforce it, preventing accidental mutation. The analysis is deliberately conservative to avoid false positives: a whole-library scan first records every name that is ever assigned, compound assigned, or incremented, and any such write disqualifies that name (matched by identifier across the library, so a like-named field in another class also protects it). A candidate is reported only when it is provably initialized through the channels recognized by the analyzer: every declared constructor setting it via : _x = ... or a this._x formal, or a declaration initializer when no constructor sets it. Enum fields are exempt. Only private (underscore-prefixed) fields are considered, and those already final, const, late, external, or abstract are skipped.

Library awareness (suppress-only): a private field can be written from a sibling part file, so write collection unions this file with every sibling in its [AnalyzeContext::library]. When the library has an unresolved part, or this file declares part/part of directives but no library context is available, the full set of writes cannot be seen — every private-field diagnostic for the file is suppressed rather than risk a false positive. A standalone file with no part directives keeps the exact single-file behavior.

Invalid

example.dartdart
// Private fields only ever initialized, never reassigned.
class A {
  int _a = 1;
  int get a => _a;
}

class B {
  static int _shared = 0;
  static int read() => _shared;
}

class C {
  int _c;
  C(this._c);
}

class D {
  int _d;
  D(int v) : _d = v;
}

class E {
  String _e = 'x';
  String describe() => _e;
}

class F {
  final List<int> items = [];
  int _f;
  F(this._f);
}

class EveryConstructor {
  int _value;
  EveryConstructor(this._value);
  EveryConstructor.zero() : _value = 0;
  int get value => _value;
}
The private field `_a` could be `final`.
2class A {
3 int _a = 1;
The private field `_shared` could be `final`.
7class B {
8 static int _shared = 0;
The private field `_c` could be `final`.
12class C {
13 int _c;
The private field `_d` could be `final`.
17class D {
18 int _d;
The private field `_e` could be `final`.
22class E {
23 String _e = 'x';
The private field `_f` could be `final`.
28 final List<int> items = [];
29 int _f;
The private field `_value` could be `final`.
33class EveryConstructor {
34 int _value;

Valid

example.dartdart
// Fields that are reassigned, already final, public, or late.
class A {
  int _a = 0;
  void inc() {
    _a += 1;
  }
}

class B {
  int _b = 0;
  void assign(int v) {
    _b = v;
  }
}

class C {
  final int _c;
  C(this._c);
}

class D {
  int value = 0;
  void bump() {
    value++;
  }
}

class E {
  late int _e;
  void init() {
    _e = 5;
  }
}

class F {
  int _f = 0;
  void tick() {
    _f++;
  }
}

class Redirecting {
  int _value;
  Redirecting(this._value);
  Redirecting.zero() : this(0);
  int get value => _value;
}

enum Status {
  active;
  static int _cache = 0;
  static int get cache => _cache;
}

Configure prefer-final-fields

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

falcon.jsonjson
{
  "linter": {
    "rules": {
      "style": {
        "prefer-final-fields": "error"
      }
    }
  }
}

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

example.dartdart
// falcon-ignore lint/style/prefer-final-fields: <reason>