blob: 53aeba9f9ae1bf95090168391258473459f5d5af [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/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import '../analyzer.dart';
import '../util/dart_type_utilities.dart';
const _desc = r"Don't create a lambda when a tear-off will do.";
const _details = r'''
**DON'T** create a lambda when a tear-off will do.
**BAD:**
```dart
names.forEach((name) {
print(name);
});
```
**GOOD:**
```dart
names.forEach(print);
```
''';
bool _containsNullAwareInvocationInChain(AstNode? node) =>
node != null &&
((node is PropertyAccess &&
(node.isNullAware ||
_containsNullAwareInvocationInChain(node.target))) ||
(node is MethodInvocation &&
(node.isNullAware ||
_containsNullAwareInvocationInChain(node.target))) ||
(node is IndexExpression &&
_containsNullAwareInvocationInChain(node.target)));
Iterable<Element?> _extractElementsOfSimpleIdentifiers(AstNode node) =>
DartTypeUtilities.traverseNodesInDFS(node)
.whereType<SimpleIdentifier>()
.map((e) => e.staticElement);
class UnnecessaryLambdas extends LintRule implements NodeLintRule {
UnnecessaryLambdas()
: super(
name: 'unnecessary_lambdas',
description: _desc,
details: _details,
group: Group.style);
@override
void registerNodeProcessors(
NodeLintRegistry registry, LinterContext context) {
var visitor = _Visitor(this, context);
registry.addFunctionExpression(this, visitor);
}
}
class _FinalExpressionChecker {
final Set<ParameterElement?> parameters;
_FinalExpressionChecker(this.parameters);
bool isFinalElement(Element? element) {
if (element is PropertyAccessorElement) {
return element.isSynthetic && element.variable.isFinal;
} else if (element is VariableElement) {
return element.isFinal;
}
return true;
}
bool isFinalNode(Expression? node) {
if (node == null) {
return true;
}
if (node is FunctionExpression) {
var referencedElements = _extractElementsOfSimpleIdentifiers(node);
return !referencedElements.any(parameters.contains);
}
if (node is ParenthesizedExpression) {
return isFinalNode(node.expression);
}
if (node is PrefixedIdentifier) {
return isFinalNode(node.prefix) && isFinalNode(node.identifier);
}
if (node is PropertyAccess) {
return isFinalNode(node.target) && isFinalNode(node.propertyName);
}
if (node is SimpleIdentifier) {
var element = node.staticElement;
if (parameters.contains(element)) {
return false;
}
return isFinalElement(element);
}
return false;
}
}
class _Visitor extends SimpleAstVisitor<void> {
final LintRule rule;
final LinterContext context;
_Visitor(this.rule, this.context);
@override
void visitFunctionExpression(FunctionExpression node) {
if (node.declaredElement?.name != '' || node.body.keyword != null) {
return;
}
var body = node.body;
if (body is BlockFunctionBody && body.block.statements.length == 1) {
var statement = body.block.statements.single;
if (statement is ExpressionStatement &&
statement.expression is InvocationExpression) {
_visitInvocationExpression(
statement.expression as InvocationExpression, node);
} else if (statement is ReturnStatement &&
statement.expression is InvocationExpression) {
var expression = statement.expression;
if (expression is InvocationExpression) {
_visitInvocationExpression(expression, node);
}
}
} else if (body is ExpressionFunctionBody) {
if (body.expression is InvocationExpression) {
_visitInvocationExpression(
body.expression as InvocationExpression, node);
}
}
}
void _visitInvocationExpression(
InvocationExpression node, FunctionExpression nodeToLint) {
var nodeToLintParams = nodeToLint.parameters?.parameters;
if (nodeToLintParams == null ||
!DartTypeUtilities.matchesArgumentsWithParameters(
node.argumentList.arguments, nodeToLintParams)) {
return;
}
bool isTearoffAssignable(DartType? assignedType) {
if (assignedType != null) {
var tearoffType = node.staticInvokeType;
if (tearoffType == null ||
!context.typeSystem.isSubtypeOf(tearoffType, assignedType)) {
return false;
}
}
return true;
}
var parameters = nodeToLintParams.map((e) => e.declaredElement).toSet();
if (node is FunctionExpressionInvocation) {
// todo (pq): consider checking for assignability
// see: https://github.com/dart-lang/linter/issues/1561
var checker = _FinalExpressionChecker(parameters);
if (checker.isFinalNode(node.function)) {
rule.reportLint(nodeToLint);
}
} else if (node is MethodInvocation) {
var target = node.target;
if (target is SimpleIdentifier) {
var element = target.staticElement;
if (element is PrefixElement) {
var imports = element.enclosingElement.getImportsWithPrefix(element);
for (var import in imports) {
if (import.isDeferred) {
return;
}
}
}
}
var parent = nodeToLint.parent;
if (parent is NamedExpression) {
var argType = parent.staticType;
if (!isTearoffAssignable(argType)) {
return;
}
} else if (parent is VariableDeclaration) {
var grandparent = parent.parent;
if (grandparent is VariableDeclarationList) {
var variableType = grandparent.type?.type;
if (!isTearoffAssignable(variableType)) {
return;
}
}
}
var checker = _FinalExpressionChecker(parameters);
if (!_containsNullAwareInvocationInChain(node) &&
checker.isFinalNode(node.target) &&
checker.isFinalElement(node.methodName.staticElement) &&
node.typeArguments == null) {
rule.reportLint(nodeToLint);
}
}
}
}