Skip to content

Commit 47ccb55

Browse files
srawlinsCommit Queue
authored andcommitted
Analyzer warnings: add support for @Deprecation.extend and .implement
Work towards #60504 For each annotation, we implement several features in the analyzer: * 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). * Possible fixes for the invalid annotation are offered (+ tests). Change-Id: I55f8663a4c0589bc8409dda879f28b072eca6197 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/442705 Reviewed-by: Konstantin Shcheglov <[email protected]> Commit-Queue: Samuel Rawlins <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]>
1 parent d346736 commit 47ccb55

19 files changed

+980
-106
lines changed

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3462,10 +3462,14 @@ WarningCode.DEPRECATED_EXPORT_USE:
34623462
status: hasFix
34633463
notes: |-
34643464
A data driven fix using `replacedBy`.
3465+
WarningCode.DEPRECATED_EXTEND:
3466+
status: hasFix
34653467
WarningCode.DEPRECATED_EXTENDS_FUNCTION:
34663468
status: needsFix
34673469
notes: |-
3468-
The fix is to remove `Function` from where it's referenced.
3470+
The fix is to remove the class from the implements list.
3471+
WarningCode.DEPRECATED_IMPLEMENT:
3472+
status: hasFix
34693473
WarningCode.DEPRECATED_IMPLEMENTS_FUNCTION:
34703474
status: hasFix
34713475
WarningCode.DEPRECATED_MIXIN_FUNCTION:
@@ -3551,6 +3555,12 @@ WarningCode.INVALID_ANNOTATION_TARGET:
35513555
status: hasFix
35523556
WarningCode.INVALID_AWAIT_NOT_REQUIRED_ANNOTATION:
35533557
status: needsFix
3558+
WarningCode.INVALID_DEPRECATED_EXTEND_ANNOTATION:
3559+
status: needsFix
3560+
notes: The fix is to remove the annotation.
3561+
WarningCode.INVALID_DEPRECATED_IMPLEMENT_ANNOTATION:
3562+
status: needsFix
3563+
notes: The fix is to remove the annotation.
35543564
WarningCode.INVALID_EXPORT_OF_INTERNAL_ELEMENT:
35553565
status: needsFix
35563566
notes: |-

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,6 +1197,8 @@ final _builtInNonLintGenerators = <DiagnosticCode, List<ProducerGenerator>>{
11971197
// a place where it can be reached (when possible).
11981198
RemoveDeadCode.new,
11991199
],
1200+
WarningCode.DEPRECATED_EXTEND: [RemoveExtendsClause.new],
1201+
WarningCode.DEPRECATED_IMPLEMENT: [RemoveNameFromDeclarationClause.new],
12001202
WarningCode.DEPRECATED_IMPLEMENTS_FUNCTION: [
12011203
RemoveNameFromDeclarationClause.new,
12021204
],

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,34 @@ class RemoveExtendsClauseTest extends FixProcessorTest {
4343
@override
4444
FixKind get kind => DartFixKind.REMOVE_EXTENDS_CLAUSE;
4545

46+
Future<void> test_deprecatedExtends() async {
47+
newFile('$testPackageLibPath/a.dart', '''
48+
@Deprecated.extend()
49+
class A {}
50+
''');
51+
await resolveTestCode('''
52+
import 'a.dart';
53+
class B extends A {}
54+
''');
55+
await assertHasFix('''
56+
import 'a.dart';
57+
class B {}
58+
''');
59+
}
60+
61+
Future<void> test_deprecatedExtends_classTypeAlias() async {
62+
newFile('$testPackageLibPath/a.dart', '''
63+
@Deprecated.extend()
64+
class A {}
65+
''');
66+
await resolveTestCode('''
67+
import 'a.dart';
68+
mixin M {}
69+
class B = A with M;
70+
''');
71+
await assertNoFix();
72+
}
73+
4674
Future<void> test_mixinClass_extends_class() async {
4775
await resolveTestCode('''
4876
class A {}

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ void main() {
1515
defineReflectiveTests(ExtendsDisallowedClassTest);
1616
defineReflectiveTests(ExtendsNonClassTest);
1717
defineReflectiveTests(ExtendsTypeAliasExpandsToTypeParameterTest);
18+
defineReflectiveTests(ImplementsDeprecatedImplementTest);
1819
defineReflectiveTests(ImplementsDisallowedClassTest);
1920
defineReflectiveTests(ImplementsRepeatedTest);
2021
defineReflectiveTests(ImplementsSuperClassTest);
@@ -92,6 +93,27 @@ class C {}
9293
}
9394
}
9495

96+
@reflectiveTest
97+
class ImplementsDeprecatedImplementTest extends FixProcessorTest {
98+
@override
99+
FixKind get kind => DartFixKind.REMOVE_NAME_FROM_DECLARATION_CLAUSE;
100+
101+
Future<void> test_deprecatedExtends() async {
102+
newFile('$testPackageLibPath/a.dart', '''
103+
@Deprecated.implement()
104+
class A {}
105+
''');
106+
await resolveTestCode('''
107+
import 'a.dart';
108+
class B implements A {}
109+
''');
110+
await assertHasFix('''
111+
import 'a.dart';
112+
class B {}
113+
''');
114+
}
115+
}
116+
95117
@reflectiveTest
96118
class ImplementsDisallowedClassTest extends FixProcessorTest {
97119
@override

pkg/analyzer/lib/src/dart/element/extensions.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,22 @@ extension Element2Extension on Element {
7474
return false;
7575
}
7676

77+
/// Whether the use of this element is deprecated.
78+
bool get isUseDeprecated {
79+
var element = this;
80+
81+
var metadata =
82+
(element is PropertyAccessorElement && element.isSynthetic)
83+
? element.variable.metadata
84+
: element.metadata;
85+
86+
var annotations = metadata.annotations.where((e) => e.isDeprecated);
87+
return annotations.any((annotation) {
88+
var value = annotation.computeConstantValue();
89+
return value?.getField('_isUse')?.toBoolValue() ?? true;
90+
});
91+
}
92+
7793
/// Whether this element is a wildcard variable.
7894
bool get isWildcardVariable {
7995
return name == '_' &&

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -988,7 +988,9 @@ const List<DiagnosticCode> diagnosticCodeValues = [
988988
WarningCode.DEAD_CODE_LATE_WILDCARD_VARIABLE_INITIALIZER,
989989
WarningCode.DEAD_CODE_ON_CATCH_SUBTYPE,
990990
WarningCode.DEPRECATED_EXPORT_USE,
991+
WarningCode.DEPRECATED_EXTEND,
991992
WarningCode.DEPRECATED_EXTENDS_FUNCTION,
993+
WarningCode.DEPRECATED_IMPLEMENT,
992994
WarningCode.DEPRECATED_IMPLEMENTS_FUNCTION,
993995
WarningCode.DEPRECATED_MIXIN_FUNCTION,
994996
WarningCode.DEPRECATED_NEW_IN_COMMENT_REFERENCE,
@@ -1022,6 +1024,8 @@ const List<DiagnosticCode> diagnosticCodeValues = [
10221024
WarningCode.INFERENCE_FAILURE_ON_UNTYPED_PARAMETER,
10231025
WarningCode.INVALID_ANNOTATION_TARGET,
10241026
WarningCode.INVALID_AWAIT_NOT_REQUIRED_ANNOTATION,
1027+
WarningCode.INVALID_DEPRECATED_EXTEND_ANNOTATION,
1028+
WarningCode.INVALID_DEPRECATED_IMPLEMENT_ANNOTATION,
10251029
WarningCode.INVALID_EXPORT_OF_INTERNAL_ELEMENT,
10261030
WarningCode.INVALID_EXPORT_OF_INTERNAL_ELEMENT_INDIRECTLY,
10271031
WarningCode.INVALID_FACTORY_METHOD_DECL,

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

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ class AnnotationVerifier {
4545
var parent = node.parent;
4646
if (element.isAwaitNotRequired) {
4747
_checkAwaitNotRequired(node);
48+
} else if (element.isDeprecated) {
49+
_checkDeprecated(node);
4850
} else if (element.isFactory) {
4951
_checkFactory(node);
5052
} else if (element.isInternal) {
@@ -113,6 +115,88 @@ class AnnotationVerifier {
113115
}
114116
}
115117

118+
void _checkDeprecated(Annotation node) {
119+
var element = node.elementAnnotation;
120+
var value = element?.computeConstantValue();
121+
if (value == null) return;
122+
123+
// The vast majority of deprecated annotations use the default constructor.
124+
// Check this case first.
125+
if (value.getField('_isUse')?.toBoolValue() ?? true) return;
126+
127+
var isExtend = value.getField('_isExtend')?.toBoolValue() ?? false;
128+
if (isExtend) {
129+
_checkDeprecatedExtend(node, node.parent);
130+
return;
131+
}
132+
133+
var isImplement = value.getField('_isImplement')?.toBoolValue() ?? false;
134+
if (isImplement) {
135+
_checkDeprecatedImplement(node, node.parent);
136+
return;
137+
}
138+
}
139+
140+
void _checkDeprecatedExtend(Annotation node, AstNode parent) {
141+
if (parent
142+
case ClassDeclaration(:var declaredFragment) ||
143+
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;
150+
}
151+
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+
}
161+
}
162+
}
163+
164+
_diagnosticReporter.atNode(
165+
node.name,
166+
WarningCode.INVALID_DEPRECATED_EXTEND_ANNOTATION,
167+
);
168+
}
169+
170+
void _checkDeprecatedImplement(Annotation node, AstNode parent) {
171+
if (parent
172+
case ClassDeclaration(:var declaredFragment) ||
173+
ClassTypeAlias(:var declaredFragment)) {
174+
var classElement = declaredFragment?.element;
175+
if (classElement == null) return;
176+
if (classElement.isImplementableOutside) return;
177+
}
178+
179+
if (parent is MixinDeclaration) {
180+
var mixinElement = parent.declaredFragment?.element;
181+
if (mixinElement == null) return;
182+
if (mixinElement.isImplementableOutside) return;
183+
}
184+
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+
}
192+
}
193+
194+
_diagnosticReporter.atNode(
195+
node.name,
196+
WarningCode.INVALID_DEPRECATED_IMPLEMENT_ANNOTATION,
197+
);
198+
}
199+
116200
/// Reports a warning at [node] if its parent is not a valid target for a
117201
/// `@factory` annotation.
118202
void _checkFactory(Annotation node) {

0 commit comments

Comments
 (0)