[analyzer] Dot shorthands: Report extra deprecation and @literal warnings.

Update the BestPracticesVerifier to report warnings on deprecated member usage and calling a `@literal` constructor without const.

Added unit tests and updated the AST.

Bug: https://github.com/dart-lang/sdk/issues/59835
Change-Id: I15aff72c00e9bead386f83c3a6f1054ca8324dc9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/434881
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Kallen Tu <kallentu@google.com>
This commit is contained in:
Kallen Tu
2025-06-16 15:27:10 -07:00
committed by Commit Queue
parent ce32e04c45
commit 4431536776
5 changed files with 201 additions and 1 deletions
+21 -1
View File
@@ -6843,7 +6843,7 @@ final class DotShorthandConstructorInvocationImpl
DotShorthandConstructorInvocation {
@generated
@override
final Token? constKeyword;
Token? constKeyword;
@generated
@override
@@ -6875,6 +6875,26 @@ final class DotShorthandConstructorInvocationImpl
return period;
}
@override
bool get canBeConst {
var element = constructorName.element;
if (element is! ConstructorElementMixin2) return false;
if (!element.isConst) return false;
// Ensure that dependencies (e.g. default parameter values) are computed.
element.baseElement.computeConstantDependencies();
// Verify that the evaluation of the constructor would not produce an
// exception.
var oldKeyword = constKeyword;
try {
constKeyword = KeywordToken(Keyword.CONST, offset);
return !hasConstantVerifierError;
} finally {
constKeyword = oldKeyword;
}
}
@generated
@override
SimpleIdentifierImpl get constructorName => _constructorName;
@@ -325,6 +325,15 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
}
}
@override
void visitDotShorthandConstructorInvocation(
DotShorthandConstructorInvocation node,
) {
_deprecatedVerifier.dotShorthandConstructorInvocation(node);
_checkForLiteralConstructorUseInDotShorthand(node);
super.visitDotShorthandConstructorInvocation(node);
}
@override
void visitEnumDeclaration(EnumDeclaration node) {
_deprecatedVerifier.pushInDeprecatedValue(
@@ -1158,6 +1167,24 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
}
}
/// Report a warning if the dot shorthand constructor is marked with [literal]
/// and is not const.
///
/// See [WarningCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR].
void _checkForLiteralConstructorUseInDotShorthand(
DotShorthandConstructorInvocation node,
) {
var constructor = node.constructorName.element;
if (constructor is! ConstructorElement) return;
if (!node.isConst && constructor.metadata.hasLiteral && node.canBeConst) {
_diagnosticReporter.atNode(
node,
WarningCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR,
arguments: [constructor.displayName],
);
}
}
/// Check that the imported library does not define a loadLibrary function.
/// The import has already been determined to be deferred when this is called.
///
@@ -36,6 +36,12 @@ abstract class BaseDeprecatedMemberUseVerifier {
_checkForDeprecated(node.element, node);
}
void dotShorthandConstructorInvocation(
DotShorthandConstructorInvocation node,
) {
_invocationArguments(node.constructorName.element, node.argumentList);
}
void exportDirective(ExportDirective node) {
_checkForDeprecated(node.libraryExport?.exportedLibrary2, node);
}
@@ -430,6 +430,125 @@ int g(Object s) =>
);
}
test_dotShorthandConstructorInvocation_deprecatedClass_deprecatedConstructor() async {
newFile('$workspaceRootPath/aaa/lib/a.dart', r'''
@deprecated
class A {
@deprecated
A();
}
''');
await assertErrorsInCode(
r'''
import 'package:aaa/a.dart';
void f() {
A a = .new();
}
''',
[
error(HintCode.DEPRECATED_MEMBER_USE, 43, 1),
error(WarningCode.UNUSED_LOCAL_VARIABLE, 45, 1),
error(HintCode.DEPRECATED_MEMBER_USE, 50, 3),
],
);
}
test_dotShorthandConstructorInvocation_deprecatedClass_undeprecatedConstructor() async {
newFile('$workspaceRootPath/aaa/lib/a.dart', r'''
@deprecated
class A {
A();
}
''');
await assertErrorsInCode(
r'''
import 'package:aaa/a.dart';
void f() {
A a = .new();
}
''',
[
error(HintCode.DEPRECATED_MEMBER_USE, 43, 1),
error(WarningCode.UNUSED_LOCAL_VARIABLE, 45, 1),
],
);
}
test_dotShorthandConstructorInvocation_deprecatedClass_undeprecatedNamedConstructor() async {
newFile('$workspaceRootPath/aaa/lib/a.dart', r'''
@deprecated
class A {
A.a();
}
''');
await assertErrorsInCode(
r'''
import 'package:aaa/a.dart';
void f() {
A a = .a();
}
''',
[
error(HintCode.DEPRECATED_MEMBER_USE, 43, 1),
error(WarningCode.UNUSED_LOCAL_VARIABLE, 45, 1),
],
);
}
test_dotShorthandConstructorInvocation_namedConstructor() async {
await assertErrorsInCode2(
externalCode: r'''
class A {
@deprecated
A.named(int i) {}
}
''',
code: r'''
f() {
A a = .named(1);
}
''',
[
error(WarningCode.UNUSED_LOCAL_VARIABLE, 39, 1),
error(
HintCode.DEPRECATED_MEMBER_USE,
44,
5,
messageContains: ["'A.named' is deprecated and shouldn't be used."],
),
],
);
}
test_dotShorthandConstructorInvocation_undeprecatedClass_deprecatedConstructor() async {
newFile('$workspaceRootPath/aaa/lib/a.dart', r'''
class A {
@deprecated
A();
}
''');
await assertErrorsInCode(
r'''
import 'package:aaa/a.dart';
void f() {
A a = .new();
}
''',
[
error(WarningCode.UNUSED_LOCAL_VARIABLE, 45, 1),
error(HintCode.DEPRECATED_MEMBER_USE, 50, 3),
],
);
}
test_export() async {
newFile('$workspaceRootPath/aaa/lib/a.dart', r'''
@deprecated
@@ -64,6 +64,34 @@ E e = const E.zero();
''');
}
test_dotShorthand_namedConstructor() async {
await assertErrorsInCode(
r'''
import 'package:meta/meta.dart';
class A {
@literal
const A.named();
}
A a = .named();
''',
[error(WarningCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR, 81, 8)],
);
}
test_dotShorthand_unnamedConstructor() async {
await assertErrorsInCode(
r'''
import 'package:meta/meta.dart';
class A {
@literal
const A();
}
A a = .new();
''',
[error(WarningCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR, 75, 6)],
);
}
test_namedConstructor() async {
await assertErrorsInCode(
r'''