Improve recovery of enum declaration

Change-Id: I8709e76c38d778d97673e11a8d89d424611a292b
Reviewed-on: https://dart-review.googlesource.com/54543
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/enum_declaration_test.dart b/pkg/analyzer/test/src/fasta/recovery/partial_code/enum_declaration_test.dart
index 8f2bffa..22b63e2 100644
--- a/pkg/analyzer/test/src/fasta/recovery/partial_code/enum_declaration_test.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/partial_code/enum_declaration_test.dart
@@ -40,7 +40,65 @@
               ],
               'enum _s_ {}',
               expectedErrorsInValidCode: [ParserErrorCode.EMPTY_ENUM_BODY]),
+          new TestDescriptor(
+              'leftBrace',
+              'enum E {',
+              [
+                ParserErrorCode.MISSING_IDENTIFIER,
+                ScannerErrorCode.EXPECTED_TOKEN
+              ],
+              'enum E {_s_}',
+              failing: [
+                'eof' /* tested separately below */,
+                'typedef',
+                'functionNonVoid',
+                'getter',
+                'setter'
+              ]),
+          new TestDescriptor(
+              'comma',
+              'enum E {,',
+              [
+                ParserErrorCode.MISSING_IDENTIFIER,
+                ParserErrorCode.MISSING_IDENTIFIER,
+                ScannerErrorCode.EXPECTED_TOKEN
+              ],
+              'enum E {_s_,_s_}',
+              failing: [
+                'eof' /* tested separately below */,
+                'typedef',
+                'functionNonVoid',
+                'getter',
+                'setter'
+              ]),
+          new TestDescriptor('value', 'enum E {a',
+              [ScannerErrorCode.EXPECTED_TOKEN], 'enum E {a}'),
+          new TestDescriptor(
+              'commaValue',
+              'enum E {,a',
+              [
+                ParserErrorCode.MISSING_IDENTIFIER,
+                ScannerErrorCode.EXPECTED_TOKEN
+              ],
+              'enum E {_s_, a}'),
+          new TestDescriptor('commaRightBrace', 'enum E {,}',
+              [ParserErrorCode.MISSING_IDENTIFIER], 'enum E {_s_}'),
+          new TestDescriptor('commaValueRightBrace', 'enum E {, a}',
+              [ParserErrorCode.MISSING_IDENTIFIER], 'enum E {_s_, a}'),
         ],
         PartialCodeTest.declarationSuffixes);
+    buildTests('enum_eof', [
+      new TestDescriptor(
+          'leftBrace',
+          'enum E {',
+          [ParserErrorCode.EMPTY_ENUM_BODY, ScannerErrorCode.EXPECTED_TOKEN],
+          'enum E {}',
+          expectedErrorsInValidCode: [ParserErrorCode.EMPTY_ENUM_BODY]),
+      new TestDescriptor(
+          'comma',
+          'enum E {,',
+          [ParserErrorCode.MISSING_IDENTIFIER, ScannerErrorCode.EXPECTED_TOKEN],
+          'enum E {_s_}'),
+    ], []);
   }
 }
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 f8ecd55..ba67f33 100644
--- a/pkg/front_end/lib/src/fasta/parser/identifier_context.dart
+++ b/pkg/front_end/lib/src/fasta/parser/identifier_context.dart
@@ -93,7 +93,7 @@
   /// Identifier is an enumerated value name being declared by an enum
   /// declaration.
   static const enumValueDeclaration =
-      const IdentifierContext('enumValueDeclaration', inDeclaration: true);
+      const EnumValueDeclarationIdentifierContext();
 
   /// Identifier is the name being declared by a class declaration or a named
   /// mixin application, for example, `Foo` in `class Foo = X with Y;`.
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 c98e858..37aedf8 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
@@ -130,6 +130,34 @@
   }
 }
 
+/// See [IdentifierContext.enumValueDeclaration].
+class EnumValueDeclarationIdentifierContext extends IdentifierContext {
+  const EnumValueDeclarationIdentifierContext()
+      : super('enumValueDeclaration', inDeclaration: true);
+
+  @override
+  Token ensureIdentifier(Token token, Parser parser) {
+    Token identifier = token.next;
+    assert(identifier.kind != IDENTIFIER_TOKEN);
+    if (identifier.isIdentifier) {
+      return identifier;
+    }
+
+    // Recovery
+    parser.reportRecoverableErrorWithToken(
+        identifier, fasta.templateExpectedIdentifier);
+    if (looksLikeStartOfNextTopLevelDeclaration(identifier) ||
+        isOneOfOrEof(identifier, const [',', '}'])) {
+      return insertSyntheticIdentifierAfter(token, parser);
+    } else if (!identifier.isKeywordOrIdentifier) {
+      // When in doubt, consume the token to ensure we make progress
+      // but insert a synthetic identifier to satisfy listeners.
+      return insertSyntheticIdentifierAfter(identifier, parser);
+    }
+    return identifier;
+  }
+}
+
 /// See [IdentifierContext.expression].
 class ExpressionIdentifierContext extends IdentifierContext {
   const ExpressionIdentifierContext()
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index a39a0b3..72adcc5 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -1614,7 +1614,12 @@
           break;
         } else {
           // Recovery
-          if (next.isIdentifier) {
+          Token endGroup = leftBrace.endGroup;
+          if (endGroup.isSynthetic) {
+            // The scanner did not place the synthetic '}' correctly.
+            token = rewriter.moveSynthetic(token, endGroup);
+            break;
+          } else if (next.isIdentifier) {
             // If the next token is an identifier, assume a missing comma.
             // TODO(danrubel): Consider improved recovery for missing `}`
             // both here and when the scanner inserts a synthetic `}`
@@ -2008,8 +2013,6 @@
       followingValues = [';'];
     } else if (context == IdentifierContext.constructorReferenceContinuation) {
       followingValues = ['.', ',', '(', ')', '[', ']', '}', ';'];
-    } else if (context == IdentifierContext.enumValueDeclaration) {
-      followingValues = [',', '}'];
     } else if (context == IdentifierContext.formalParameterDeclaration) {
       followingValues = [':', '=', ',', '(', ')', '[', ']', '{', '}'];
     } else if (context == IdentifierContext.labelDeclaration) {