From 4800d2c19bb0b2b98f20c2e36427d7368e7bcee4 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 2 Sep 2025 13:04:53 -0700 Subject: [PATCH] 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 Reviewed-by: Brian Wilkerson Commit-Queue: Samuel Rawlins --- .../services/correction/error_fix_status.yaml | 5 + .../diagnostic/diagnostic_code_values.g.dart | 2 + .../lib/src/error/annotation_verifier.dart | 14 +++ pkg/analyzer/lib/src/error/codes.g.dart | 31 +++++ .../deprecated_functionality_verifier.dart | 19 +++ .../lib/src/test_utilities/mock_sdk.dart | 4 +- pkg/analyzer/messages.yaml | 73 +++++++++++ .../diagnostics/deprecated_mixin_test.dart | 115 ++++++++++++++++++ ...alid_deprecated_mixin_annotation_test.dart | 44 +++++++ .../test/src/diagnostics/test_all.dart | 5 + 10 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 pkg/analyzer/test/src/diagnostics/deprecated_mixin_test.dart create mode 100644 pkg/analyzer/test/src/diagnostics/invalid_deprecated_mixin_annotation_test.dart diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml index c106f81d5b2..c40f03b324d 100644 --- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml +++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml @@ -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. diff --git a/pkg/analyzer/lib/src/diagnostic/diagnostic_code_values.g.dart b/pkg/analyzer/lib/src/diagnostic/diagnostic_code_values.g.dart index 419fb8c169a..ef6d89194b5 100644 --- a/pkg/analyzer/lib/src/diagnostic/diagnostic_code_values.g.dart +++ b/pkg/analyzer/lib/src/diagnostic/diagnostic_code_values.g.dart @@ -987,6 +987,7 @@ const List diagnosticCodeValues = [ WarningCode.deprecatedImplement, WarningCode.deprecatedImplementsFunction, WarningCode.deprecatedInstantiate, + WarningCode.deprecatedMixin, WarningCode.deprecatedMixinFunction, WarningCode.deprecatedNewInCommentReference, WarningCode.deprecatedSubclass, @@ -1023,6 +1024,7 @@ const List diagnosticCodeValues = [ WarningCode.invalidDeprecatedExtendAnnotation, WarningCode.invalidDeprecatedImplementAnnotation, WarningCode.invalidDeprecatedInstantiateAnnotation, + WarningCode.invalidDeprecatedMixinAnnotation, WarningCode.invalidDeprecatedSubclassAnnotation, WarningCode.invalidExportOfInternalElement, WarningCode.invalidExportOfInternalElementIndirectly, diff --git a/pkg/analyzer/lib/src/error/annotation_verifier.dart b/pkg/analyzer/lib/src/error/annotation_verifier.dart index ba910854d13..c1d288c3c8e 100644 --- a/pkg/analyzer/lib/src/error/annotation_verifier.dart +++ b/pkg/analyzer/lib/src/error/annotation_verifier.dart @@ -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 diff --git a/pkg/analyzer/lib/src/error/codes.g.dart b/pkg/analyzer/lib/src/error/codes.g.dart index 6badfea71ef..cbecfd17f21 100644 --- a/pkg/analyzer/lib/src/error/codes.g.dart +++ b/pkg/analyzer/lib/src/error/codes.g.dart @@ -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, }) { diff --git a/pkg/analyzer/lib/src/error/deprecated_functionality_verifier.dart b/pkg/analyzer/lib/src/error/deprecated_functionality_verifier.dart index fe76536c1db..c952c87ad6c 100644 --- a/pkg/analyzer/lib/src/error/deprecated_functionality_verifier.dart +++ b/pkg/analyzer/lib/src/error/deprecated_functionality_verifier.dart @@ -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? namedTypes) { if (namedTypes == null) return; for (var namedType in namedTypes) { diff --git a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart index b2c1a4ed4f5..0af38aedb38 100644 --- a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart +++ b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart @@ -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 { diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml index 20bca995341..e16f15a15ba 100644 --- a/pkg/analyzer/messages.yaml +++ b/pkg/analyzer/messages.yaml @@ -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." diff --git a/pkg/analyzer/test/src/diagnostics/deprecated_mixin_test.dart b/pkg/analyzer/test/src/diagnostics/deprecated_mixin_test.dart new file mode 100644 index 00000000000..53635879ecc --- /dev/null +++ b/pkg/analyzer/test/src/diagnostics/deprecated_mixin_test.dart @@ -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 {} +'''); + } +} diff --git a/pkg/analyzer/test/src/diagnostics/invalid_deprecated_mixin_annotation_test.dart b/pkg/analyzer/test/src/diagnostics/invalid_deprecated_mixin_annotation_test.dart new file mode 100644 index 00000000000..f9a9c1ca310 --- /dev/null +++ b/pkg/analyzer/test/src/diagnostics/invalid_deprecated_mixin_annotation_test.dart @@ -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)], + ); + } +} diff --git a/pkg/analyzer/test/src/diagnostics/test_all.dart b/pkg/analyzer/test/src/diagnostics/test_all.dart index ee966087c2a..93278c4b58a 100644 --- a/pkg/analyzer/test/src/diagnostics/test_all.dart +++ b/pkg/analyzer/test/src/diagnostics/test_all.dart @@ -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();