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:
committed by
Commit Queue
parent
ced0ef4946
commit
4800d2c19b
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user