blob: 3f20814cacdf5c50d73547348e665f147d61339d [file]
// 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/source/source_range.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag;
import 'package:analyzer/src/error/listener.dart';
import 'package:analyzer/src/error/super_formal_parameters_verifier.dart';
import 'package:analyzer/src/utilities/extensions/collection.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(
parameters: node.parameters,
initializers: node.initializers,
superConstructor: node.declaredFragment?.element.superConstructor,
errorRange: node.errorRange,
);
_checkForDeprecatedOptionalRedirectedParameters(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 primaryConstructorDeclaration(PrimaryConstructorDeclaration node) {
_checkForDeprecatedOptionalSuperParameters(
parameters: node.formalParameters,
initializers: node.body?.initializers ?? const [],
superConstructor: node.declaredFragment?.element.superConstructor,
errorRange: node.errorRange,
);
}
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.report(
diag.deprecatedExtend.withArguments(typeName: element.name!).at(node),
);
} else if (element.isDeprecatedWithKind('subclass')) {
_diagnosticReporter.report(
diag.deprecatedSubclass
.withArguments(typeName: element.name!)
.at(node),
);
}
}
}
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.report(
diag.deprecatedImplement
.withArguments(typeName: element.name!)
.at(namedType),
);
} else if (element.isDeprecatedWithKind('subclass')) {
_diagnosticReporter.report(
diag.deprecatedSubclass
.withArguments(typeName: element.name!)
.at(namedType),
);
}
}
}
}
void _checkForDeprecatedInstantiate({
required InterfaceElement element,
required AstNode errorNode,
}) {
if (element.isDeprecatedWithKind('instantiate')) {
_diagnosticReporter.report(
diag.deprecatedInstantiate
.withArguments(typeName: element.name!)
.at(errorNode),
);
}
}
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.report(
diag.deprecatedMixin.withArguments(typeName: element.name!).at(mixin),
);
}
}
}
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.report(
diag.deprecatedOptional
.withArguments(parameterName: parameter.name ?? '<unknown>')
.at(errorEntity),
);
}
}
}
void _checkForDeprecatedOptionalRedirectedParameters(
ConstructorDeclaration node,
) {
if (node.redirectedConstructor?.element case var redirectedConstructor?) {
var SourceRange(offset: errorOffset, length: errorLength) =
node.errorRange;
var positionalArgumentCount = node.parameters.parameters
.where((p) => p.isPositional)
.length;
var namedArgumentNames = node.parameters.parameters
.where((p) => p.isNamed)
.map((p) => p.name?.lexeme)
.nonNulls
.toList();
var redirectedConstructorPositionalParameterCount = 0;
for (var parameter in redirectedConstructor.formalParameters) {
if (parameter.isPositional) {
redirectedConstructorPositionalParameterCount++;
}
if (!parameter.isOptional) continue;
if (!parameter.isDeprecatedWithKind('optional')) continue;
if (parameter.isPositional) {
if (redirectedConstructorPositionalParameterCount <=
positionalArgumentCount) {
continue;
}
} else {
if (namedArgumentNames.contains(parameter.name)) continue;
}
_diagnosticReporter.report(
diag.deprecatedOptional
.withArguments(parameterName: parameter.name ?? '<unknown>')
.atOffset(offset: errorOffset, length: errorLength),
);
}
}
}
void _checkForDeprecatedOptionalSuperParameters({
required FormalParameterList parameters,
required List<ConstructorInitializer> initializers,
required ConstructorElement? superConstructor,
required SourceRange errorRange,
}) {
if (superConstructor == null) return;
var superConstructorInvocations = initializers
.whereType<SuperConstructorInvocation>();
if (superConstructorInvocations.length > 1) {
// Error reported elsewhere.
return;
}
var VerifySuperFormalParametersResult(
:positionalArgumentCount,
:namedArgumentNames,
) = verifySuperFormalParameters(
formalParameterList: parameters,
diagnosticReporter: _diagnosticReporter,
);
List<Argument> superConstructorArguments;
if (superConstructorInvocations.isEmpty) {
superConstructorArguments = [];
} else {
// Arguments may be passed to the super constructor _either_ in
// `superConstructorInvocation` or via super-parameters.
var superConstructorInvocation = superConstructorInvocations.single;
superConstructorArguments =
superConstructorInvocation.argumentList.arguments;
var errorEntity =
superConstructorInvocation.constructorName ??
superConstructorInvocation.superKeyword;
errorRange = errorEntity.sourceRange;
}
var namedSuperConstructorArgumentNames = superConstructorArguments
.whereType<NamedArgument>()
.map((a) => a.name.lexeme)
.toList();
var positionalSuperConstructorArgumentCount = superConstructorArguments
.whereNotType<NamedArgument>()
.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.report(
diag.deprecatedOptional
.withArguments(parameterName: parameter.name ?? '<unknown>')
.atSourceRange(errorRange),
);
}
}
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.report(
diag.deprecatedSubclass
.withArguments(typeName: element.name!)
.at(namedType),
);
}
}
}
}
}