Improve annotation identifier recovery

Change-Id: Iaac50ebf39f6f76939f738f97929c8212fa2c098
Reviewed-on: https://dart-review.googlesource.com/56240
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
diff --git a/pkg/analyzer/test/src/fasta/recovery/partial_code/annotation_test.dart b/pkg/analyzer/test/src/fasta/recovery/partial_code/annotation_test.dart
index 1b2fdee..06105e3 100644
--- a/pkg/analyzer/test/src/fasta/recovery/partial_code/annotation_test.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/partial_code/annotation_test.dart
@@ -12,47 +12,116 @@
 
 class AnnotationTest extends PartialCodeTest {
   buildAll() {
-    List<TestDescriptor> descriptors = <TestDescriptor>[
-      new TestDescriptor(
-        'ampersand',
-        '@',
-        [ParserErrorCode.MISSING_IDENTIFIER],
-        '@_s_',
-        allFailing: true,
-      ),
-      new TestDescriptor(
-        'leftParen',
-        '@a(',
-        [ParserErrorCode.EXPECTED_TOKEN],
-        '@a()',
-        allFailing: true,
-      ),
-    ];
     buildTests(
       'annotation_topLevel',
-      expectErrors(descriptors, [ParserErrorCode.EXPECTED_EXECUTABLE]),
+      [
+        new TestDescriptor(
+            'ampersand',
+            '@',
+            [
+              ParserErrorCode.MISSING_IDENTIFIER,
+              ParserErrorCode.EXPECTED_EXECUTABLE
+            ],
+            '@_s_',
+            expectedErrorsInValidCode: [ParserErrorCode.EXPECTED_EXECUTABLE]),
+        new TestDescriptor(
+            'leftParen',
+            '@a(',
+            [
+              ScannerErrorCode.EXPECTED_TOKEN,
+              ParserErrorCode.EXPECTED_EXECUTABLE
+            ],
+            '@a()',
+            expectedErrorsInValidCode: [ParserErrorCode.EXPECTED_EXECUTABLE]),
+      ],
       [],
     );
     buildTests(
       'annotation_topLevel',
-      descriptors,
+      [
+        new TestDescriptor(
+            'ampersand', '@', [ParserErrorCode.MISSING_IDENTIFIER], '@_s_',
+            failing: ['typedef', 'functionNonVoid', 'getter', 'setter']),
+        new TestDescriptor(
+            'leftParen', '@a(', [ScannerErrorCode.EXPECTED_TOKEN], '@a()',
+            allFailing: true),
+      ],
       PartialCodeTest.declarationSuffixes,
       includeEof: false,
     );
+
     buildTests(
       'annotation_classMember',
-      descriptors,
-      PartialCodeTest.classMemberSuffixes,
+      [
+        new TestDescriptor(
+            'ampersand',
+            '@',
+            [
+              ParserErrorCode.MISSING_IDENTIFIER,
+              ParserErrorCode.EXPECTED_CLASS_MEMBER
+            ],
+            '@_s_',
+            expectedErrorsInValidCode: [ParserErrorCode.EXPECTED_CLASS_MEMBER]),
+        new TestDescriptor(
+            'leftParen',
+            '@a(',
+            [
+              ScannerErrorCode.EXPECTED_TOKEN,
+              ParserErrorCode.EXPECTED_CLASS_MEMBER
+            ],
+            '@a()',
+            expectedErrorsInValidCode: [ParserErrorCode.EXPECTED_CLASS_MEMBER]),
+      ],
+      [],
       head: 'class C { ',
       tail: ' }',
     );
     buildTests(
+      'annotation_classMember',
+      [
+        new TestDescriptor(
+            'ampersand', '@', [ParserErrorCode.MISSING_IDENTIFIER], '@_s_',
+            failing: ['methodNonVoid', 'getter', 'setter']),
+        new TestDescriptor(
+            'leftParen', '@a(', [ScannerErrorCode.EXPECTED_TOKEN], '@a()',
+            allFailing: true),
+      ],
+      PartialCodeTest.classMemberSuffixes,
+      includeEof: false,
+      head: 'class C { ',
+      tail: ' }',
+    );
+
+    buildTests(
       'annotation_local',
-      expectErrors(descriptors, [
-        ParserErrorCode.EXPECTED_TOKEN,
-        ParserErrorCode.EXPECTED_TYPE_NAME,
-        ParserErrorCode.MISSING_IDENTIFIER,
-      ]),
+      [
+        new TestDescriptor(
+            'ampersand',
+            '@',
+            [
+              ParserErrorCode.MISSING_IDENTIFIER,
+              ParserErrorCode.EXPECTED_TOKEN,
+              ParserErrorCode.MISSING_IDENTIFIER
+            ],
+            '@_s_',
+            expectedErrorsInValidCode: [
+              ParserErrorCode.MISSING_IDENTIFIER,
+              ParserErrorCode.EXPECTED_TOKEN
+            ]),
+        new TestDescriptor(
+            'leftParen',
+            '@a(',
+            [
+              ScannerErrorCode.EXPECTED_TOKEN,
+              ParserErrorCode.EXPECTED_TOKEN,
+              ParserErrorCode.MISSING_IDENTIFIER
+            ],
+            '@a()',
+            expectedErrorsInValidCode: [
+              ParserErrorCode.MISSING_IDENTIFIER,
+              ParserErrorCode.EXPECTED_TOKEN
+            ]),
+      ],
       [],
       head: 'f() { ',
       tail: ' }',
@@ -60,24 +129,71 @@
     // TODO(brianwilkerson) Many of the combinations produced by the following
     // produce "valid" code that is not valid. Even when we recover the
     // annotation, the following statement is not allowed to have an annotation.
+    const localAllowed = const [
+      'localVariable',
+      'localFunctionNonVoid',
+      'localFunctionVoid'
+    ];
+    List<TestSuffix> localAnnotationAllowedSuffixes = PartialCodeTest
+        .statementSuffixes
+        .where((t) => localAllowed.contains(t.name))
+        .toList();
+    List<TestSuffix> localAnnotationNotAllowedSuffixes = PartialCodeTest
+        .statementSuffixes
+        .where((t) => !localAllowed.contains(t.name))
+        .toList();
+
     buildTests(
       'annotation_local',
-      descriptors,
-      PartialCodeTest.statementSuffixes,
-      head: 'f() { ',
+      [
+        new TestDescriptor(
+            'ampersand', '@', [ParserErrorCode.MISSING_IDENTIFIER], '@_s_',
+            failing: ['localFunctionNonVoid']),
+        new TestDescriptor(
+            'leftParen', '@a(', [ParserErrorCode.MISSING_IDENTIFIER], '@a()',
+            allFailing: true),
+      ],
+      localAnnotationAllowedSuffixes,
       includeEof: false,
+      head: 'f() { ',
+      tail: ' }',
+    );
+    buildTests(
+      'annotation_local',
+      [
+        new TestDescriptor(
+            'ampersand',
+            '@',
+            [
+              ParserErrorCode.MISSING_IDENTIFIER,
+              ParserErrorCode.EXPECTED_TOKEN,
+              ParserErrorCode.MISSING_IDENTIFIER
+            ],
+            '@_s_',
+            expectedErrorsInValidCode: [
+              ParserErrorCode.MISSING_IDENTIFIER,
+              ParserErrorCode.EXPECTED_TOKEN
+            ],
+            failing: ['labeled']),
+        new TestDescriptor(
+            'leftParen',
+            '@a(',
+            [
+              ScannerErrorCode.EXPECTED_TOKEN,
+              ParserErrorCode.EXPECTED_TOKEN,
+              ParserErrorCode.MISSING_IDENTIFIER
+            ],
+            '@a()',
+            expectedErrorsInValidCode: [
+              ParserErrorCode.MISSING_IDENTIFIER,
+              ParserErrorCode.EXPECTED_TOKEN
+            ],
+            allFailing: true),
+      ],
+      localAnnotationNotAllowedSuffixes,
+      includeEof: false,
+      head: 'f() { ',
       tail: ' }',
     );
   }
-
-  /**
-   * Return a list of descriptors just like the given [descriptors] except that
-   * they have the given list of [errors] as the errors that are expected to be
-   * in the valid code.
-   */
-  List<TestDescriptor> expectErrors(
-          List<TestDescriptor> descriptors, List<ParserErrorCode> errors) =>
-      descriptors
-          .map((descriptor) => descriptor.withExpectedErrorsInValidCode(errors))
-          .toList();
 }
diff --git a/pkg/analyzer/test/src/fasta/recovery/partial_code/partial_code_support.dart b/pkg/analyzer/test/src/fasta/recovery/partial_code/partial_code_support.dart
index faaddf6..533c856 100644
--- a/pkg/analyzer/test/src/fasta/recovery/partial_code/partial_code_support.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/partial_code/partial_code_support.dart
@@ -260,17 +260,6 @@
    */
   TestDescriptor(this.name, this.invalid, this.errorCodes, this.valid,
       {this.allFailing: false, this.failing, this.expectedErrorsInValidCode});
-
-  /**
-   * Return a new description that is exactly like this descriptor except with
-   * the given [expectedErrorsInValidCode].
-   */
-  TestDescriptor withExpectedErrorsInValidCode(
-          List<ErrorCode> expectedErrorsInValidCode) =>
-      new TestDescriptor(name, invalid, errorCodes, valid,
-          allFailing: allFailing,
-          failing: failing,
-          expectedErrorsInValidCode: expectedErrorsInValidCode);
 }
 
 /**
diff --git a/pkg/front_end/lib/src/fasta/parser/identifier_context.dart b/pkg/front_end/lib/src/fasta/parser/identifier_context.dart
index 9c65f14..a4075e1 100644
--- a/pkg/front_end/lib/src/fasta/parser/identifier_context.dart
+++ b/pkg/front_end/lib/src/fasta/parser/identifier_context.dart
@@ -39,19 +39,17 @@
 
   /// Identifier is the start of a name in an annotation that precedes a
   /// declaration (i.e. it appears directly after an `@`).
-  static const metadataReference =
-      const IdentifierContext('metadataReference', isScopeReference: true);
+  static const metadataReference = const MetadataReferenceIdentifierContext();
 
   /// Identifier is part of a name in an annotation that precedes a declaration,
   /// but it's not the first identifier in the name.
   static const metadataContinuation =
-      const IdentifierContext('metadataContinuation', isContinuation: true);
+      const MetadataReferenceIdentifierContext.continuation();
 
   /// Identifier is part of a name in an annotation that precedes a declaration,
   /// but it appears after type parameters (e.g. `foo` in `@X<Y>.foo()`).
-  static const metadataContinuationAfterTypeArguments = const IdentifierContext(
-      'metadataContinuationAfterTypeArguments',
-      isContinuation: true);
+  static const metadataContinuationAfterTypeArguments =
+      const MetadataReferenceIdentifierContext.continuationAfterTypeArguments();
 
   /// Identifier is the name being declared by a typedef declaration.
   static const typedefDeclaration = const TypedefDeclarationIdentifierContext();
diff --git a/pkg/front_end/lib/src/fasta/parser/identifier_context_impl.dart b/pkg/front_end/lib/src/fasta/parser/identifier_context_impl.dart
index ab54ca0..2beb97c 100644
--- a/pkg/front_end/lib/src/fasta/parser/identifier_context_impl.dart
+++ b/pkg/front_end/lib/src/fasta/parser/identifier_context_impl.dart
@@ -458,6 +458,47 @@
   }
 }
 
+/// See [IdentifierContext.metadataReference]
+/// and [IdentifierContext.metadataContinuation]
+/// and [IdentifierContext.metadataContinuationAfterTypeArguments].
+class MetadataReferenceIdentifierContext extends IdentifierContext {
+  const MetadataReferenceIdentifierContext()
+      : super('metadataReference', isScopeReference: true);
+
+  const MetadataReferenceIdentifierContext.continuation()
+      : super('metadataContinuation', isContinuation: true);
+
+  const MetadataReferenceIdentifierContext.continuationAfterTypeArguments()
+      : super('metadataContinuationAfterTypeArguments', isContinuation: true);
+
+  @override
+  Token ensureIdentifier(Token token, Parser parser) {
+    Token identifier = token.next;
+    assert(identifier.kind != IDENTIFIER_TOKEN);
+    if (identifier.isIdentifier) {
+      return identifier;
+    }
+
+    // Recovery
+    if (isOneOfOrEof(identifier, const ['{', '}', '(', ')', ']']) ||
+        looksLikeStartOfNextTopLevelDeclaration(identifier) ||
+        looksLikeStartOfNextClassMember(identifier) ||
+        looksLikeStartOfNextStatement(identifier)) {
+      identifier = parser.insertSyntheticIdentifier(token, this,
+          message: fasta.templateExpectedIdentifier.withArguments(identifier));
+    } else {
+      parser.reportRecoverableErrorWithToken(
+          identifier, fasta.templateExpectedIdentifier);
+      if (!identifier.isKeywordOrIdentifier) {
+        // When in doubt, consume the token to ensure we make progress
+        // but insert a synthetic identifier to satisfy listeners.
+        identifier = parser.rewriter.insertSyntheticIdentifier(identifier);
+      }
+    }
+    return identifier;
+  }
+}
+
 /// See [IdentifierContext.methodDeclaration],
 /// and [IdentifierContext.methodDeclarationContinuation],
 /// and [IdentifierContext.operatorName].
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index 66e5dc0..fc82a91 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -6070,8 +6070,10 @@
       // TODO(danrubel): Provide a more specific error message for extra ';'.
       reportRecoverableErrorWithToken(next, fasta.templateExpectedClassMember);
       listener.handleInvalidMember(next);
-      // Ensure we make progress.
-      token = next;
+      if (!identical(value, '}')) {
+        // Ensure we make progress.
+        token = next;
+      }
     } else {
       token = parseFields(beforeStart, externalToken, staticToken,
           covariantToken, varFinalOrConst, beforeType, typeInfo, token, false);