blob: 5fb2cc8c15e813dfc57cbdc4d84ef5915e446461 [file] [log] [blame]
// Copyright (c) 2024, 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/syntactic_entity.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/error/codes.dart';
/// Checks if the arguments for a parameter annotated with `@mustBeConst` are
/// actually constant.
class ConstArgumentsVerifier extends SimpleAstVisitor<void> {
final ErrorReporter _errorReporter;
ConstArgumentsVerifier(this._errorReporter);
@override
void visitAssignmentExpression(AssignmentExpression node) {
if (node.operator.type == TokenType.EQ) {
_check(arguments: [node.rightHandSide], errorNode: node.operator);
} else if (node
.rightHandSide
.correspondingParameter
?.metadata2
.hasMustBeConst ??
false) {
// If the operator is not `=`, then the argument cannot be const, as it
// depends on the value of the left hand side.
_errorReporter.atNode(
node.rightHandSide,
WarningCode.NON_CONST_ARGUMENT_FOR_CONST_PARAMETER,
arguments: [node.rightHandSide],
);
}
}
@override
void visitBinaryExpression(BinaryExpression node) {
_check(arguments: [node.rightOperand], errorNode: node.operator);
}
@override
void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
if (node.staticInvokeType is FunctionType) {
_check(arguments: node.argumentList.arguments, errorNode: node);
}
}
@override
void visitIndexExpression(IndexExpression node) {
_check(arguments: [node.index], errorNode: node.leftBracket);
}
@override
void visitInstanceCreationExpression(InstanceCreationExpression node) {
if (node.inConstantContext) return;
_check(
arguments: node.argumentList.arguments,
errorNode: node.constructorName,
);
}
@override
void visitMethodInvocation(MethodInvocation node) {
_check(arguments: node.argumentList.arguments, errorNode: node.methodName);
}
@override
void visitRedirectingConstructorInvocation(
RedirectingConstructorInvocation node,
) {
_check(
arguments: node.argumentList.arguments,
errorNode: node.constructorName ?? node.thisKeyword,
);
}
@override
void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
_check(
arguments: node.argumentList.arguments,
errorNode: node.constructorName ?? node.superKeyword,
);
}
void _check({
required List<Expression> arguments,
required SyntacticEntity errorNode,
}) {
for (var argument in arguments) {
var parameter = argument.correspondingParameter;
if (parameter == null) {
continue;
}
var parameterName = parameter.name3;
if (parameterName == null) {
continue;
}
if (parameter.metadata2.hasMustBeConst) {
Expression resolvedArgument;
if (parameter.isNamed) {
resolvedArgument = (argument as NamedExpression).expression;
} else {
resolvedArgument = argument;
}
if (!_isConst(resolvedArgument)) {
_errorReporter.atNode(
argument,
WarningCode.NON_CONST_ARGUMENT_FOR_CONST_PARAMETER,
arguments: [parameterName],
);
}
}
}
}
bool _isConst(Expression expression) {
if (expression.inConstantContext) {
return true;
} else if (expression is InstanceCreationExpression && expression.isConst) {
return true;
} else if (expression is Literal) {
return switch (expression) {
BooleanLiteral() => true,
DoubleLiteral() => true,
IntegerLiteral() => true,
NullLiteral() => true,
SimpleStringLiteral() => true,
AdjacentStrings() => true,
SymbolLiteral() => true,
RecordLiteral() => expression.isConst,
TypedLiteral() => expression.isConst,
// TODO(mosum): Expand the logic to check if the individual interpolation elements are const.
StringInterpolation() => false,
};
} else if (expression is Identifier) {
var element = expression.element;
switch (element) {
case GetterElement(variable3: var variable?):
return variable.isConst;
case VariableElement():
return element.isConst;
}
}
return false;
}
}