| // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/visitor.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| |
| import '../analyzer.dart'; |
| import '../extensions.dart'; |
| |
| const _desc = |
| r'Prefer final for variable declarations if they are not reassigned.'; |
| |
| const _details = r''' |
| **DO** prefer declaring variables as final if they are not reassigned later in |
| the code. |
| |
| Declaring variables as final when possible is a good practice because it helps |
| avoid accidental reassignments and allows the compiler to do optimizations. |
| |
| **BAD:** |
| ```dart |
| void badMethod() { |
| var label = 'hola mundo! badMethod'; // LINT |
| print(label); |
| } |
| ``` |
| |
| **GOOD:** |
| ```dart |
| void goodMethod() { |
| final label = 'hola mundo! goodMethod'; |
| print(label); |
| } |
| ``` |
| |
| **GOOD:** |
| ```dart |
| void mutableCase() { |
| var label = 'hola mundo! mutableCase'; |
| print(label); |
| label = 'hello world'; |
| print(label); |
| } |
| ``` |
| |
| '''; |
| |
| class PreferFinalLocals extends LintRule { |
| static const LintCode code = LintCode( |
| 'prefer_final_locals', 'Local variables should be final.', |
| correctionMessage: 'Try making the variable final.'); |
| |
| PreferFinalLocals() |
| : super( |
| name: 'prefer_final_locals', |
| description: _desc, |
| details: _details, |
| group: Group.style); |
| |
| @override |
| List<String> get incompatibleRules => const ['unnecessary_final']; |
| |
| @override |
| LintCode get lintCode => code; |
| |
| @override |
| void registerNodeProcessors( |
| NodeLintRegistry registry, LinterContext context) { |
| var visitor = _Visitor(this); |
| registry.addDeclaredVariablePattern(this, visitor); |
| registry.addPatternVariableDeclaration(this, visitor); |
| registry.addVariableDeclarationList(this, visitor); |
| } |
| } |
| |
| class _DeclaredVariableVisitor extends RecursiveAstVisitor<void> { |
| final List<BindPatternVariableElement> declaredElements = []; |
| |
| @override |
| void visitDeclaredVariablePattern(DeclaredVariablePattern node) { |
| var element = node.declaredElement; |
| if (element != null) { |
| declaredElements.add(element); |
| } |
| } |
| } |
| |
| class _Visitor extends SimpleAstVisitor<void> { |
| final LintRule rule; |
| |
| _Visitor(this.rule); |
| |
| bool isPotentiallyMutated(AstNode pattern, FunctionBody function) { |
| if (pattern is DeclaredVariablePattern) { |
| var element = pattern.declaredElement; |
| if (element == null || function.isPotentiallyMutatedInScope(element)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @override |
| void visitDeclaredVariablePattern(DeclaredVariablePattern node) { |
| // [DeclaredVariablePattern]s which are declared by a |
| // [PatternVariableDeclaration] are reported in |
| // [visitPatternVariableDeclaration]. |
| if (node.thisOrAncestorOfType<PatternVariableDeclaration>() != null) return; |
| if (node.isDeclaredFinal) return; |
| |
| var function = node.thisOrAncestorOfType<FunctionBody>(); |
| if (function == null) return; |
| |
| var inCaseClause = node.thisOrAncestorOfType<CaseClause>() != null; |
| if (inCaseClause) { |
| if (!isPotentiallyMutated(node, function)) { |
| rule.reportLint(node); |
| } |
| } else { |
| if (isPotentiallyMutated(node, function)) return; |
| } |
| |
| if (!inCaseClause) { |
| rule.reportLint(node); |
| } |
| } |
| |
| @override |
| void visitPatternVariableDeclaration(PatternVariableDeclaration node) { |
| if (node.isDeclaredFinal) return; |
| if (node.keyword.isFinal) return; |
| |
| var function = node.thisOrAncestorOfType<FunctionBody>(); |
| if (function == null) return; |
| |
| var inCaseClause = node.thisOrAncestorOfType<CaseClause>() != null; |
| |
| if (inCaseClause) { |
| if (!isPotentiallyMutated(node, function)) { |
| rule.reportLint(node); |
| } |
| } else { |
| var declaredVariableVisitor = _DeclaredVariableVisitor(); |
| node.accept(declaredVariableVisitor); |
| var declaredElements = declaredVariableVisitor.declaredElements; |
| for (var element in declaredElements) { |
| if (function.isPotentiallyMutatedInScope(element)) { |
| return; |
| } |
| } |
| |
| rule.reportLintForToken(node.keyword); |
| } |
| } |
| |
| @override |
| void visitVariableDeclarationList(VariableDeclarationList node) { |
| if (node.isConst || node.isFinal) return; |
| |
| var function = node.thisOrAncestorOfType<FunctionBody>(); |
| if (function == null) return; |
| |
| for (var variable in node.variables) { |
| if (variable.equals == null || variable.initializer == null) { |
| return; |
| } |
| var declaredElement = variable.declaredElement; |
| if (declaredElement != null && |
| function.isPotentiallyMutatedInScope(declaredElement)) { |
| return; |
| } |
| } |
| if (node.keyword != null) { |
| rule.reportLintForToken(node.keyword); |
| } else if (node.type != null) { |
| rule.reportLint(node.type); |
| } |
| } |
| } |
| |
| extension on AstNode { |
| bool get isDeclaredFinal { |
| var self = this; |
| if (self is DeclaredVariablePattern) { |
| if (self.keyword.isFinal) return true; |
| var pattern = self.thisOrAncestorOfType<ForEachPartsWithPattern>(); |
| if (pattern != null && pattern.keyword.isFinal) return true; |
| } |
| return false; |
| } |
| } |