Analyzer warnings: add support for @Deprecation.mixin

Work towards https://github.com/dart-lang/sdk/issues/60504

* The annotation causes certain usage to generate a warning (+ tests).
* Possible fixes for this new warning are offered (+ tests).
* Placing the annotation on an invalid element generates a different
  warning (+ tests).

Change-Id: I70dc502aa7c26feab9b8e3de8bb7b59bdce8fb66
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/448220
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
This commit is contained in:
Sam Rawlins
2025-09-02 13:04:53 -07:00
committed by Commit Queue
parent ced0ef4946
commit 4800d2c19b
10 changed files with 311 additions and 1 deletions
@@ -3485,6 +3485,8 @@ WarningCode.DEPRECATED_INSTANTIATE:
notes: |-
The only fix would be to delete the whole instance creation expression, or
follow the directions in the deprecation message.
WarningCode.DEPRECATED_MIXIN:
status: needsFix
WarningCode.DEPRECATED_MIXIN_FUNCTION:
status: needsFix
notes: |-
@@ -3579,6 +3581,9 @@ WarningCode.INVALID_DEPRECATED_IMPLEMENT_ANNOTATION:
WarningCode.INVALID_DEPRECATED_INSTANTIATE_ANNOTATION:
status: needsFix
notes: The fix is to remove the annotation.
WarningCode.INVALID_DEPRECATED_MIXIN_ANNOTATION:
status: needsFix
notes: The fix is to remove the annotation.
WarningCode.INVALID_DEPRECATED_SUBCLASS_ANNOTATION:
status: needsFix
notes: The fix is to remove the annotation.
@@ -987,6 +987,7 @@ const List<DiagnosticCode> diagnosticCodeValues = [
WarningCode.deprecatedImplement,
WarningCode.deprecatedImplementsFunction,
WarningCode.deprecatedInstantiate,
WarningCode.deprecatedMixin,
WarningCode.deprecatedMixinFunction,
WarningCode.deprecatedNewInCommentReference,
WarningCode.deprecatedSubclass,
@@ -1023,6 +1024,7 @@ const List<DiagnosticCode> diagnosticCodeValues = [
WarningCode.invalidDeprecatedExtendAnnotation,
WarningCode.invalidDeprecatedImplementAnnotation,
WarningCode.invalidDeprecatedInstantiateAnnotation,
WarningCode.invalidDeprecatedMixinAnnotation,
WarningCode.invalidDeprecatedSubclassAnnotation,
WarningCode.invalidExportOfInternalElement,
WarningCode.invalidExportOfInternalElementIndirectly,
@@ -136,6 +136,11 @@ class AnnotationVerifier {
return;
}
if (kind == 'mixin') {
_checkDeprecatedMixin(node, node.parent);
return;
}
if (kind == 'subclass') {
_checkDeprecatedSubclass(node, node.parent);
return;
@@ -219,6 +224,15 @@ class AnnotationVerifier {
);
}
void _checkDeprecatedMixin(Annotation node, AstNode parent) {
if (parent is ClassDeclaration && parent.mixinKeyword != null) return;
_diagnosticReporter.atNode(
node.name,
WarningCode.invalidDeprecatedMixinAnnotation,
);
}
void _checkDeprecatedSubclass(Annotation node, AstNode parent) {
Element? declaredElement;
if (parent
+31
View File
@@ -10872,6 +10872,19 @@ class WarningCode extends DiagnosticCodeWithExpectedTypes {
expectedTypes: [ExpectedType.object],
);
/// Parameters:
/// Object typeName: the name of the type
static const WarningTemplate<
LocatableDiagnostic Function({required Object typeName})
>
deprecatedMixin = WarningTemplate(
'DEPRECATED_MIXIN',
"Mixing in '{0}' is deprecated.",
correctionMessage: "Try removing '{0}' from the 'with' clause.",
withArguments: _withArgumentsDeprecatedMixin,
expectedTypes: [ExpectedType.object],
);
/// No parameters.
static const WarningWithoutArguments deprecatedMixinFunction =
WarningWithoutArguments(
@@ -11371,6 +11384,18 @@ class WarningCode extends DiagnosticCodeWithExpectedTypes {
expectedTypes: [],
);
/// This warning is generated anywhere where `@Deprecated.mixin` annotates
/// something other than a mixin class.
///
/// No parameters.
static const WarningWithoutArguments invalidDeprecatedMixinAnnotation =
WarningWithoutArguments(
'INVALID_DEPRECATED_MIXIN_ANNOTATION',
"The annotation '@Deprecated.mixin' can only be applied to classes.",
correctionMessage: "Try removing the '@Deprecated.mixin' annotation.",
expectedTypes: [],
);
/// No parameters.
static const WarningWithoutArguments
invalidDeprecatedSubclassAnnotation = WarningWithoutArguments(
@@ -12958,6 +12983,12 @@ class WarningCode extends DiagnosticCodeWithExpectedTypes {
return LocatableDiagnosticImpl(deprecatedInstantiate, [p0]);
}
static LocatableDiagnostic _withArgumentsDeprecatedMixin({
required Object typeName,
}) {
return LocatableDiagnosticImpl(deprecatedMixin, [typeName]);
}
static LocatableDiagnostic _withArgumentsDeprecatedSubclass({
required Object p0,
}) {
@@ -21,12 +21,14 @@ class DeprecatedFunctionalityVerifier {
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 constructorName(ConstructorName node) {
@@ -43,6 +45,7 @@ class DeprecatedFunctionalityVerifier {
void enumDeclaration(EnumDeclaration node) {
_checkForDeprecatedImplement(node.implementsClause?.interfaces);
_checkForDeprecatedMixin(node.withClause);
}
void mixinDeclaration(MixinDeclaration node) {
@@ -98,6 +101,22 @@ class DeprecatedFunctionalityVerifier {
}
}
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 _checkForDeprecatedSubclass(List<NamedType>? namedTypes) {
if (namedTypes == null) return;
for (var namedType in namedTypes) {
@@ -325,10 +325,12 @@ class Deprecated extends Object {
: _kind = _DeprecationKind.subclass;
const Deprecated.instantiate([this.message = "next release"])
: _kind = _DeprecationKind.instantiate;
const Deprecated.mixin([this.message = "next release"])
: _kind = _DeprecationKind.mixin;
}
enum _DeprecationKind {
use, implement, extend, subclass, instantiate;
use, implement, extend, subclass, instantiate, mixin;
}
class pragma {
+73
View File
@@ -24088,6 +24088,48 @@ WarningCode:
#### Common fixes
Follow any directions found in the `Deprecation.instantiate` annotation.
DEPRECATED_MIXIN:
parameters:
Object typeName: the name of the type
problemMessage: "Mixing in '#typeName' is deprecated."
correctionMessage: Try removing '#typeName' from the 'with' clause.
hasPublishedDocs: false
documentation: |-
#### Description
The analyzer produces this diagnostic when a mixin class annotated with
`@Deprecated.mixin` is used in the `with` clause of a class or enum
declaration. This annotation indicates that the ability for classes
to mixin the annotated mixin class is deprecated, and will soon be
removed, perhaps by removing the `mixin` class modifier.
#### Example
If the library `p` defines a class annotated with `@Deprecated.mixin`:
```dart
%uri="package:p/p.dart"
@Deprecated.mixin()
mixin class C {}
```
Then, the following code, when in a library other than `p`, produces this
diagnostic:
```dart
import 'package:p/p.dart';
class D with [!C!] {}
```
#### Common fixes
Follow any directions found in the `Deprecation.mixin` annotation, or
just remove the mixin class name from the `with` clause.
```dart
class D {}
```
DEPRECATED_MIXIN_FUNCTION:
parameters: none
sharedName: DEPRECATED_SUBTYPE_OF_FUNCTION
@@ -25093,6 +25135,37 @@ WarningCode:
```dart
sealed class C {}
```
INVALID_DEPRECATED_MIXIN_ANNOTATION:
parameters: none
problemMessage: "The annotation '@Deprecated.mixin' can only be applied to classes."
correctionMessage: Try removing the '@Deprecated.mixin' annotation.
hasPublishedDocs: false
comment: |-
This warning is generated anywhere where `@Deprecated.mixin` annotates
something other than a mixin class.
documentation: |-
#### Description
The analyzer produces this diagnostic when anything other than a
mixin class is annotated with Deprecated.mixin.
#### Example
The following code produces this diagnostic because the annotation is on a
non-mixin class:
```dart
@[!Deprecated.mixin!]()
class C {}
```
#### Common fixes
Remove the annotation:
```dart
class C {}
```
INVALID_DEPRECATED_SUBCLASS_ANNOTATION:
parameters: none
problemMessage: "The annotation '@Deprecated.subclass' can only be applied to subclassable classes and mixins."
@@ -0,0 +1,115 @@
// 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/src/error/codes.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../dart/resolution/context_collection_resolution.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(DeprecatedMixinTest);
});
}
@reflectiveTest
class DeprecatedMixinTest extends PubPackageResolutionTest {
test_annotatedClass_typedef() async {
newFile('$testPackageLibPath/foo.dart', r'''
@Deprecated.mixin()
mixin class Foo {}
typedef Foo2 = Foo;
''');
await assertErrorsInCode(
r'''
import 'foo.dart';
class Bar with Foo {}
''',
[error(WarningCode.deprecatedMixin, 34, 3)],
);
}
test_annotatedClassTypeAlias() async {
newFile('$testPackageLibPath/foo.dart', r'''
mixin M {}
@Deprecated.mixin()
mixin class Foo = Object with M;
''');
await assertErrorsInCode(
r'''
import 'foo.dart';
class Bar with Foo {}
''',
[error(WarningCode.deprecatedMixin, 34, 3)],
);
}
test_class() async {
newFile('$testPackageLibPath/foo.dart', r'''
@Deprecated.mixin()
mixin class Foo {}
''');
await assertErrorsInCode(
r'''
import 'foo.dart';
class Bar with Foo {}
''',
[error(WarningCode.deprecatedMixin, 34, 3)],
);
}
test_classTypeAlias() async {
newFile('$testPackageLibPath/foo.dart', r'''
@Deprecated.mixin()
mixin class Foo {}
''');
await assertErrorsInCode(
r'''
import 'foo.dart';
class Bar = Object with Foo;
''',
[error(WarningCode.deprecatedMixin, 43, 3)],
);
}
test_enum() async {
newFile('$testPackageLibPath/foo.dart', r'''
@Deprecated.mixin()
mixin class Foo {}
''');
await assertErrorsInCode(
r'''
import 'foo.dart';
enum Bar with Foo {
one, two;
}
''',
[error(WarningCode.deprecatedMixin, 33, 3)],
);
}
test_insideLibrary() async {
await assertNoErrorsInCode(r'''
@Deprecated.mixin()
mixin class Foo {}
class Bar with Foo {}
''');
}
test_noAnnotation() async {
newFile('$testPackageLibPath/foo.dart', r'''
mixin class Foo {}
''');
await assertNoErrorsInCode(r'''
import 'foo.dart';
class Bar with Foo {}
''');
}
}
@@ -0,0 +1,44 @@
// 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/src/error/codes.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../dart/resolution/context_collection_resolution.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(InvalidDeprecatedMixinAnnotationTest);
});
}
@reflectiveTest
class InvalidDeprecatedMixinAnnotationTest extends PubPackageResolutionTest {
test_class_mixin() async {
await assertNoErrorsInCode(r'''
@Deprecated.mixin()
mixin class C {}
''');
}
test_class_noMixin() async {
await assertErrorsInCode(
r'''
@Deprecated.mixin()
class C {}
''',
[error(WarningCode.invalidDeprecatedMixinAnnotation, 1, 16)],
);
}
test_mixin() async {
await assertErrorsInCode(
r'''
@Deprecated.mixin()
mixin M {}
''',
[error(WarningCode.invalidDeprecatedMixinAnnotation, 1, 16)],
);
}
}
@@ -188,6 +188,7 @@ import 'deprecated_implements_function_test.dart'
import 'deprecated_instantiate_test.dart' as deprecated_instantiate;
import 'deprecated_member_use_test.dart' as deprecated_member_use;
import 'deprecated_mixin_function_test.dart' as deprecated_mixin_function;
import 'deprecated_mixin_test.dart' as deprecated_mixin;
import 'deprecated_subclass_test.dart' as deprecated_subclass;
import 'doc_directive_argument_wrong_format_test.dart'
as doc_directive_argument_wrong_format;
@@ -444,6 +445,8 @@ import 'invalid_deprecated_implement_annotation_test.dart'
as invalid_deprecated_implement_annotation;
import 'invalid_deprecated_instantiate_annotation_test.dart'
as invalid_deprecated_instantiate_annotation;
import 'invalid_deprecated_mixin_annotation_test.dart'
as invalid_deprecated_mixin_annotation;
import 'invalid_deprecated_subclass_annotation_test.dart'
as invalid_deprecated_subclass_annotation;
import 'invalid_do_not_submit_test.dart' as invalid_do_not_submit;
@@ -1076,6 +1079,7 @@ main() {
deprecated_instantiate.main();
deprecated_member_use.main();
deprecated_mixin_function.main();
deprecated_mixin.main();
deprecated_subclass.main();
doc_directive_argument_wrong_format.main();
doc_directive_has_extra_arguments.main();
@@ -1234,6 +1238,7 @@ main() {
invalid_deprecated_extend_annotation.main();
invalid_deprecated_implement_annotation.main();
invalid_deprecated_instantiate_annotation.main();
invalid_deprecated_mixin_annotation.main();
invalid_deprecated_subclass_annotation.main();
invalid_do_not_submit.main();
invalid_exception_value.main();