blob: 87a73124a9fb76d4790a82d2ea01818a15e51101 [file]
// Copyright (c) 2021, 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/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag;
import 'package:analyzer/src/error/listener.dart';
class UseResultVerifier {
final DiagnosticReporter _diagnosticReporter;
UseResultVerifier(this._diagnosticReporter);
void checkFunctionExpressionInvocation(FunctionExpressionInvocation node) {
var element = node.element;
if (element == null) {
return;
}
_check(node, element);
}
void checkInstanceCreationExpression(InstanceCreationExpression node) {
var element = node.constructorName.element;
if (element == null) {
return;
}
_check(node, element);
}
void checkMethodInvocation(MethodInvocation node) {
var element = node.methodName.element;
if (element == null) {
return;
}
_check(node, element);
}
void checkPropertyAccess(PropertyAccess node) {
var element = node.propertyName.element;
if (element == null) {
return;
}
_check(node, element);
}
void checkSimpleIdentifier(SimpleIdentifier node) {
if (node.inDeclarationContext()) {
return;
}
var parent = node.parent2;
// Covered by checkPropertyAccess, checkMethodInvocation
// and checkFunctionExpressionInvocation respectively.
if (parent is PropertyAccess ||
parent is MethodInvocation ||
parent is FunctionExpressionInvocation) {
return;
}
var element = node.element;
if (element == null) {
return;
}
_check(node, element);
}
void _check(AstNode node, Element element) {
var parent = node.parent2;
if (parent is PrefixedIdentifier) {
parent = parent.parent2;
}
if (parent is CommentReference) {
// Don't flag references in comments.
return;
}
if (parent is ShowCombinator || parent is HideCombinator) {
return;
}
var annotation = _getUseResultMetadata(element);
if (annotation == null) {
return;
}
if (_passesUsingParam(node, annotation)) {
return;
}
if (_isUsed(node)) {
return;
}
var toAnnotate = node.nodeToAnnotate;
var displayName = toAnnotate is SimpleIdentifier
? toAnnotate.name
: element.displayName;
var message = annotation.useResultMessage;
if (message == null || message.isEmpty) {
_diagnosticReporter.report(
diag.unusedResult.withArguments(name: displayName).at(toAnnotate),
);
} else {
_diagnosticReporter.report(
diag.unusedResultWithMessage
.withArguments(name: displayName, message: message)
.at(toAnnotate),
);
}
}
bool _passesUsingParam(AstNode node, ElementAnnotation annotation) {
if (node is! MethodInvocation) {
return false;
}
var unlessParam = annotation.useResultUnlessParameter;
if (unlessParam == null) {
return false;
}
var argumentList = node.argumentList as ArgumentListImpl;
var parameters = argumentList.correspondingStaticParameters;
if (parameters == null) {
return false;
}
for (var param in parameters) {
var name = param?.name;
if (unlessParam == name) {
return true;
}
}
return false;
}
static ElementAnnotation? _getUseResultMetadata(Element element) {
// Implicit getters/setters.
if (element is PropertyAccessorElement && element.isOriginVariable) {
element = element.variable;
}
var annotations = element.metadata.annotations;
for (int i = 0; i < annotations.length; i++) {
var annotation = annotations[i];
if (annotation.isUseResult) return annotation;
}
return null;
}
static bool _isUsed(AstNode node) {
var parent = node.parent2;
if (parent == null) {
return false;
}
if (parent is CascadeExpression) {
return parent.target2 == node;
}
if (parent is PrefixedIdentifier) {
if (parent.prefix == node) {
return true;
} else {
return _isUsed(parent);
}
}
if (parent is PostfixExpression) {
// Null-checking a result is not a "use." Other uses, like `++`, do count.
return parent.operator.type == TokenType.BANG && _isUsed(parent);
}
if (parent is AsExpression ||
parent is AwaitExpression ||
parent is ConditionalExpression ||
parent is ForElement ||
parent is IfElement ||
parent is ParenthesizedExpression ||
parent is PrefixExpression ||
parent is SpreadElement) {
return _isUsed(parent);
}
if (parent is ForParts) {
// If [node] is the condition of a for-loop, it is used; if it is one of
// the updaters, it is not.
return parent.condition == node;
}
return parent is ArgumentList ||
parent is AssertInitializer ||
parent is AssertStatement ||
// Node should always be RHS so no need to check for a property
// assignment.
parent is AssignmentExpression ||
parent is BinaryExpression ||
parent is ConstructorFieldInitializer ||
parent is DoStatement ||
parent is ExpressionFunctionBody ||
parent is ForEachParts ||
parent is ForLoopParts ||
parent is FunctionExpressionInvocation ||
parent is IfStatement ||
parent is IndexExpression ||
parent is InterpolationExpression ||
parent is ListLiteral ||
parent is MapLiteralEntry ||
parent is MethodInvocation ||
parent is NamedArgument ||
parent is PatternAssignment ||
parent is PatternVariableDeclaration ||
parent is PropertyAccess ||
parent is RecordLiteral ||
parent is RecordLiteralNamedField ||
parent is ReturnStatement ||
parent is SetOrMapLiteral ||
parent is SwitchExpression ||
parent is SwitchExpressionCase ||
parent is SwitchStatement ||
parent is ThrowExpression ||
parent is VariableDeclaration ||
parent is WhenClause ||
parent is WhileStatement ||
parent is YieldStatement;
}
}
extension on ElementAnnotation {
String? get useResultMessage {
if (element is GetterElement) {
return null;
}
return computeConstantValue()?.getField('message')?.toStringValue();
}
String? get useResultUnlessParameter {
return computeConstantValue()
?.getField('parameterDefined')
?.toStringValue();
}
}
extension on AstNode {
AstNode get nodeToAnnotate => switch (this) {
MethodInvocation node => node.methodName,
PropertyAccess node => node.propertyName,
FunctionExpressionInvocation node => node.function2.nodeToAnnotate,
_ => this,
};
}