blob: 1eece0ea5d0bab803efe537ec0a9d548800cfef9 [file] [log] [blame]
// 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 'package:analyzer/src/dart/element/extensions.dart'; // ignore: implementation_imports
import '../analyzer.dart';
import '../extensions.dart';
const _desc =
r'Prefer final for variable declarations if they are not reassigned.';
class PreferFinalLocals extends LintRule {
PreferFinalLocals()
: super(name: LintNames.prefer_final_locals, description: _desc);
@override
List<String> get incompatibleRules => const [LintNames.unnecessary_final];
@override
LintCode get lintCode => LinterLintCode.prefer_final_locals;
@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<BindPatternVariableElement2> declaredElements = [];
@override
void visitDeclaredVariablePattern(DeclaredVariablePattern node) {
var element = node.declaredElement2;
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.declaredElement2;
if (element == null || function.isPotentiallyMutatedInScope2(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 {
var forEachPattern = node.thisOrAncestorOfType<ForEachPartsWithPattern>();
if (forEachPattern != null) {
if (forEachPattern.hasPotentiallyMutatedDeclaredVariableInScope(
function,
)) {
return;
}
} 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 {
if (!node.hasPotentiallyMutatedDeclaredVariableInScope(function)) {
if (node.pattern.containsJustWildcards) return;
rule.reportAtToken(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.declaredElement2;
if (declaredElement != null &&
(declaredElement.isWildcardVariable ||
function.isPotentiallyMutatedInScope2(declaredElement))) {
return;
}
}
var keyword = node.keyword;
if (keyword != null) {
rule.reportAtToken(keyword);
} else if (node.type != null) {
rule.reportLint(node.type);
}
}
}
extension on DartPattern {
bool get containsJustWildcards {
var pattern = this;
return switch (pattern) {
ListPattern() => pattern.elements.every(
(e) => e is DartPattern && e.containsJustWildcards,
),
MapPattern() => pattern.elements.every(
(e) => e is MapPatternEntry && e.value is WildcardPattern,
),
ObjectPattern() => pattern.fields.every(
(e) => e.pattern.containsJustWildcards,
),
ParenthesizedPattern() => pattern.pattern.containsJustWildcards,
RecordPattern() => pattern.fields.every(
(e) => e.pattern.containsJustWildcards,
),
WildcardPattern() => true,
_ => false,
};
}
}
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;
}
bool hasPotentiallyMutatedDeclaredVariableInScope(FunctionBody function) {
var declaredVariableVisitor = _DeclaredVariableVisitor();
accept(declaredVariableVisitor);
var declaredElements = declaredVariableVisitor.declaredElements;
for (var element in declaredElements) {
if (function.isPotentiallyMutatedInScope2(element)) {
return true;
}
}
return false;
}
}