22// for details. All rights reserved. Use of this source code is governed by a
33// BSD-style license that can be found in the LICENSE file.
44
5- import 'dart:collection' ;
6-
75import 'package:analyzer/dart/ast/ast.dart' ;
86import 'package:analyzer/dart/ast/token.dart' ;
97import 'package:analyzer/dart/ast/visitor.dart' ;
@@ -83,17 +81,6 @@ class NotAssignedInAllConstructors {
8381```
8482''' ;
8583
86- bool _containedInFormal (Element element, FormalParameter formal) {
87- var formalField = formal.declaredElement;
88- return formalField is FieldFormalParameterElement &&
89- formalField.field == element;
90- }
91-
92- bool _containedInInitializer (
93- Element element, ConstructorInitializer initializer) =>
94- initializer is ConstructorFieldInitializer &&
95- initializer.fieldName.canonicalElement == element;
96-
9784class PreferFinalFields extends LintRule {
9885 static const LintCode code = LintCode (
9986 'prefer_final_fields' , "The private field {0} could be 'final'." ,
@@ -112,16 +99,39 @@ class PreferFinalFields extends LintRule {
11299 @override
113100 void registerNodeProcessors (
114101 NodeLintRegistry registry, LinterContext context) {
115- var visitor = _Visitor (this );
102+ var visitor = _Visitor (this , context );
116103 registry.addCompilationUnit (this , visitor);
117- registry.addFieldDeclaration (this , visitor);
118104 }
119105}
120106
121- class _MutatedFieldsCollector extends RecursiveAstVisitor <void > {
122- final Set <FieldElement > _mutatedFields;
107+ class _DeclarationsCollector extends RecursiveAstVisitor <void > {
108+ final fields = < FieldElement , VariableDeclaration > {};
109+
110+ @override
111+ void visitFieldDeclaration (FieldDeclaration node) {
112+ if (node.parent is EnumDeclaration ) return ;
113+ if (node.fields.isFinal || node.fields.isConst) {
114+ return ;
115+ }
116+
117+ for (var variable in node.fields.variables) {
118+ var element = variable.declaredElement;
119+ if (element is FieldElement &&
120+ element.isPrivate &&
121+ ! element.overridesField) {
122+ fields[element] = variable;
123+ }
124+ }
125+ }
126+ }
127+
128+ class _FieldMutationFinder extends RecursiveAstVisitor <void > {
129+ /// The collection of fields declared in this library.
130+ ///
131+ /// This visitor removes a field when it finds that it is assigned anywhere.
132+ final Map <FieldElement , VariableDeclaration > _fields;
123133
124- _MutatedFieldsCollector (this ._mutatedFields );
134+ _FieldMutationFinder (this ._fields );
125135
126136 @override
127137 void visitAssignmentExpression (AssignmentExpression node) {
@@ -148,67 +158,57 @@ class _MutatedFieldsCollector extends RecursiveAstVisitor<void> {
148158 void _addMutatedFieldElement (CompoundAssignmentExpression assignment) {
149159 var element = assignment.writeElement? .canonicalElement;
150160 if (element is FieldElement ) {
151- _mutatedFields. add (element);
161+ _fields. remove (element);
152162 }
153163 }
154164}
155165
156166class _Visitor extends SimpleAstVisitor <void > {
157167 final LintRule rule;
158168
159- final Set < FieldElement > _mutatedFields = HashSet < FieldElement >() ;
169+ final LinterContext context ;
160170
161- _Visitor (this .rule);
171+ _Visitor (this .rule, this .context );
162172
163173 @override
164174 void visitCompilationUnit (CompilationUnit node) {
165- node.accept (_MutatedFieldsCollector (_mutatedFields));
166- }
167-
168- @override
169- void visitFieldDeclaration (FieldDeclaration node) {
170- if (node.parent is EnumDeclaration ) return ;
175+ var declarationsCollector = _DeclarationsCollector ();
176+ node.accept (declarationsCollector);
177+ var fields = declarationsCollector.fields;
171178
172- var fields = node. fields;
173- if (fields.isFinal || fields.isConst ) {
174- return ;
179+ var fieldMutationFinder = _FieldMutationFinder ( fields) ;
180+ for ( var unit in context.allUnits ) {
181+ unit.unit. accept (fieldMutationFinder) ;
175182 }
176183
177- for (var variable in fields.variables) {
178- var element = variable.declaredElement;
184+ for (var MapEntry (key: field, value: variable) in fields.entries) {
185+ // TODO(srawlins): We could look at the constructors once and store a set
186+ // of which fields are initialized by any, and a set of which fields are
187+ // initialized by all. This would concievably improve performance.
188+ var classDeclaration = variable.parent? .parent? .parent;
189+ var constructors = classDeclaration is ClassDeclaration
190+ ? classDeclaration.members.whereType <ConstructorDeclaration >()
191+ : < ConstructorDeclaration > [];
179192
180- if (element is PropertyInducingElement &&
181- element.isPrivate &&
182- ! _mutatedFields.contains (element)) {
183- bool fieldInConstructor (ConstructorDeclaration constructor) =>
184- constructor.initializers.any ((ConstructorInitializer initializer) =>
185- _containedInInitializer (element, initializer)) ||
186- constructor.parameters.parameters.any ((FormalParameter formal) =>
187- _containedInFormal (element, formal));
188-
189- var classDeclaration = node.parent;
190- var constructors = classDeclaration is ClassDeclaration
191- ? classDeclaration.members.whereType <ConstructorDeclaration >()
192- : < ConstructorDeclaration > [];
193- var isFieldInConstructors = constructors.any (fieldInConstructor);
194- var isFieldInAllConstructors = constructors.every (fieldInConstructor);
195-
196- if (element.overridesField ()) continue ;
197-
198- if (isFieldInConstructors) {
199- if (isFieldInAllConstructors) {
200- rule.reportLint (variable, arguments: [variable.name.lexeme]);
201- }
202- } else if (element.hasInitializer) {
193+ var isSetInAnyConstructor = constructors
194+ .any ((constructor) => field.isSetInConstructor (constructor));
195+
196+ if (isSetInAnyConstructor) {
197+ var isSetInEveryConstructor = constructors
198+ .every ((constructor) => field.isSetInConstructor (constructor));
199+
200+ if (isSetInEveryConstructor) {
203201 rule.reportLint (variable, arguments: [variable.name.lexeme]);
204202 }
203+ } else if (field.hasInitializer) {
204+ rule.reportLint (variable, arguments: [variable.name.lexeme]);
205205 }
206206 }
207207 }
208208}
209209
210210extension on VariableElement {
211- bool overridesField () {
211+ bool get overridesField {
212212 var enclosingElement = this .enclosingElement;
213213 if (enclosingElement is ! InterfaceElement ) return false ;
214214
@@ -219,4 +219,20 @@ extension on VariableElement {
219219 .lookUpSetter2 (name, inherited: true , library) !=
220220 null ;
221221 }
222+
223+ bool isSetInConstructor (ConstructorDeclaration constructor) =>
224+ constructor.initializers.any (isSetInInitializer) ||
225+ constructor.parameters.parameters.any (isSetInParameter);
226+
227+ /// Whether `this` is initialized in [initializer] .
228+ bool isSetInInitializer (ConstructorInitializer initializer) =>
229+ initializer is ConstructorFieldInitializer &&
230+ initializer.fieldName.canonicalElement == this ;
231+
232+ /// Whether `this` is initialized with [parameter] .
233+ bool isSetInParameter (FormalParameter parameter) {
234+ var formalField = parameter.declaredElement;
235+ return formalField is FieldFormalParameterElement &&
236+ formalField.field == this ;
237+ }
222238}
0 commit comments