Skip to content

Commit 93725ad

Browse files
srawlinsCommit Queue
authored andcommitted
Analyzer warnings: add support for @Deprecation.subclass
Work towards #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: Icbb956e88ed4325f96fbe54b7eea7fcc18a4ab57 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/443901 Commit-Queue: Samuel Rawlins <[email protected]> Reviewed-by: Konstantin Shcheglov <[email protected]>
1 parent 1e3a0fd commit 93725ad

File tree

14 files changed

+554
-49
lines changed

14 files changed

+554
-49
lines changed

pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3478,6 +3478,8 @@ WarningCode.DEPRECATED_MIXIN_FUNCTION:
34783478
The fix is to remove `Function` from where it's referenced.
34793479
WarningCode.DEPRECATED_NEW_IN_COMMENT_REFERENCE:
34803480
status: hasFix
3481+
WarningCode.DEPRECATED_SUBCLASS:
3482+
status: hasFix
34813483
WarningCode.DOC_DIRECTIVE_ARGUMENT_WRONG_FORMAT:
34823484
status: noFix
34833485
WarningCode.DOC_DIRECTIVE_HAS_EXTRA_ARGUMENTS:
@@ -3561,6 +3563,9 @@ WarningCode.INVALID_DEPRECATED_EXTEND_ANNOTATION:
35613563
WarningCode.INVALID_DEPRECATED_IMPLEMENT_ANNOTATION:
35623564
status: needsFix
35633565
notes: The fix is to remove the annotation.
3566+
WarningCode.INVALID_DEPRECATED_SUBCLASS_ANNOTATION:
3567+
status: needsFix
3568+
notes: The fix is to remove the annotation.
35643569
WarningCode.INVALID_EXPORT_OF_INTERNAL_ELEMENT:
35653570
status: needsFix
35663571
notes: |-

pkg/analysis_server/lib/src/services/correction/fix_internal.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,6 +1206,7 @@ final _builtInNonLintGenerators = <DiagnosticCode, List<ProducerGenerator>>{
12061206
WarningCode.DEPRECATED_NEW_IN_COMMENT_REFERENCE: [
12071207
RemoveDeprecatedNewInCommentReference.new,
12081208
],
1209+
WarningCode.DEPRECATED_SUBCLASS: [RemoveNameFromDeclarationClause.new],
12091210
WarningCode.DUPLICATE_HIDDEN_NAME: [RemoveNameFromCombinator.new],
12101211
WarningCode.DUPLICATE_IMPORT: [RemoveUnusedImport.new],
12111212
WarningCode.DUPLICATE_SHOWN_NAME: [RemoveNameFromCombinator.new],

pkg/analysis_server/test/src/services/correction/fix/remove_name_from_declaration_clause_test.dart

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ void main() {
1616
defineReflectiveTests(ExtendsNonClassTest);
1717
defineReflectiveTests(ExtendsTypeAliasExpandsToTypeParameterTest);
1818
defineReflectiveTests(ImplementsDeprecatedImplementTest);
19+
defineReflectiveTests(ImplementsDeprecatedSubclassTest);
1920
defineReflectiveTests(ImplementsDisallowedClassTest);
2021
defineReflectiveTests(ImplementsRepeatedTest);
2122
defineReflectiveTests(ImplementsSuperClassTest);
@@ -114,6 +115,42 @@ class B {}
114115
}
115116
}
116117

