analyzer: Check implicit super-constructor use in ElementUsageDetector
When a generative, non-redirecting constructor does not feature a
super-initiailizer, then the supertype's unnamed constructor is called
implicitly. If that super-constructor is marked for usage (e.g. marked
with `@Deprecated` or `@experimental`), then we should report any
implicit call as usage.
Change-Id: I2c5ae31720a6024b0ff357db686b6d85d2ccdf58
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/455040
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
diff --git a/pkg/analyzer/lib/src/error/best_practices_verifier.dart b/pkg/analyzer/lib/src/error/best_practices_verifier.dart
index 3594be5..536867c 100644
--- a/pkg/analyzer/lib/src/error/best_practices_verifier.dart
+++ b/pkg/analyzer/lib/src/error/best_practices_verifier.dart
@@ -316,6 +316,9 @@
for (var v in _elementUsageFrontierDetectors) {
v.pushElement(element);
}
+ for (var v in _elementUsageFrontierDetectors) {
+ v.constructorDeclaration(node);
+ }
// TODO(srawlins): Use _elementUsageFrontierDetectors to detect
// `@Deprecated` or `@experimental` parameters in a redirecting factory
diff --git a/pkg/analyzer/lib/src/error/element_usage_detector.dart b/pkg/analyzer/lib/src/error/element_usage_detector.dart
index e72bb6b..58c530d 100644
--- a/pkg/analyzer/lib/src/error/element_usage_detector.dart
+++ b/pkg/analyzer/lib/src/error/element_usage_detector.dart
@@ -113,6 +113,11 @@
var invokeClass = invokeType.element;
displayName = '${invokeClass.name}.${element.displayName}';
}
+
+ // TODO(srawlins): Consider `node` being a `ConstructorDeclaration`, and use
+ // `ConstructorDeclaration.errorRange` here. This would stray from the API
+ // of passing a SyntacticEntity here.
+
elementUsageReporter.report(
errorEntity,
displayName,
@@ -121,6 +126,23 @@
);
}
+ void constructorDeclaration(ConstructorDeclaration node) {
+ // Check usage of any implicit super-constructor call.
+ // There is only an implicit super-constructor if:
+ // * this is not a factory constructor,
+ // * there is no redirecting constructor invocation, and
+ // * there is no explicit super constructor invocation.
+ if (node.factoryKeyword != null) return;
+ var hasConstructorInvocation = node.initializers.any(
+ (i) =>
+ i is SuperConstructorInvocation ||
+ i is RedirectingConstructorInvocation,
+ );
+ if (hasConstructorInvocation) return;
+
+ checkUsage(node.declaredFragment!.element.superConstructor, node);
+ }
+
void constructorName(ConstructorName node) {
checkUsage(node.element, node);
}
diff --git a/pkg/analyzer/test/src/diagnostics/deprecated_member_use_test.dart b/pkg/analyzer/test/src/diagnostics/deprecated_member_use_test.dart
index 796a872..61f7c30 100644
--- a/pkg/analyzer/test/src/diagnostics/deprecated_member_use_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/deprecated_member_use_test.dart
@@ -1947,6 +1947,41 @@
);
}
+ test_superConstructor_factoryConstructor() async {
+ await assertNoErrorsInCode2(
+ externalCode: r'''
+class A {
+ @deprecated
+ A();
+ A.two();
+}
+''',
+ code: r'''
+class B extends A {
+ factory B() => B.two();
+ B.two() : super.two();
+}
+''',
+ );
+ }
+
+ test_superConstructor_implicitCall() async {
+ await assertErrorsInCode2(
+ externalCode: r'''
+class A {
+ @deprecated
+ A();
+}
+''',
+ code: r'''
+class B extends A {
+ B();
+}
+''',
+ [error(HintCode.deprecatedMemberUse, 51, 4)],
+ );
+ }
+
test_superConstructor_namedConstructor() async {
await assertErrorsInCode2(
externalCode: r'''
@@ -1971,6 +2006,24 @@
);
}
+ test_superConstructor_redirectingConstructor() async {
+ await assertNoErrorsInCode2(
+ externalCode: r'''
+class A {
+ @deprecated
+ A();
+ A.two();
+}
+''',
+ code: r'''
+class B extends A {
+ B() : this.two();
+ B.two() : super.two();
+}
+''',
+ );
+ }
+
test_superConstructor_unnamedConstructor() async {
await assertErrorsInCode2(
externalCode: r'''