Skip to content

use_existing_destructuring

v0.4.0 Warning Fix Pattern Matching

This rule flags user.email when a destructuring of user already exists earlier in the same scope. The property belongs in that pattern; the quick fix adds it there and rewrites the access to the new variable.

Once a destructuring exists, it is the place a reader looks to see what this function uses from the object. A property read that bypasses it hides one field from that list, and the next person adding a field has to decide, for no reason, which of the two styles to follow.

See also: Dart patterns

class Session {
const Session({
required this.userId,
required this.token,
required this.expiresAt,
});
final String userId;
final String token;
final DateTime expiresAt;
}
void audit(Session session) {
final Session(:userId) = session;
print(userId);
print(session.token); // LINT: add :token to the destructuring
print(session.expiresAt); // LINT: add :expiresAt too
}
void audit(Session session) {
final Session(:userId, :token, :expiresAt) = session;
print(userId);
print(token);
print(expiresAt);
}

The same applies to a record destructuring:

// Don't
void describe(({int width, int height}) size) {
final (:width) = size;
print('$width x ${size.height}'); // LINT
}
// Do
void describe(({int width, int height}) size) {
final (:width, :height) = size;
print('$width x $height');
}

The rule reports only where adding the field to the pattern would be a behaviour-preserving edit. It stays silent when:

No destructuring exists. Plain session.token in a function that never destructures session is fine — this rule does not ask you to start.

The access comes first. A read above the destructuring cannot use a variable that is not bound yet.

It is a method call, not a property. session.refresh() is a call; patterns bind fields and getters, not invocations.

It is an assignment target. session.token = 'x' writes through the object, which a destructured copy cannot do.

The receiver is not a local variable. A top-level or field object (globalSession.token), or a call result (currentSession().token), is left alone.

The access is inside a closure. A property read inside a () { ... } runs later, so the rule treats it as a separate context.

A different variable is being read. final Session(:userId) = a; print(b.token); touches two objects and is not reported.

This rule is in the opinionated preset, so it is on with preset: opinionated, or by name:

many_lints.yaml
rules:
use_existing_destructuring: true

To turn it off again:

many_lints.yaml
rules:
use_existing_destructuring: false

To keep the rule on but skip certain paths, use per-rule exclude.