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';