blob: 91cdf92a3332b11d18279c7986a0036517e706bf [file] [log] [blame]
// Copyright (c) 2017, 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/analysis_rule/rule_context.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/error/error.dart';
import '../analyzer.dart';
import '../extensions.dart';
const _desc = r'Use `forEach` to only apply a function to all the elements.';
class PreferForeach extends LintRule {
PreferForeach() : super(name: LintNames.prefer_foreach, description: _desc);
@override
DiagnosticCode get diagnosticCode => LinterLintCode.prefer_foreach;
@override
void registerNodeProcessors(NodeLintRegistry registry, RuleContext context) {
var visitor = _Visitor(this);
registry.addForStatement(this, visitor);
}
}
class _PreferForEachVisitor extends SimpleAstVisitor<void> {
final LintRule rule;
LocalVariableElement? element;
ForStatement? forEachStatement;
_PreferForEachVisitor(this.rule);
@override
void visitBlock(Block node) {
if (node.statements.length == 1) {
node.statements.first.accept(this);
}
}
@override
void visitExpressionStatement(ExpressionStatement node) {
node.expression.accept(this);
}
@override
void visitForStatement(ForStatement node) {
var loopParts = node.forLoopParts;
if (loopParts is ForEachPartsWithDeclaration) {
var element = loopParts.loopVariable.declaredElement2;
if (element != null) {
forEachStatement = node;
this.element = element;
node.body.accept(this);
}
}
}
@override
void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
var arguments = node.argumentList.arguments;
if (arguments.length == 1 && arguments.first.canonicalElement == element) {
rule.reportAtNode(forEachStatement);
}
}
@override
void visitMethodInvocation(MethodInvocation node) {
var arguments = node.argumentList.arguments;
var target = node.target;
if (arguments.length == 1 &&
arguments.first.canonicalElement == element &&
(target == null || !_ReferenceFinder(element).references(target))) {
rule.reportAtNode(forEachStatement);
}
}
@override
void visitParenthesizedExpression(ParenthesizedExpression node) {
node.unParenthesized.accept(this);
}
}
class _ReferenceFinder extends UnifyingAstVisitor<void> {
bool found = false;
final LocalVariableElement? element;
_ReferenceFinder(this.element);
bool references(Expression target) {
if (target.canonicalElement == element) return true;
target.accept(this);
return found;
}
@override
visitNode(AstNode node) {
if (found) return;
found = node.canonicalElement == element;
if (!found) {
super.visitNode(node);
}
}
}
class _Visitor extends SimpleAstVisitor<void> {
final LintRule rule;
_Visitor(this.rule);
@override
void visitForStatement(ForStatement node) {
var loopParts = node.forLoopParts;
if (loopParts is ForEachParts) {
var visitor = _PreferForEachVisitor(rule);
node.accept(visitor);
}
}
}