118+
@reflectiveTest
119+
class ImplementsDeprecatedSubclassTest extends FixProcessorTest {
120+
@override
121+
FixKind get kind => DartFixKind.REMOVE_NAME_FROM_DECLARATION_CLAUSE;
122+
123+
Future<void> test_deprecatedSubclass_withExtends() async {
124+
newFile('$testPackageLibPath/a.dart', '''
125+
@Deprecated.subclass()
126+
class A {}
127+
''');
128+
await resolveTestCode('''
129+
import 'a.dart';
130+
class B extends A {}
131+
''');
132+
await assertHasFix('''
133+
import 'a.dart';
134+
class B {}
135+
''');
136+
}
137+
138+
Future<void> test_deprecatedSubclass_withImplements() async {
139+
newFile('$testPackageLibPath/a.dart', '''
140+
@Deprecated.subclass()
141+
class A {}
142+
''');
143+
await resolveTestCode('''
144+
import 'a.dart';
145+
class B implements A {}
146+
''');
147+
await assertHasFix('''
148+
import 'a.dart';
149+
class B {}
150+
''');
151+
}
152+
}
153+
117154
@reflectiveTest
118155
class ImplementsDisallowedClassTest extends FixProcessorTest {
119156
@override

pkg/analyzer/lib/src/diagnostic/diagnostic_code_values.g.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,7 @@ const List<DiagnosticCode> diagnosticCodeValues = [
994994
WarningCode.DEPRECATED_IMPLEMENTS_FUNCTION,
995995
WarningCode.DEPRECATED_MIXIN_FUNCTION,
996996
WarningCode.DEPRECATED_NEW_IN_COMMENT_REFERENCE,
997+
WarningCode.DEPRECATED_SUBCLASS,
997998
WarningCode.DOC_DIRECTIVE_ARGUMENT_WRONG_FORMAT,
998999
WarningCode.DOC_DIRECTIVE_HAS_EXTRA_ARGUMENTS,
9991000
WarningCode.DOC_DIRECTIVE_HAS_UNEXPECTED_NAMED_ARGUMENT,
@@ -1026,6 +1027,7 @@ const List<DiagnosticCode> diagnosticCodeValues = [
10261027
WarningCode.INVALID_AWAIT_NOT_REQUIRED_ANNOTATION,
10271028
WarningCode.INVALID_DEPRECATED_EXTEND_ANNOTATION,
10281029
WarningCode.INVALID_DEPRECATED_IMPLEMENT_ANNOTATION,
1030+
WarningCode.INVALID_DEPRECATED_SUBCLASS_ANNOTATION,
10291031
WarningCode.INVALID_EXPORT_OF_INTERNAL_ELEMENT,
10301032
WarningCode.INVALID_EXPORT_OF_INTERNAL_ELEMENT_INDIRECTLY,
10311033
WarningCode.INVALID_FACTORY_METHOD_DECL,

pkg/analyzer/lib/src/error/annotation_verifier.dart

Lines changed: 54 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -135,29 +135,30 @@ class AnnotationVerifier {
135135
_checkDeprecatedImplement(node, node.parent);
136136
return;
137137
}
138+
139+
var isSubclass = value.getField('_isSubclass')?.toBoolValue() ?? false;
140+
if (isSubclass) {
141+
_checkDeprecatedSubclass(node, node.parent);
142+
return;
143+
}
138144
}
139145

140146
void _checkDeprecatedExtend(Annotation node, AstNode parent) {
147+
Element? declaredElement;
141148
if (parent
142149
case ClassDeclaration(:var declaredFragment) ||
143150
ClassTypeAlias(:var declaredFragment)) {
144-
var classElement = declaredFragment?.element;
145-
if (classElement == null) return;
146-
var hasGenerativeConstructor = classElement.constructors.any(
147-
(c) => c.isPublic && c.isGenerative,
148-
);
149-
if (classElement.isExtendableOutside && hasGenerativeConstructor) return;
151+
declaredElement = declaredFragment!.element;
152+
} else if (parent is GenericTypeAlias) {
153+
declaredElement = parent.type.type?.element;
150154
}
151155

152-
if (parent is GenericTypeAlias) {
153-
var classElement = parent.type.type?.element;
154-
if (classElement is ClassElement) {
155-
var hasGenerativeConstructor = classElement.constructors.any(
156-
(c) => c.isPublic && c.isGenerative,
157-
);
158-
if (classElement.isExtendableOutside && hasGenerativeConstructor) {
159-
return;
160-
}
156+
if (declaredElement is ClassElement) {
157+
var hasGenerativeConstructor = declaredElement.constructors.any(
158+
(c) => c.isPublic && c.isGenerative,
159+
);
160+
if (declaredElement.isExtendableOutside && hasGenerativeConstructor) {
161+
return;
161162
}
162163
}
163164

@@ -168,32 +169,55 @@ class AnnotationVerifier {
168169
}
169170

170171
void _checkDeprecatedImplement(Annotation node, AstNode parent) {
172+
Element? declaredElement;
171173
if (parent
172174
case ClassDeclaration(:var declaredFragment) ||
173175
ClassTypeAlias(:var declaredFragment)) {
174-
var classElement = declaredFragment?.element;
175-
if (classElement == null) return;
176-
if (classElement.isImplementableOutside) return;
176+
declaredElement = declaredFragment!.element;
177+
} else if (parent is MixinDeclaration) {
178+
declaredElement = parent.declaredFragment!.element;
179+
} else if (parent is GenericTypeAlias) {
180+
declaredElement = parent.type.type?.element;
177181
}
178182

179-
if (parent is MixinDeclaration) {
180-
var mixinElement = parent.declaredFragment?.element;
181-
if (mixinElement == null) return;
182-
if (mixinElement.isImplementableOutside) return;
183+
if (declaredElement is ClassElement &&
184+
declaredElement.isImplementableOutside) {
185+
return;
186+
} else if (declaredElement is MixinElement &&
187+
declaredElement.isImplementableOutside) {
188+
return;
183189
}
184190

185-
if (parent is GenericTypeAlias) {
186-
var element = parent.type.type?.element;
187-
if (element is ClassElement && element.isImplementableOutside) {
188-
return;
189-
} else if (element is MixinElement && element.isImplementableOutside) {
190-
return;
191-
}
191+
_diagnosticReporter.atNode(
192+
node.name,
193+
WarningCode.INVALID_DEPRECATED_IMPLEMENT_ANNOTATION,
194+
);
195+
}
196+
197+
void _checkDeprecatedSubclass(Annotation node, AstNode parent) {
198+
Element? declaredElement;
199+
if (parent
200+
case ClassDeclaration(:var declaredFragment) ||
201+
ClassTypeAlias(:var declaredFragment)) {
202+
declaredElement = declaredFragment!.element;
203+
} else if (parent is MixinDeclaration) {
204+
declaredElement = parent.declaredFragment!.element;
205+
} else if (parent is GenericTypeAlias) {
206+
declaredElement = parent.type.type?.element;
207+
}
208+
209+
if (declaredElement is ClassElement &&
210+
(declaredElement.isImplementableOutside ||
211+
declaredElement.isExtendableOutside)) {
212+
return;
213+
} else if (declaredElement is MixinElement &&
214+
declaredElement.isImplementableOutside) {
215+
return;
192216
}
193217

194218
_diagnosticReporter.atNode(
195219
node.name,
196-
WarningCode.INVALID_DEPRECATED_IMPLEMENT_ANNOTATION,
220+
WarningCode.INVALID_DEPRECATED_SUBCLASS_ANNOTATION,
197221
);
198222
}
199223

pkg/analyzer/lib/src/error/best_practices_verifier.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,7 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
681681
_enclosingClass = element;
682682
_invalidAccessVerifier._enclosingClass = _enclosingClass;
683683

684+
_deprecatedFunctionalityVerifier.mixinDeclaration(node);
684685
_deprecatedMemberUseVerifier.pushInDeprecatedValue(element.isUseDeprecated);
685686

686687
try {

pkg/analyzer/lib/src/error/codes.g.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6423,6 +6423,16 @@ class WarningCode extends DiagnosticCode {
64236423
hasPublishedDocs: true,
64246424
);
64256425

6426+
/// Parameters:
6427+
/// 0: the name of the member
6428+
static const WarningCode DEPRECATED_SUBCLASS = WarningCode(
6429+
'DEPRECATED_SUBCLASS',
6430+
"Subclassing '{0}' is deprecated.",
6431+
correctionMessage:
6432+
"Try removing the 'extends' clause, or removing '{0}' from the "
6433+
"'implements' clause.",
6434+
);
6435+
64266436
/// Parameters:
64276437
/// 0: the name of the doc directive argument
64286438
/// 1: the expected format
@@ -6734,6 +6744,14 @@ class WarningCode extends DiagnosticCode {
67346744
"Try removing the '@Deprecated.implement' annotation.",
67356745
);
67366746

6747+
/// No parameters.
6748+
static const WarningCode INVALID_DEPRECATED_SUBCLASS_ANNOTATION = WarningCode(
6749+
'INVALID_DEPRECATED_SUBCLASS_ANNOTATION',
6750+
"The annotation '@Deprecated.subclass' can only be applied to subclassable "
6751+
"classes and mixins.",
6752+
correctionMessage: "Try removing the '@Deprecated.subclass' annotation.",
6753+
);
6754+
67376755
/// Parameters:
67386756
/// 0: the name of the element
67396757
static const WarningCode INVALID_EXPORT_OF_INTERNAL_ELEMENT = WarningCode(

pkg/analyzer/lib/src/error/deprecated_functionality_verifier.dart

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,24 @@ class DeprecatedFunctionalityVerifier {
1919

2020
void classDeclaration(ClassDeclaration node) {
2121
_checkForDeprecatedExtend(node.extendsClause?.superclass);
22-
_checkForDeprecatedImplement(node.implementsClause);
22+
_checkForDeprecatedImplement(node.implementsClause?.interfaces);
23+
_checkForDeprecatedSubclass(node.withClause?.mixinTypes);
2324
}
2425

2526
void classTypeAlias(ClassTypeAlias node) {
2627
_checkForDeprecatedExtend(node.superclass);
27-
_checkForDeprecatedImplement(node.implementsClause);
28+
_checkForDeprecatedImplement(node.implementsClause?.interfaces);
2829
}
2930

3031
void enumDeclaration(EnumDeclaration node) {
31-
_checkForDeprecatedImplement(node.implementsClause);
32+
_checkForDeprecatedImplement(node.implementsClause?.interfaces);
33+
}
34+
35+
void mixinDeclaration(MixinDeclaration node) {
36+
_checkForDeprecatedImplement(node.implementsClause?.interfaces);
37+
// Not technically "implementing," but is similar enough for
38+
// `@Deprecated.implement` and `@Deprecated.subclass`.
39+
_checkForDeprecatedImplement(node.onClause?.superclassConstraints);
3240
}
3341

3442
void _checkForDeprecatedExtend(NamedType? node) {
@@ -43,24 +51,53 @@ class DeprecatedFunctionalityVerifier {
4351
WarningCode.DEPRECATED_EXTEND,
4452
arguments: [element.name!],
4553
);
54+
} else if (element.hasDeprecatedWithField('_isSubclass')) {
55+
_diagnosticReporter.atNode(
56+
node,
57+
WarningCode.DEPRECATED_SUBCLASS,
58+
arguments: [element.name!],
59+
);
4660
}
4761
}
4862
}
4963

50-
void _checkForDeprecatedImplement(ImplementsClause? node) {
51-
if (node == null) return;
52-
var interfaces = node.interfaces;
53-
for (var interface in interfaces) {
54-
var element = interface.element;
64+
void _checkForDeprecatedImplement(List<NamedType>? namedTypes) {
65+
if (namedTypes == null) return;
66+
for (var namedType in namedTypes) {
67+
var element = namedType.element;
5568
if (element == null) continue;
56-
if (interface.type?.element is InterfaceElement) {
57-
if (element.library == _currentLibrary) continue;
69+
if (element.library == _currentLibrary) continue;
70+
if (namedType.type?.element is InterfaceElement) {
5871
if (element.hasDeprecatedWithField('_isImplement')) {
5972
_diagnosticReporter.atNode(
60-
interface,
73+
namedType,
6174
WarningCode.DEPRECATED_IMPLEMENT,
6275
arguments: [element.name!],
6376
);
77+
} else if (element.hasDeprecatedWithField('_isSubclass')) {
78+
_diagnosticReporter.atNode(
79+
namedType,
80+
WarningCode.DEPRECATED_SUBCLASS,
81+
arguments: [element.name!],
82+
);
83+
}
84+
}
85+
}
86+
}
87+
88+
void _checkForDeprecatedSubclass(List<NamedType>? namedTypes) {
89+
if (namedTypes == null) return;
90+
for (var namedType in namedTypes) {
91+
var element = namedType.element;
92+
if (element == null) continue;
93+
if (element.library == _currentLibrary) continue;
94+
if (namedType.type?.element is InterfaceElement) {
95+
if (element.hasDeprecatedWithField('_isSubclass')) {
96+
_diagnosticReporter.atNode(
97+
namedType,
98+
WarningCode.DEPRECATED_SUBCLASS,
99+
arguments: [element.name!],
100+
);
64101
}
65102
}
66103
}

pkg/analyzer/lib/src/test_utilities/mock_sdk.dart

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -317,18 +317,27 @@ class Deprecated extends Object {
317317
final bool _isUse;
318318
final bool _isImplement;
319319
final bool _isExtend;
320+
final bool _isSubclass;
320321
const Deprecated(this.message)
321322
: _isUse = true,
322323
_isImplement = false,
323-
_isExtend = false;
324+
_isExtend = false,
325+
_isSubclass = false;
324326
const Deprecated.implement([this.message = "next release"])
325327
: _isUse = false,
326328
_isImplement = true,
327-
_isExtend = false;
329+
_isExtend = false,
330+
_isSubclass = false;
328331
const Deprecated.extend([this.message = "next release"])
329332
: _isUse = false,
330333
_isImplement = false,
331-
_isExtend = true;
334+
_isExtend = true,
335+
_isSubclass = false;
336+
const Deprecated.subclass([this.message = "next release"])
337+
: _isUse = false,
338+
_isImplement = false,
339+
_isExtend = false,
340+
_isSubclass = true;
332341
}
333342
334343
class pragma {

0 commit comments

Comments
 (0)