blob: cd42097aec7e6f917035eb449260ce82ae18abb8 [file]
// 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/src/dart/element/element.dart';
import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag;
import 'package:analyzer/src/error/listener.dart';
/// Checks for missing arguments for required named parameters.
class RequiredParametersVerifier extends SimpleAstVisitor2<void> {
final DiagnosticReporter _errorReporter;
RequiredParametersVerifier(this._errorReporter);
void verifyNamedFunctionInvocation(NamedFunctionInvocation node) {
if (node.resolution case StaticInvocationResolution(:var invokeType)) {
_check(
parameters: invokeType.formalParameters,
arguments: node.argumentList.arguments2,
errorEntity: node.name,
);
}
}
@override
void visitCallInvocation(CallInvocation node) {
if (node.resolution case StaticInvocationResolution(:var invokeType)) {
_check(
parameters: invokeType.formalParameters,
arguments: node.argumentList.arguments2,
errorEntity: node,
);
}
}
@override
void visitCascadeMethodInvocation(CascadeMethodInvocation node) {
verifyNamedFunctionInvocation(node);
}
@override
void visitConstructorInvocation(ConstructorInvocation node) {
_check(
parameters: node.constructorReference.element?.formalParameters,
arguments: node.argumentList.arguments2,
errorEntity: node.constructorReference,
);
}
@override
void visitDotShorthandConstructorInvocation2(
DotShorthandConstructorInvocation2 node,
) {
var constructorElement = node.element;
if (constructorElement != null) {
_check(
parameters: constructorElement.formalParameters,
arguments: node.argumentList.arguments2,
errorEntity: node.name,
);
}
}
@override
void visitDotShorthandMethodInvocation(DotShorthandMethodInvocation node) {
verifyNamedFunctionInvocation(node);
}
@override
void visitEnumConstantDeclaration(EnumConstantDeclaration node) {
_check(
parameters: node.constructorElement?.formalParameters,
arguments: node.arguments?.argumentList.arguments2 ?? <Argument>[],
errorEntity: node.name,
);
}
@override
void visitImportPrefixedFunctionInvocation(
ImportPrefixedFunctionInvocation node,
) {
verifyNamedFunctionInvocation(node);
}
@override
void visitReceiverMethodInvocation(ReceiverMethodInvocation node) {
verifyNamedFunctionInvocation(node);
}
@override
void visitRedirectingConstructorInvocation(
RedirectingConstructorInvocation node,
) {
_check(
parameters: _executableElement(node.element)?.formalParameters,
arguments: node.argumentList.arguments2,
errorEntity: node,
);
}
@override
void visitSuperConstructorInvocation(
SuperConstructorInvocation node, {
ConstructorElement? enclosingConstructor,
}) {
_check(
parameters: _executableElement(node.element)?.formalParameters,
enclosingConstructor: enclosingConstructor,
arguments: node.argumentList.arguments2,
errorEntity: node,
);
}
@override
void visitUnqualifiedFunctionInvocation(UnqualifiedFunctionInvocation node) {
verifyNamedFunctionInvocation(node);
}
void _check({
required List<FormalParameterElement>? parameters,
ConstructorElement? enclosingConstructor,
required List<Argument> arguments,
required SyntacticEntity errorEntity,
}) {
if (parameters == null) {
return;
}
for (FormalParameterElement parameter in parameters) {
if (parameter.isRequiredNamed) {
var parameterName = parameter.name;
if (parameterName == null) {
continue;
}
if (!_containsNamedExpression(
enclosingConstructor,
arguments,
parameterName,
)) {
_containsNamedExpression(
enclosingConstructor,
arguments,
parameterName,
);
_errorReporter.report(
diag.missingRequiredArgument
.withArguments(name: parameterName)
.at(errorEntity),
);
}
}
if (parameter.isOptionalNamed) {
var annotation = _requiredAnnotation(parameter);
if (annotation != null) {
String parameterName = parameter.name!;
if (!_containsNamedExpression(
enclosingConstructor,
arguments,
parameterName,
)) {
var reason = annotation.getReason(strictCasts: true);
if (reason != null) {
_errorReporter.report(
diag.missingRequiredParamWithDetails
.withArguments(name: parameterName, details: reason)
.at(errorEntity),
);
} else {
_errorReporter.report(
diag.missingRequiredParam
.withArguments(name: parameterName)
.at(errorEntity),
);
}
}
}
}
}
}
static bool _containsNamedExpression(
ConstructorElement? enclosingConstructor,
List<Argument> arguments,
String name,
) {
for (int i = arguments.length - 1; i >= 0; i--) {
var argument = arguments[i];
if (argument is NamedArgument) {
if (argument.name.lexeme == name) {
return true;
}
}
}
if (enclosingConstructor != null) {
return enclosingConstructor.formalParameters.any(
(e) => e is SuperFormalParameterElement && e.isNamed && e.name == name,
);
}
return false;
}
static ExecutableElement? _executableElement(Element? element) {
if (element is ExecutableElement) {
return element;
} else {
return null;
}
}
static _RequiredAnnotation? _requiredAnnotation(
FormalParameterElement element,
) {
ElementAnnotationImpl? annotation;
for (var a in element.metadata.annotations) {
if (a.isRequired) {
annotation = a as ElementAnnotationImpl;
break;
}
}
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;
}
}