blob: a61dea417b44ebdebbe8a623a0a65823be22066b [file] [log] [blame]
// Copyright (c) 2019, 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/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/element/element.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:collection/collection.dart';
/// Checks for missing arguments for required named parameters.
class RequiredParametersVerifier extends SimpleAstVisitor<void> {
final ErrorReporter _errorReporter;
RequiredParametersVerifier(this._errorReporter);
@override
void visitAnnotation(Annotation node) {
var element = node.element2;
var argumentList = node.arguments;
if (element is ConstructorElement && argumentList != null) {
var errorNode = node.constructorIdentifier ?? node.classIdentifier;
if (errorNode != null) {
_check(
parameters: element.formalParameters,
arguments: argumentList.arguments,
errorEntity: errorNode,
);
}
}
}
@override
void visitDotShorthandConstructorInvocation(
DotShorthandConstructorInvocation node,
) {
var constructorElement = node.constructorName.element;
if (constructorElement is ConstructorElement) {
_check(
parameters: constructorElement.formalParameters,
arguments: node.argumentList.arguments,
errorEntity: node.constructorName,
);
}
}
@override
void visitDotShorthandInvocation(DotShorthandInvocation node) {
_check(
parameters: _executableElement(node.memberName.element)?.formalParameters,
arguments: node.argumentList.arguments,
errorEntity: node.memberName,
);
}
@override
void visitEnumConstantDeclaration(EnumConstantDeclaration node) {
_check(
parameters: node.constructorElement2?.formalParameters,
arguments: node.arguments?.argumentList.arguments ?? <Expression>[],
errorEntity: node.name,
);
}
@override
void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
var type = node.staticInvokeType;
if (type is FunctionType) {
_check(
parameters: type.formalParameters,
arguments: node.argumentList.arguments,
errorEntity: node,
);
}
}
@override
void visitInstanceCreationExpression(InstanceCreationExpression node) {
_check(
parameters: node.constructorName.element?.formalParameters,
arguments: node.argumentList.arguments,
errorEntity: node.constructorName,
);
}
@override
void visitMethodInvocation(MethodInvocation node) {
if (node.methodName.name == MethodElement.CALL_METHOD_NAME) {
var targetType = node.realTarget?.staticType;
if (targetType is FunctionType) {
_check(
parameters: targetType.formalParameters,
arguments: node.argumentList.arguments,
errorEntity: node.argumentList,
);
return;
}
}
_check(
parameters: _executableElement(node.methodName.element)?.formalParameters,
arguments: node.argumentList.arguments,
errorEntity: node.methodName,
);
}
@override
void visitRedirectingConstructorInvocation(
RedirectingConstructorInvocation node,
) {
_check(
parameters: _executableElement(node.element)?.formalParameters,
arguments: node.argumentList.arguments,
errorEntity: node,
);
}
@override
void visitSuperConstructorInvocation(
SuperConstructorInvocation node, {
ConstructorElement? enclosingConstructor,
}) {
_check(
parameters: _executableElement(node.element)?.formalParameters,
enclosingConstructor: enclosingConstructor,
arguments: node.argumentList.arguments,
errorEntity: node,
);
}
void _check({
required List<FormalParameterElement>? parameters,
ConstructorElement? enclosingConstructor,
required List<Expression> arguments,
required SyntacticEntity errorEntity,
}) {
if (parameters == null) {
return;
}
for (FormalParameterElement parameter in parameters) {
if (parameter.isRequiredNamed) {
String parameterName = parameter.name3!;
if (!_containsNamedExpression(
enclosingConstructor,
arguments,
parameterName,
)) {
_containsNamedExpression(
enclosingConstructor,
arguments,
parameterName,
);
_errorReporter.atEntity(
errorEntity,
CompileTimeErrorCode.MISSING_REQUIRED_ARGUMENT,
arguments: [parameterName],
);
}
}
if (parameter.isOptionalNamed) {
var annotation = _requiredAnnotation(parameter);
if (annotation != null) {
String parameterName = parameter.name3!;
if (!_containsNamedExpression(
enclosingConstructor,
arguments,
parameterName,
)) {
var reason = annotation.getReason(strictCasts: true);
if (reason != null) {
_errorReporter.atEntity(
errorEntity,
WarningCode.MISSING_REQUIRED_PARAM_WITH_DETAILS,
arguments: [parameterName, reason],
);
} else {
_errorReporter.atEntity(
errorEntity,
WarningCode.MISSING_REQUIRED_PARAM,
arguments: [parameterName],
);
}
}
}
}
}
}
static bool _containsNamedExpression(
ConstructorElement? enclosingConstructor,
List<Expression> arguments,
String name,
) {
for (int i = arguments.length - 1; i >= 0; i--) {
Expression expression = arguments[i];
if (expression is NamedExpression) {
if (expression.name.label.name == name) {
return true;
}
}
}
if (enclosingConstructor != null) {
return enclosingConstructor.formalParameters.any(
(e) => e is SuperFormalParameterElement && e.isNamed && e.name3 == name,
);
}
return false;
}
static ExecutableElement? _executableElement(Element? element) {
if (element is ExecutableElement) {
return element;
} else {
return null;
}
}
static _RequiredAnnotation? _requiredAnnotation(
FormalParameterElement element,
) {
var annotation =
element.metadata.annotations.firstWhereOrNull((e) => e.isRequired)
as ElementAnnotationImpl?;
if (annotation != null) {
return _RequiredAnnotation(annotation);
}
if (element.baseElement.isRequiredNamed) {
return _RequiredAnnotation(annotation);
}
return null;
}
}
class _RequiredAnnotation {
/// The instance of `@required` annotation.
/// If `null`, then the parameter is `required` in null safety.
final ElementAnnotationImpl? annotation;
_RequiredAnnotation(this.annotation);
String? getReason({required bool strictCasts}) {
if (annotation == null) {
return null;
}
var constantValue = annotation!.computeConstantValue();
var value = constantValue?.getField('reason')?.toStringValue();
return (value == null || value.isEmpty) ? null : value;
}
}
/// The annotation should be a constructor invocation.
///
// TODO(scheglov): This is not ideal.
// Ideally when resolving an annotation we should restructure it into
// specific components - an import prefix, top-level declaration, getter,
// constructor, etc. So that later in the analyzer, or in clients, we
// don't have to identify it again and again.
extension _InstantiatedAnnotation on Annotation {
SimpleIdentifier? get classIdentifier {
assert(arguments != null);
var name = this.name;
if (name is SimpleIdentifier) {
return _ifClassElement(name);
} else if (name is PrefixedIdentifier) {
return _ifClassElement(name.identifier);
}
return null;
}
SimpleIdentifier? get constructorIdentifier {
assert(arguments != null);
var constructorName = _ifConstructorElement(this.constructorName);
if (constructorName != null) {
return constructorName;
}
var name = this.name;
if (name is SimpleIdentifier) {
return _ifConstructorElement(name);
} else if (name is PrefixedIdentifier) {
return _ifConstructorElement(name.identifier);
}
return null;
}
static SimpleIdentifier? _ifClassElement(SimpleIdentifier? node) {
return node?.element is InterfaceElement ? node : null;
}
static SimpleIdentifier? _ifConstructorElement(SimpleIdentifier? node) {
return node?.element is ConstructorElement ? node : null;
}
}