blob: c4bdd05df7dba75df5867f33cd38aa1d698f1848 [file] [log] [blame]
// Copyright (c) 2025, 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/syntactic_entity.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/source/source_range.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/error/super_formal_parameters_verifier.dart';
class DeprecatedFunctionalityVerifier {
final DiagnosticReporter _diagnosticReporter;
final LibraryElement _currentLibrary;
DeprecatedFunctionalityVerifier(
this._diagnosticReporter,
this._currentLibrary,
);
void classDeclaration(ClassDeclaration node) {
_checkForDeprecatedExtend(node.extendsClause?.superclass);
_checkForDeprecatedImplement(node.implementsClause?.interfaces);
_checkForDeprecatedMixin(node.withClause);
_checkForDeprecatedSubclass(node.withClause?.mixinTypes);
}
void classTypeAlias(ClassTypeAlias node) {
_checkForDeprecatedExtend(node.superclass);
_checkForDeprecatedImplement(node.implementsClause?.interfaces);
_checkForDeprecatedMixin(node.withClause);
}
void constructorDeclaration(ConstructorDeclaration node) {
_checkForDeprecatedOptionalSuperParameters(node);
// Check redirectiong constructor invocations in the initializer list.
for (var redirectingConstructorInvocation
in node.initializers.whereType<RedirectingConstructorInvocation>()) {
var element = redirectingConstructorInvocation.element;
if (element is! ConstructorElement) return;
_checkForDeprecatedOptional(
element: element,
argumentList: redirectingConstructorInvocation.argumentList,
errorEntity:
redirectingConstructorInvocation.constructorName ??
redirectingConstructorInvocation.thisKeyword,
);
}
// TODO(srawlins): Detect omitted parameters in a redirecting factory
// constructor.
}
void constructorName(ConstructorName node) {
var interfaceElement = node.type.element;
if (interfaceElement is! InterfaceElement) return;
_checkForDeprecatedInstantiate(element: interfaceElement, errorNode: node);
}
void dotShorthandConstructorInvocation(
DotShorthandConstructorInvocation node,
) {
var element = node.element;
if (element is! ConstructorElement) return;
_checkForDeprecatedOptional(
element: element,
argumentList: node.argumentList,
errorEntity: node.constructorName,
);
_checkForDeprecatedInstantiate(
element: element.enclosingElement,
errorNode: node.constructorName,
);
}
void dotShorthandInvocation(DotShorthandInvocation node) {
var element = node.memberName.element;
if (element is! ExecutableElement) return;
_checkForDeprecatedOptional(
element: element,
argumentList: node.argumentList,
errorEntity: node.memberName,
);
}
void enumDeclaration(EnumDeclaration node) {
_checkForDeprecatedImplement(node.implementsClause?.interfaces);
_checkForDeprecatedMixin(node.withClause);
}
void instanceCreationExpression(InstanceCreationExpression node) {
var constructor = node.constructorName.element;
if (constructor == null) return;
_checkForDeprecatedOptional(
element: constructor,
argumentList: node.argumentList,
errorEntity: node.constructorName,
);
var interfaceElement = node.constructorName.type.element;
if (interfaceElement is! InterfaceElement) return;
_checkForDeprecatedInstantiate(
element: interfaceElement,
errorNode: node.constructorName,
);
}
void methodInvocation(MethodInvocation node) {
var method = node.methodName.element;
if (method is! ExecutableElement) return;
if (method is LocalFunctionElement) return;
_checkForDeprecatedOptional(
element: method,
argumentList: node.argumentList,
errorEntity: node.methodName,
);
}
void mixinDeclaration(MixinDeclaration node) {
_checkForDeprecatedImplement(node.implementsClause?.interfaces);
// Not technically "implementing," but is similar enough for
// `@Deprecated.implement` and `@Deprecated.subclass`.
_checkForDeprecatedImplement(node.onClause?.superclassConstraints);
}
void _checkForDeprecatedExtend(NamedType? node) {
if (node == null) return;
var element = node.element;
if (element == null) return;
if (node.type?.element is InterfaceElement) {
if (element.library == _currentLibrary) return;
if (element.isDeprecatedWithKind('extend')) {
_diagnosticReporter.atNode(
node,
WarningCode.deprecatedExtend,
arguments: [element.name!],
);
} else if (element.isDeprecatedWithKind('subclass')) {
_diagnosticReporter.atNode(
node,
WarningCode.deprecatedSubclass,
arguments: [element.name!],
);
}
}
}
void _checkForDeprecatedImplement(List<NamedType>? namedTypes) {
if (namedTypes == null) return;
for (var namedType in namedTypes) {
var element = namedType.element;
if (element == null) continue;
if (element.library == _currentLibrary) continue;
if (namedType.type?.element is InterfaceElement) {
if (element.isDeprecatedWithKind('implement')) {
_diagnosticReporter.atNode(
namedType,
WarningCode.deprecatedImplement,
arguments: [element.name!],
);
} else if (element.isDeprecatedWithKind('subclass')) {
_diagnosticReporter.atNode(
namedType,
WarningCode.deprecatedSubclass,
arguments: [element.name!],
);
}
}
}
}
void _checkForDeprecatedInstantiate({
required InterfaceElement element,
required AstNode errorNode,
}) {
if (element.isDeprecatedWithKind('instantiate')) {
_diagnosticReporter.atNode(
errorNode,
WarningCode.deprecatedInstantiate,
arguments: [element.name!],
);
}
}
void _checkForDeprecatedMixin(WithClause? node) {
if (node == null) return;
for (var mixin in node.mixinTypes) {
var element = mixin.type?.element;
if (element is! InterfaceElement) continue;
if (element.library == _currentLibrary) continue;
if (element.isDeprecatedWithKind('mixin')) {
_diagnosticReporter.atNode(
mixin,
WarningCode.deprecatedMixin,
arguments: [element.name!],
);
}
}
}
void _checkForDeprecatedOptional({
required ExecutableElement element,
required ArgumentList argumentList,
required SyntacticEntity errorEntity,
}) {
var omittedParameters = element.formalParameters.toList();
for (var argument in argumentList.arguments) {
var parameter = argument.correspondingParameter;
if (parameter == null) continue;
omittedParameters.remove(parameter);
}
for (var parameter in omittedParameters) {
if (parameter.isDeprecatedWithKind('optional')) {
_diagnosticReporter.atEntity(
errorEntity,
WarningCode.deprecatedOptional,
arguments: [parameter.name ?? '<unknown>'],
);
}
}
}
void _checkForDeprecatedOptionalSuperParameters(ConstructorDeclaration node) {
var superConstructorInvocations = node.initializers
.whereType<SuperConstructorInvocation>();
if (superConstructorInvocations.length > 1) {
// Error reported elsewhere.
return;
}
var VerifySuperFormalParametersResult(
:positionalArgumentCount,
:namedArgumentNames,
) = verifySuperFormalParameters(
constructor: node,
diagnosticReporter: _diagnosticReporter,
);
ConstructorElement superConstructor;
List<Expression> superConstructorArguments;
int errorOffset;
int errorLength;
if (superConstructorInvocations.isEmpty) {
// The unnamed super-constructor will be invoked; report a warning for
// each `@Deprecated.optional` parameter in that constructor without a
// matching super-parameter.
if (node.declaredFragment!.element.superConstructor
case var unnamedSuperConstructor?) {
superConstructor = unnamedSuperConstructor;
} else {
// Error reported elsewhere.
return;
}
superConstructorArguments = [];
SourceRange(offset: errorOffset, length: errorLength) = node.errorRange;
} else {
// Arguments may be passed to the super constructor _either_ in
// `superConstructorInvocation` or via super-parameters.
var superConstructorInvocation = superConstructorInvocations.single;
if (superConstructorInvocation.element
case var superInvocationConstructor?) {
superConstructor = superInvocationConstructor;
} else {
// Error reported elsewhere.
return;
}
superConstructorArguments =
superConstructorInvocation.argumentList.arguments;
var errorEntity =
superConstructorInvocation.constructorName ??
superConstructorInvocation.superKeyword;
errorOffset = errorEntity.offset;
errorLength = errorEntity.length;
}
var namedSuperConstructorArgumentNames = superConstructorArguments
.whereType<NamedExpression>()
.map((a) => a.name.label.name)
.toList();
var positionalSuperConstructorArgumentCount = superConstructorArguments
.where((a) => a is! NamedExpression)
.length;
var superConstructorPositionalParameterCount = 0;
for (var parameter in superConstructor.formalParameters) {
if (parameter.isPositional) {
superConstructorPositionalParameterCount++;
}
if (!parameter.isOptional) continue;
if (!parameter.isDeprecatedWithKind('optional')) continue;
if (parameter.isPositional) {
if (superConstructorPositionalParameterCount <=
positionalArgumentCount + positionalSuperConstructorArgumentCount) {
continue;
}
} else {
if (namedArgumentNames.contains(parameter.name)) continue;
if (namedSuperConstructorArgumentNames.contains(parameter.name)) {
continue;
}
}
_diagnosticReporter.atOffset(
offset: errorOffset,
length: errorLength,
diagnosticCode: WarningCode.deprecatedOptional,
arguments: [parameter.name ?? '<unknown>'],
);
}
}
void _checkForDeprecatedSubclass(List<NamedType>? namedTypes) {
if (namedTypes == null) return;
for (var namedType in namedTypes) {
var element = namedType.element;
if (element == null) continue;
if (element.library == _currentLibrary) continue;
if (namedType.type?.element is InterfaceElement) {
if (element.isDeprecatedWithKind('subclass')) {
_diagnosticReporter.atNode(
namedType,
WarningCode.deprecatedSubclass,
arguments: [element.name!],
);
}
}
}
}
}