prefer-moving-to-variable
Same as prefer-moving-to-variable in Dart Code Metrics
- Group
- Complexity
- Path
lint/complexity/prefer-moving-to-variable- Default
- Off, opt in
- Origin
- Dart Code Metrics
- Rule
- 386 of 512
Flags repeated property/getter chains in local variable initializers.
Chains rooted in an identifier, this, or super, including receiver-based
method calls, are candidates. Receiverless calls, constructors and fresh
collections retain their separate evaluations. Initializer source text is
compared within each statement list. Mutation and deferred late reads reset
counts so a shared variable does not retain a stale value.
Unresolved Type.member calls remain candidates because constructors and static methods are ambiguous.
Options
allowed-duplicated-chains (integer, default: 2) — the occurrence index at
which a repeat is flagged; 2 flags the 2nd and later occurrences, 3 the
3rd and later. Values below 2 are clamped to 2.
Invalid
// Test cases for prefer-moving-to-variable rule
// Repeated property/getter chains can share a local binding.
void duplicateGetterChain() {
final a = service.configuration.value;
final b = service.configuration.value;
}
void duplicateFieldAccess() {
final x = obj.property.value;
final y = obj.property.value;
}
class Processor {
void process() {
final first = this.data.value;
final second = this.data.value;
combine(first, second);
}
}
void duplicateNullAwareChain() {
final x = obj?.property?.value;
final y = obj?.property?.value;
}
void duplicateAcrossReadOnlyDeclaration() {
final v1 = obj.property.value;
final unrelated = 42;
final v2 = obj.property.value;
}
void duplicateAcrossPlainString() {
final before = obj.property.value;
final separator = ', ';
final after = obj.property.value;
}
void duplicateMethodChain() {
final chain1 = obj.method().chain().result;
final chain2 = obj.method().chain().result;
}
void duplicateReceiverInvocation() {
final filtered1 = list.where((e) => e > 5).toList();
final filtered2 = list.where((e) => e > 5).toList();
}
void duplicateThemeLookup() {
final first = Theme.of(context).textTheme;
final second = Theme.of(context).textTheme;
}
void duplicateReceiverCall() {
final first = service.nextValue();
final second = service.nextValue();
}
void unrelatedNonLateName() {
final shared = 1;
final first = o.s;
final unrelated = shared;
final second = o.s;
}
void pureLateRead() {
late final m = 1;
final first = o.s;
final unrelated = m;
final second = o.s;
}
class StaticLookup {
static Object of(Object context) => context;
}
void duplicateKnownStaticMethod(Object context) {
final first = StaticLookup.of(context);
final second = StaticLookup.of(context);
}
void capturedLateShadowedByParameter() {
late final m = mutate();
void inner(int m) {
final a = o.s;
final b = m;
final c = o.s;
}
inner(1);
}
void capturedLateShadowedByLocal() {
late final m = mutate();
final inner = () {
final m = 1;
final a = o.s;
final b = m;
final c = o.s;
};
inner();
}
void creatingClosureDoesNotReadLate() {
late final m = mutate();
final a = o.s;
final inner = () => m;
final c = o.s;
}
void lateSwitchCaseScopes(int value) {
switch (value) {
case 0:
late final m = mutate();
final a = o.s;
final b = m;
final c = o.s;
break;
default:
final m = 1;
final a = o.s;
final b = m;
final c = o.s;
}
}
void lateTryCatchFinallyScopes() {
try {
late final m = mutate();
final a = o.s;
final b = m;
final c = o.s;
} catch (error) {
final m = 1;
final a = o.s;
final b = m;
final c = o.s;
} finally {
final m = 1;
final a = o.s;
final b = m;
final c = o.s;
}
} final a = service.configuration.value; final b = service.configuration.value; final x = obj.property.value; final y = obj.property.value; final first = this.data.value; final second = this.data.value; final x = obj?.property?.value; final y = obj?.property?.value; final unrelated = 42; final v2 = obj.property.value; final separator = ', '; final after = obj.property.value; final chain1 = obj.method().chain().result; final chain2 = obj.method().chain().result; final filtered1 = list.where((e) => e > 5).toList(); final filtered2 = list.where((e) => e > 5).toList(); final first = Theme.of(context).textTheme; final second = Theme.of(context).textTheme; final first = service.nextValue(); final second = service.nextValue(); final unrelated = shared; final second = o.s; final unrelated = m; final second = o.s; final first = StaticLookup.of(context); final second = StaticLookup.of(context); final b = m; final c = o.s; final b = m; final c = o.s; final inner = () => m; final c = o.s; final b = m; final c = o.s; final b = m; final c = o.s; final b = m; final c = o.s;Valid
import 'dart:async';
import 'dart:core';
import 'dart:core' as core;
// Test cases for prefer-moving-to-variable rule
// No violations: unsupported initializers or chains separated by mutation barriers.
void distinctComputations() {
final a = calculateExpensive(10);
final b = calculateExpensive(20);
}
void singleUse() {
final result = getResult(data);
print(result);
}
// Trivial literals are not flagged
void trivialLiterals() {
final x = 42;
final y = 42;
final s1 = "hello";
final s2 = "hello";
}
class Processor {
void process() {
final first = getResult(dataA);
final second = getResult(dataB);
combine(first, second);
}
}
// Only one assignment of complex expression
void singleComplexAssignment() {
final x = obj.method().chain().result;
print(x);
}
// Identifiers (trivial) don't trigger violation
void identifierDuplicates() {
final a = variable1;
final b = variable1;
}
// Different chained calls
void differentChains() {
final c1 = obj.method1().result;
final c2 = obj.method2().result;
}
void separateConstructors() {
final first = Completer();
final second = Completer();
final namedFirst = Completer.sync();
final namedSecond = Completer.sync();
}
void separateCalls() {
final first = calculateExpensive(10);
final second = calculateExpensive(10);
}
Future<void> separateAwaits() async {
final first = await service.load();
final second = await service.load();
}
void freshCollections() {
final list1 = <int>[];
final list2 = <int>[];
final map1 = <String, int>{};
final map2 = <String, int>{};
final set1 = <int>{};
final set2 = <int>{};
final completers1 = [Completer()];
final completers2 = [Completer()];
}
void separateOperators() {
final first = a + b;
final second = a + b;
}
void changingState() {
final before = notifier.state;
notifier.state = nextState;
final after = notifier.state;
}
void changingThroughCall() {
final before = LoggerService.instance;
configureLogger();
final after = LoggerService.instance;
}
void effectfulInitializer() {
final before = notifier.state;
final mutation = notifier.refresh();
final after = notifier.state;
}
Future<void> stateAcrossAwait() async {
final before = notifier.state;
await refresh();
final after = notifier.state;
}
void nestedMutation() {
final before = notifier.state;
if (condition) {
notifier.state = nextState;
}
final after = notifier.state;
}
void mutatedInSameDeclaration() {
final before = notifier.state,
mutation = notifier.refresh(),
after = notifier.state;
}
void reboundRoot() {
final before = notifier.state;
notifier = nextNotifier;
final after = notifier.state;
}
final repeatedConstructorsInCallback = () {
final first = Completer();
final second = Completer();
};
Future<void> stateAcrossAwaitInitializer() async {
final before = notifier.state;
final loaded = await refresh();
final after = notifier.state;
}
void deferredReads() {
final before = notifier.state;
late final after = notifier.state;
notifier.state = nextState;
use(before, after);
}
void separateDeferredReads() {
late final before = notifier.state;
late final after = notifier.state;
use(before);
notifier.state = nextState;
use(after);
}
void deferredDeclarationBarrier() {
final before = notifier.state;
late final deferred = notifier.state;
final after = notifier.state;
}
void interpolatedInitializerBarrier() {
final before = notifier.state;
final text = '${notifier.refresh()}';
final after = notifier.state;
}
void lateReadAfterFirstChain() {
late final m = mutate();
final a = o.s;
final b = m;
final c = o.s;
}
void lateReadInsideExpression() {
late final m = mutate();
final a = o.s;
final b = m.value;
final c = o.s;
}
void outerDeferredRead() {
late final m = mutate();
if (condition) {
final a = o.s;
final b = m;
final c = o.s;
}
}
void freshControllerReceivers() {
final first = TextEditingController().value;
final second = TextEditingController().value;
final explicitFirst = new TextEditingController().value;
final explicitSecond = new TextEditingController().value;
}
class Fresh {
Fresh.named();
int get value => 0;
}
void resolvedFreshReceivers() {
final a = Fresh.named().value;
final b = Fresh.named().value;
final c = DateTime.now().year;
final d = DateTime.now().year;
final e = core.DateTime(2025).year;
final f = core.DateTime(2025).year;
final g = StreamController.broadcast().stream;
final h = StreamController.broadcast().stream;
final i = List.filled(3, 0).length;
final j = List.filled(3, 0).length;
}
void unrelatedLateName() {
late final shared = mutate();
print(shared);
}
void capturedLateInLocalFunction() {
late final m = mutate();
void inner() {
final a = o.s;
final b = m;
final c = o.s;
}
inner();
}
void capturedLateInClosure() {
late final m = mutate();
final inner = () {
final a = o.s;
final b = m;
final c = o.s;
};
inner();
}
void siblingTryBindingDoesNotShadowCapturedLate() {
late final m = mutate();
try {
final m = 1;
use(m);
} catch (_) {
final a = o.s;
final b = m;
final c = o.s;
}
}
void siblingCaseBindingDoesNotShadowCapturedLate(int value) {
late final m = mutate();
switch (value) {
case 0:
final m = 1;
use(m);
break;
default:
final a = o.s;
final b = m;
final c = o.s;
}
}Configure prefer-moving-to-variable
Set its severity in falcon.json. See the configuration guide.
{
"linter": {
"rules": {
"complexity": {
"prefer-moving-to-variable": "error"
}
}
}
}Or silence one occurrence with a suppression comment; the reason is required.
// falcon-ignore lint/complexity/prefer-moving-to-variable: <reason>More complexity rules
- prefer-null-aware-operators
prefer_null_aware_operatorsrecommendedFlags
x == null ? null : x.y(and the invertedx != null ? x.y : null). - unnecessary-const
unnecessary_constrecommendedFlags a redundant
constkeyword inside an already-const context. - unnecessary-getters-setters
unnecessary_getters_settersrecommendedFlags a trivial getter/setter pair that only reads and writes a private field.
- unnecessary-lambdas
unnecessary_lambdasrecommendedFlags a closure that only forwards its parameters to a function a tear-off could name.
- unnecessary-overrides
unnecessary_overridesrecommendedFlags a member that does nothing but forward to
superwith the same arguments and resolved contract. - widget-tree-depthflutter
Limits the depth of an inline Flutter widget construction tree.
- avoid-classes-with-only-static-members
avoid_classes_with_only_static_membersFlags a class that declares only static members.
- avoid-collapsible-if
Flags an else-free
ifwhose only then-statement is another else-freeif.