Implement INVALID_SEALED_ANNOTATION analyzer Hint
This follows the pattern of other meta-annotations, when it comes to the AST:
* Element gains a new `hasSealed` getter. Many overrides.
* ElementAnnotation gains a new `isSealed` getter.
Bug: https://github.com/dart-lang/sdk/issues/34232
Change-Id: If8ae8e16b500125cb3b92b3cf83d46de6ca6ee23
Reviewed-on: https://dart-review.googlesource.com/71227
Commit-Queue: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/lib/dart/element/element.dart b/pkg/analyzer/lib/dart/element/element.dart
index b37f425..7e2ca4c 100644
--- a/pkg/analyzer/lib/dart/element/element.dart
+++ b/pkg/analyzer/lib/dart/element/element.dart
@@ -685,6 +685,11 @@
bool get hasRequired;
/**
+ * Return `true` if this element has an annotation of the form '@sealed'.
+ */
+ bool get hasSealed;
+
+ /**
* Return `true` if this element has an annotation of the form
* `@visibleForTemplate`.
*/
@@ -991,6 +996,12 @@
bool get isRequired;
/**
+ * Return `true` if this annotation marks the associated class as being
+ * sealed.
+ */
+ bool get isSealed;
+
+ /**
* Return `true` if this annotation marks the associated member as being
* visible for template files.
*/
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart
index 4cfcde6..5662d2f 100644
--- a/pkg/analyzer/lib/error/error.dart
+++ b/pkg/analyzer/lib/error/error.dart
@@ -305,6 +305,7 @@
HintCode.INVALID_METHOD_OVERRIDE_TYPE_PARAMETERS,
HintCode.INVALID_METHOD_OVERRIDE_TYPE_PARAMETER_BOUND,
HintCode.INVALID_REQUIRED_PARAM,
+ HintCode.INVALID_SEALED_ANNOTATION,
HintCode.INVALID_USE_OF_PROTECTED_MEMBER,
HintCode.INVALID_USE_OF_VISIBLE_FOR_TEMPLATE_MEMBER,
HintCode.INVALID_USE_OF_VISIBLE_FOR_TESTING_MEMBER,
diff --git a/pkg/analyzer/lib/src/dart/element/element.dart b/pkg/analyzer/lib/src/dart/element/element.dart
index 667b79f..73e23a7 100644
--- a/pkg/analyzer/lib/src/dart/element/element.dart
+++ b/pkg/analyzer/lib/src/dart/element/element.dart
@@ -2952,6 +2952,11 @@
*/
static String _REQUIRED_VARIABLE_NAME = "required";
+ /**
+ * The name of the top-level variable used to mark a class as being sealed.
+ */
+ static String _SEALED_VARIABLE_NAME = "sealed";
+
/// The name of the top-level variable used to mark a method as being
/// visible for templates.
static String _VISIBLE_FOR_TEMPLATE_VARIABLE_NAME = "visibleForTemplate";
@@ -3087,6 +3092,12 @@
element.library?.name == _META_LIB_NAME;
@override
+ bool get isSealed =>
+ element is PropertyAccessorElement &&
+ element.name == _SEALED_VARIABLE_NAME &&
+ element.library?.name == _META_LIB_NAME;
+
+ @override
bool get isVisibleForTemplate =>
element is PropertyAccessorElement &&
element.name == _VISIBLE_FOR_TEMPLATE_VARIABLE_NAME &&
@@ -3309,6 +3320,10 @@
metadata.any((ElementAnnotation annotation) => annotation.isRequired);
@override
+ bool get hasSealed =>
+ metadata.any((ElementAnnotation annotation) => annotation.isSealed);
+
+ @override
bool get hasVisibleForTemplate => metadata
.any((ElementAnnotation annotation) => annotation.isVisibleForTemplate);
@@ -7775,6 +7790,9 @@
bool get hasRequired => false;
@override
+ bool get hasSealed => false;
+
+ @override
bool get hasVisibleForTemplate => false;
@override
diff --git a/pkg/analyzer/lib/src/dart/element/handle.dart b/pkg/analyzer/lib/src/dart/element/handle.dart
index e201bbf..331832d 100644
--- a/pkg/analyzer/lib/src/dart/element/handle.dart
+++ b/pkg/analyzer/lib/src/dart/element/handle.dart
@@ -56,6 +56,9 @@
bool get hasRequired => actualElement.hasRequired;
@override
+ bool get hasSealed => actualElement.hasSealed;
+
+ @override
bool get hasStaticMember => actualElement.hasStaticMember;
@override
@@ -388,6 +391,9 @@
bool get hasRequired => actualElement.hasRequired;
@override
+ bool get hasSealed => actualElement.hasSealed;
+
+ @override
bool get hasVisibleForTemplate => actualElement.hasVisibleForTemplate;
@override
diff --git a/pkg/analyzer/lib/src/dart/element/member.dart b/pkg/analyzer/lib/src/dart/element/member.dart
index 58736d2..5692687 100644
--- a/pkg/analyzer/lib/src/dart/element/member.dart
+++ b/pkg/analyzer/lib/src/dart/element/member.dart
@@ -419,6 +419,9 @@
bool get hasRequired => _baseElement.hasRequired;
@override
+ bool get hasSealed => _baseElement.hasSealed;
+
+ @override
bool get hasVisibleForTemplate => _baseElement.hasVisibleForTemplate;
@override
diff --git a/pkg/analyzer/lib/src/dart/element/wrapped.dart b/pkg/analyzer/lib/src/dart/element/wrapped.dart
index 88291aa..cd3d985 100644
--- a/pkg/analyzer/lib/src/dart/element/wrapped.dart
+++ b/pkg/analyzer/lib/src/dart/element/wrapped.dart
@@ -76,6 +76,9 @@
bool get hasRequired => wrappedUnit.hasRequired;
@override
+ bool get hasSealed => wrappedUnit.hasSealed;
+
+ @override
bool get hasVisibleForTemplate => wrappedUnit.hasVisibleForTemplate;
@override
@@ -252,6 +255,9 @@
bool get hasRequired => wrappedImport.hasRequired;
@override
+ bool get hasSealed => wrappedImport.hasSealed;
+
+ @override
bool get hasVisibleForTemplate => wrappedImport.hasVisibleForTemplate;
@override
@@ -446,6 +452,9 @@
bool get hasRequired => wrappedLib.hasRequired;
@override
+ bool get hasSealed => wrappedLib.hasSealed;
+
+ @override
bool get hasVisibleForTemplate => wrappedLib.hasVisibleForTemplate;
@override
diff --git a/pkg/analyzer/lib/src/dart/error/hint_codes.dart b/pkg/analyzer/lib/src/dart/error/hint_codes.dart
index 97341b4..73b516c 100644
--- a/pkg/analyzer/lib/src/dart/error/hint_codes.dart
+++ b/pkg/analyzer/lib/src/dart/error/hint_codes.dart
@@ -297,6 +297,19 @@
correction: "Remove @required.");
/**
+ * This hint is generated anywhere where `@sealed` annotates something other
+ * than a class or mixin.
+ *
+ * Parameters:
+ * 0: the name of the member
+ */
+ static const HintCode INVALID_SEALED_ANNOTATION = const HintCode(
+ 'INVALID_SEALED_ANNOTATION',
+ "The member '{0}' is annotated with @sealed but only classes and mixins "
+ "can be annotated with it.",
+ correction: "Remove @sealed.");
+
+ /**
* This hint is generated anywhere where a member annotated with `@protected`
* is used outside an instance member of a subclass.
*
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index 7d0b9c0..ef983c1 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -1574,6 +1574,13 @@
// arguments should be constants
_validateConstantArguments(argumentList);
}
+ if (node.elementAnnotation?.isSealed == true &&
+ !(node.parent is ClassDeclaration ||
+ node.parent is ClassTypeAlias ||
+ node.parent is MixinDeclaration)) {
+ _errorReporter.reportErrorForNode(
+ HintCode.INVALID_SEALED_ANNOTATION, node.parent, [node.element.name]);
+ }
return null;
}
diff --git a/pkg/analyzer/test/generated/hint_code_kernel_test.dart b/pkg/analyzer/test/generated/hint_code_kernel_test.dart
index ebc8fb6..073e8c5 100644
--- a/pkg/analyzer/test/generated/hint_code_kernel_test.dart
+++ b/pkg/analyzer/test/generated/hint_code_kernel_test.dart
@@ -157,6 +157,13 @@
@failingTest
@override
+ // Failing due to https://github.com/dart-lang/sdk/issues/34249
+ test_invalidSealedAnnotation_onMixinApplication() async {
+ return super.test_invalidSealedAnnotation_onMixinApplication();
+ }
+
+ @failingTest
+ @override
test_strongMode_downCastCompositeHint() async {
return super.test_strongMode_downCastCompositeHint();
}
diff --git a/pkg/analyzer/test/generated/hint_code_test.dart b/pkg/analyzer/test/generated/hint_code_test.dart
index 7235f38..36f7faa 100644
--- a/pkg/analyzer/test/generated/hint_code_test.dart
+++ b/pkg/analyzer/test/generated/hint_code_test.dart
@@ -38,6 +38,7 @@
const _MustCallSuper mustCallSuper = const _MustCallSuper();
const _Protected protected = const _Protected();
const Required required = const Required();
+const _Sealed sealed = const _Sealed();
const _VisibleForTesting visibleForTesting = const _VisibleForTesting();
class Immutable {
@@ -63,6 +64,9 @@
final String reason;
const Required([this.reason]);
}
+class _Sealed {
+ const _Sealed();
+}
class _VisibleForTesting {
const _VisibleForTesting();
}
@@ -1654,6 +1658,55 @@
verify([source]);
}
+ test_invalidSealedAnnotation_onNonClass() async {
+ Source source = addNamedSource('/lib1.dart', r'''
+import 'package:meta/meta.dart';
+
+@sealed m({a = 1}) => null;
+''');
+ await computeAnalysisResult(source);
+ assertErrors(source, [HintCode.INVALID_SEALED_ANNOTATION]);
+ verify([source]);
+ }
+
+ test_invalidSealedAnnotation_onClass() async {
+ Source source = addNamedSource('/lib1.dart', r'''
+import 'package:meta/meta.dart';
+
+@sealed class A {}
+''');
+ await computeAnalysisResult(source);
+ assertNoErrors(source);
+ verify([source]);
+ }
+
+ test_invalidSealedAnnotation_onMixinApplication() async {
+ Source source = addNamedSource('/lib1.dart', r'''
+import 'package:meta/meta.dart';
+
+abstract class A {}
+
+abstract class B {}
+
+@sealed abstract class M = A with B;
+''');
+ await computeAnalysisResult(source);
+ assertNoErrors(source);
+ verify([source]);
+ }
+
+ @failingTest
+ test_invalidSealedAnnotation_onMixin() async {
+ Source source = addNamedSource('/lib1.dart', r'''
+import 'package:meta/meta.dart';
+
+@sealed mixin M {}
+''');
+ await computeAnalysisResult(source);
+ assertNoErrors(source);
+ verify([source]);
+ }
+
test_invalidUseOfProtectedMember_closure() async {
Source source = addNamedSource('/lib1.dart', r'''
import 'package:meta/meta.dart';