Skip to content
This repository was archived by the owner on Nov 20, 2024. It is now read-only.

Commit 16aa227

Browse files
authored
support record and list patterns in prefer_final_locals (#4132)
* support record and list patterns in prefer_final_locals * moar cases * todo * ++tests
1 parent 98b0475 commit 16aa227

File tree

3 files changed

+374
-0
lines changed

3 files changed

+374
-0
lines changed

lib/src/rules/prefer_final_locals.dart

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'package:analyzer/dart/ast/ast.dart';
6+
import 'package:analyzer/dart/ast/token.dart';
67
import 'package:analyzer/dart/ast/visitor.dart';
78

89
import '../analyzer.dart';
@@ -67,6 +68,10 @@ class PreferFinalLocals extends LintRule {
6768
void registerNodeProcessors(
6869
NodeLintRegistry registry, LinterContext context) {
6970
var visitor = _Visitor(this);
71+
registry.addListPattern(this, visitor);
72+
registry.addMapPattern(this, visitor);
73+
registry.addObjectPattern(this, visitor);
74+
registry.addRecordPattern(this, visitor);
7075
registry.addVariableDeclarationList(this, visitor);
7176
}
7277
}
@@ -76,6 +81,101 @@ class _Visitor extends SimpleAstVisitor<void> {
7681

7782
_Visitor(this.rule);
7883

84+
void checkPatternFields(DartPattern node) {
85+
var function = node.thisOrAncestorOfType<FunctionBody>();
86+
if (function == null) return;
87+
88+
late NodeList<PatternField> fields;
89+
if (node is RecordPattern) fields = node.fields;
90+
if (node is ObjectPattern) fields = node.fields;
91+
92+
var parent = node.unParenthesized.parent;
93+
var inPatternVariableDeclaration = false;
94+
if (parent is PatternVariableDeclaration) {
95+
if (parent.keyword.keyword == Keyword.FINAL) return;
96+
inPatternVariableDeclaration = true;
97+
}
98+
99+
for (var field in fields) {
100+
var pattern = field.pattern.declaredVariablePattern;
101+
if (pattern is DeclaredVariablePattern) {
102+
var element = pattern.declaredElement;
103+
if (element == null) continue;
104+
if (function.isPotentiallyMutatedInScope(element)) {
105+
if (inPatternVariableDeclaration) {
106+
return;
107+
} else {
108+
continue;
109+
}
110+
}
111+
if (inPatternVariableDeclaration) {
112+
rule.reportLint((parent! as PatternVariableDeclaration).expression);
113+
return;
114+
} else {
115+
if (!pattern.keyword.isFinal) {
116+
rule.reportLintForToken(pattern.name);
117+
}
118+
}
119+
}
120+
}
121+
}
122+
123+
bool isDeclaredFinal(AstNode node) {
124+
var declaration = node.thisOrAncestorOfType<PatternVariableDeclaration>();
125+
if (declaration == null) return false; // To be safe.
126+
return declaration.keyword.isFinal;
127+
}
128+
129+
bool isPotentiallyMutated(AstNode pattern, FunctionBody function) {
130+
if (pattern is DeclaredVariablePattern) {
131+
var element = pattern.declaredElement;
132+
if (element == null || function.isPotentiallyMutatedInScope(element)) {
133+
return true;
134+
}
135+
}
136+
return false;
137+
}
138+
139+
@override
140+
void visitListPattern(ListPattern node) {
141+
if (isDeclaredFinal(node)) return;
142+
143+
var function = node.thisOrAncestorOfType<FunctionBody>();
144+
if (function == null) return;
145+
146+
for (var element in node.elements) {
147+
if (isPotentiallyMutated(element, function)) return;
148+
}
149+
150+
rule.reportLint(node);
151+
}
152+
153+
@override
154+
void visitMapPattern(MapPattern node) {
155+
if (isDeclaredFinal(node)) return;
156+
157+
var function = node.thisOrAncestorOfType<FunctionBody>();
158+
if (function == null) return;
159+
160+
for (var element in node.elements) {
161+
if (element is MapPatternEntry) {
162+
if (isPotentiallyMutated(element.value, function)) return;
163+
}
164+
}
165+
166+
rule.reportLint(node);
167+
}
168+
169+
@override
170+
void visitObjectPattern(ObjectPattern node) {
171+
checkPatternFields(node);
172+
}
173+
174+
@override
175+
void visitRecordPattern(RecordPattern node) {
176+
checkPatternFields(node);
177+
}
178+
79179
@override
80180
void visitVariableDeclarationList(VariableDeclarationList node) {
81181
if (node.isConst || node.isFinal) return;
@@ -100,3 +200,23 @@ class _Visitor extends SimpleAstVisitor<void> {
100200
}
101201
}
102202
}
203+
204+
extension on Token? {
205+
bool get isFinal {
206+
var self = this;
207+
if (self == null) return false;
208+
return self.keyword == Keyword.FINAL;
209+
}
210+
}
211+
212+
extension on DartPattern {
213+
DeclaredVariablePattern? get declaredVariablePattern {
214+
var self = this;
215+
if (self is DeclaredVariablePattern) return self;
216+
// todo(pq): more cases?
217+
if (self is LogicalAndPattern) {
218+
return self.rightOperand.declaredVariablePattern;
219+
}
220+
return null;
221+
}
222+
}

test/rules/all.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ import 'prefer_constructors_over_static_methods_test.dart'
7777
as prefer_constructors_over_static_methods;
7878
import 'prefer_contains_test.dart' as prefer_contains;
7979
import 'prefer_final_fields_test.dart' as prefer_final_fields;
80+
import 'prefer_final_locals_test.dart' as prefer_final_locals;
8081
import 'prefer_final_parameters_test.dart' as prefer_final_parameters;
8182
import 'prefer_generic_function_type_aliases_test.dart'
8283
as prefer_generic_function_type_aliases;
@@ -169,6 +170,7 @@ void main() {
169170
prefer_constructors_over_static_methods.main();
170171
prefer_contains.main();
171172
prefer_final_fields.main();
173+
prefer_final_locals.main();
172174
prefer_final_parameters.main();
173175
prefer_generic_function_type_aliases.main();
174176
prefer_relative_imports.main();

0 commit comments

Comments
 (0)