Improve formal parameter identifier recovery

Change-Id: I7bc6012295836a4455388c287da3cf15eed9ee5f
Reviewed-on: https://dart-review.googlesource.com/56300
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
diff --git a/pkg/analyzer/test/generated/parser_test.dart b/pkg/analyzer/test/generated/parser_test.dart
index 56f90ba..7b529d3 100644
--- a/pkg/analyzer/test/generated/parser_test.dart
+++ b/pkg/analyzer/test/generated/parser_test.dart
@@ -4495,12 +4495,12 @@
     // being reported in code that cannot actually be reached), but that hasn't
     // been proven yet.
     createParser('(int a, int b ;',
-        expectedEndOffset: 0 /* ErrorToken at end of token stream */);
+        expectedEndOffset: 14 /* ErrorToken at end of token stream */);
     FormalParameterList list = parser.parseFormalParameterList();
     expectNotNullIfNoErrors(list);
     if (usingFastaParser) {
-      listener
-          .assertErrors([expectedError(ParserErrorCode.EXPECTED_TOKEN, 14, 1)]);
+      // Fasta scanner reports missing `)` error
+      listener.assertNoErrors();
     } else if (fe.Scanner.useFasta) {
       listener.errors
           .contains(expectedError(ParserErrorCode.EXPECTED_TOKEN, 14, 1));
diff --git a/pkg/analyzer/test/src/fasta/recovery/partial_code/method_declaration_test.dart b/pkg/analyzer/test/src/fasta/recovery/partial_code/method_declaration_test.dart
index a2c8624..aacff92 100644
--- a/pkg/analyzer/test/src/fasta/recovery/partial_code/method_declaration_test.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/partial_code/method_declaration_test.dart
@@ -12,6 +12,15 @@
 
 class MethodTest extends PartialCodeTest {
   buildAll() {
+    const allExceptEof = const [
+      'field',
+      'fieldConst',
+      'fieldFinal',
+      'methodNonVoid',
+      'methodVoid',
+      'getter',
+      'setter'
+    ];
     buildTests(
       'method_declaration',
       [
@@ -21,44 +30,52 @@
         new TestDescriptor(
           'noType_leftParen',
           'm(',
-          [ParserErrorCode.EXPECTED_TOKEN],
-          'm();',
-          allFailing: true,
+          [
+            ScannerErrorCode.EXPECTED_TOKEN,
+            ParserErrorCode.MISSING_FUNCTION_BODY
+          ],
+          'm() {}',
+          failing: allExceptEof,
         ),
         new TestDescriptor(
           'noType_paramName',
           'm(B',
-          [ParserErrorCode.EXPECTED_TOKEN],
-          'm(B);',
-          allFailing: true,
+          [
+            ScannerErrorCode.EXPECTED_TOKEN,
+            ParserErrorCode.MISSING_FUNCTION_BODY
+          ],
+          'm(B) {}',
+          failing: ['methodNonVoid', 'getter', 'setter'],
         ),
         new TestDescriptor(
-          'noType_paramTypeAndName',
-          'm(B b',
-          [ParserErrorCode.EXPECTED_TOKEN],
-          'm(B b);',
-          allFailing: true,
-        ),
+            'noType_paramTypeAndName',
+            'm(B b',
+            [
+              ScannerErrorCode.EXPECTED_TOKEN,
+              ParserErrorCode.MISSING_FUNCTION_BODY
+            ],
+            'm(B b) {}'),
         new TestDescriptor(
           'noType_paramAndComma',
           'm(B b,',
-          [ParserErrorCode.MISSING_IDENTIFIER, ParserErrorCode.EXPECTED_TOKEN],
-          'm(B b, _s_);',
-          allFailing: true,
+          [
+            ScannerErrorCode.EXPECTED_TOKEN,
+            ParserErrorCode.MISSING_FUNCTION_BODY
+          ],
+          'm(B b) {}',
+          failing: allExceptEof,
         ),
         new TestDescriptor(
           'noType_noParams',
           'm()',
-          [ParserErrorCode.EXPECTED_TOKEN],
-          'm();',
-          allFailing: true,
+          [ParserErrorCode.MISSING_FUNCTION_BODY],
+          'm() {}',
         ),
         new TestDescriptor(
           'noType_params',
           'm(b, c)',
-          [ParserErrorCode.EXPECTED_TOKEN],
-          'm(b, c);',
-          allFailing: true,
+          [ParserErrorCode.MISSING_FUNCTION_BODY],
+          'm(b, c) {}',
         ),
         new TestDescriptor(
           'noType_emptyOptional',
@@ -84,44 +101,53 @@
         new TestDescriptor(
           'type_leftParen',
           'A m(',
-          [ParserErrorCode.EXPECTED_TOKEN],
-          'A m();',
-          allFailing: true,
+          [
+            ScannerErrorCode.EXPECTED_TOKEN,
+            ParserErrorCode.MISSING_FUNCTION_BODY
+          ],
+          'A m() {}',
+          failing: allExceptEof,
         ),
         new TestDescriptor(
           'type_paramName',
           'A m(B',
-          [ParserErrorCode.EXPECTED_TOKEN],
-          'A m(B);',
-          allFailing: true,
+          [
+            ScannerErrorCode.EXPECTED_TOKEN,
+            ParserErrorCode.MISSING_FUNCTION_BODY
+          ],
+          'A m(B) {}',
+          failing: ['methodNonVoid', 'getter', 'setter'],
         ),
         new TestDescriptor(
           'type_paramTypeAndName',
           'A m(B b',
-          [ParserErrorCode.EXPECTED_TOKEN],
-          'A m(B b);',
-          allFailing: true,
+          [
+            ScannerErrorCode.EXPECTED_TOKEN,
+            ParserErrorCode.MISSING_FUNCTION_BODY
+          ],
+          'A m(B b) {}',
         ),
         new TestDescriptor(
           'type_paramAndComma',
           'A m(B b,',
-          [ParserErrorCode.MISSING_IDENTIFIER, ParserErrorCode.EXPECTED_TOKEN],
-          'A m(B b, _s_);',
-          allFailing: true,
+          [
+            ScannerErrorCode.EXPECTED_TOKEN,
+            ParserErrorCode.MISSING_FUNCTION_BODY
+          ],
+          'A m(B b) {}',
+          failing: allExceptEof,
         ),
         new TestDescriptor(
           'type_noParams',
           'A m()',
-          [ParserErrorCode.EXPECTED_TOKEN],
-          'A m();',
-          allFailing: true,
+          [ParserErrorCode.MISSING_FUNCTION_BODY],
+          'A m() {}',
         ),
         new TestDescriptor(
           'type_params',
           'A m(b, c)',
-          [ParserErrorCode.EXPECTED_TOKEN],
-          'A m(b, c);',
-          allFailing: true,
+          [ParserErrorCode.MISSING_FUNCTION_BODY],
+          'A m(b, c) {}',
         ),
         new TestDescriptor(
           'type_emptyOptional',
@@ -147,50 +173,53 @@
         new TestDescriptor(
           'static_noType_leftParen',
           'static m(',
-          [ParserErrorCode.EXPECTED_TOKEN],
-          'static m();',
-          allFailing: true,
-          expectedErrorsInValidCode: [ParserErrorCode.MISSING_FUNCTION_BODY],
+          [
+            ScannerErrorCode.EXPECTED_TOKEN,
+            ParserErrorCode.MISSING_FUNCTION_BODY
+          ],
+          'static m() {}',
+          failing: allExceptEof,
         ),
         new TestDescriptor(
           'static_noType_paramName',
           'static m(B',
-          [ParserErrorCode.EXPECTED_TOKEN],
-          'static m(B);',
-          allFailing: true,
-          expectedErrorsInValidCode: [ParserErrorCode.MISSING_FUNCTION_BODY],
+          [
+            ScannerErrorCode.EXPECTED_TOKEN,
+            ParserErrorCode.MISSING_FUNCTION_BODY
+          ],
+          'static m(B) {}',
+          failing: ['methodNonVoid', 'getter', 'setter'],
         ),
         new TestDescriptor(
           'static_noType_paramTypeAndName',
           'static m(B b',
-          [ParserErrorCode.EXPECTED_TOKEN],
-          'static m(B b);',
-          allFailing: true,
-          expectedErrorsInValidCode: [ParserErrorCode.MISSING_FUNCTION_BODY],
+          [
+            ScannerErrorCode.EXPECTED_TOKEN,
+            ParserErrorCode.MISSING_FUNCTION_BODY
+          ],
+          'static m(B b) {}',
         ),
         new TestDescriptor(
           'static_noType_paramAndComma',
           'static m(B b,',
-          [ParserErrorCode.MISSING_IDENTIFIER, ParserErrorCode.EXPECTED_TOKEN],
-          'static m(B b, _s_);',
-          allFailing: true,
-          expectedErrorsInValidCode: [ParserErrorCode.MISSING_FUNCTION_BODY],
+          [
+            ScannerErrorCode.EXPECTED_TOKEN,
+            ParserErrorCode.MISSING_FUNCTION_BODY
+          ],
+          'static m(B b) {}',
+          failing: allExceptEof,
         ),
         new TestDescriptor(
           'static_noType_noParams',
           'static m()',
-          [ParserErrorCode.EXPECTED_TOKEN],
-          'static m();',
-          allFailing: true,
-          expectedErrorsInValidCode: [ParserErrorCode.MISSING_FUNCTION_BODY],
+          [ParserErrorCode.MISSING_FUNCTION_BODY],
+          'static m() {}',
         ),
         new TestDescriptor(
           'static_noType_params',
           'static m(b, c)',
-          [ParserErrorCode.EXPECTED_TOKEN],
-          'static m(b, c);',
-          allFailing: true,
-          expectedErrorsInValidCode: [ParserErrorCode.MISSING_FUNCTION_BODY],
+          [ParserErrorCode.MISSING_FUNCTION_BODY],
+          'static m(b, c) {}',
         ),
         new TestDescriptor(
           'static_noType_emptyOptional',
@@ -216,50 +245,53 @@
         new TestDescriptor(
           'static_type_leftParen',
           'static A m(',
-          [ParserErrorCode.EXPECTED_TOKEN],
-          'static A m();',
-          allFailing: true,
-          expectedErrorsInValidCode: [ParserErrorCode.MISSING_FUNCTION_BODY],
+          [
+            ScannerErrorCode.EXPECTED_TOKEN,
+            ParserErrorCode.MISSING_FUNCTION_BODY
+          ],
+          'static A m() {}',
+          failing: allExceptEof,
         ),
         new TestDescriptor(
           'static_type_paramName',
           'static A m(B',
-          [ParserErrorCode.EXPECTED_TOKEN],
-          'static A m(B);',
-          allFailing: true,
-          expectedErrorsInValidCode: [ParserErrorCode.MISSING_FUNCTION_BODY],
+          [
+            ScannerErrorCode.EXPECTED_TOKEN,
+            ParserErrorCode.MISSING_FUNCTION_BODY
+          ],
+          'static A m(B) {}',
+          failing: ['methodNonVoid', 'getter', 'setter'],
         ),
         new TestDescriptor(
           'static_type_paramTypeAndName',
           'static A m(B b',
-          [ParserErrorCode.EXPECTED_TOKEN],
-          'static A m(B b);',
-          allFailing: true,
-          expectedErrorsInValidCode: [ParserErrorCode.MISSING_FUNCTION_BODY],
+          [
+            ScannerErrorCode.EXPECTED_TOKEN,
+            ParserErrorCode.MISSING_FUNCTION_BODY
+          ],
+          'static A m(B b) {}',
         ),
         new TestDescriptor(
           'static_type_paramAndComma',
           'static A m(B b,',
-          [ParserErrorCode.MISSING_IDENTIFIER, ParserErrorCode.EXPECTED_TOKEN],
-          'static A m(B b, _s_);',
-          allFailing: true,
-          expectedErrorsInValidCode: [ParserErrorCode.MISSING_FUNCTION_BODY],
+          [
+            ScannerErrorCode.EXPECTED_TOKEN,
+            ParserErrorCode.MISSING_FUNCTION_BODY
+          ],
+          'static A m(B b) {}',
+          failing: allExceptEof,
         ),
         new TestDescriptor(
           'static_type_noParams',
           'static A m()',
-          [ParserErrorCode.EXPECTED_TOKEN],
-          'static A m();',
-          allFailing: true,
-          expectedErrorsInValidCode: [ParserErrorCode.MISSING_FUNCTION_BODY],
+          [ParserErrorCode.MISSING_FUNCTION_BODY],
+          'static A m() {}',
         ),
         new TestDescriptor(
           'static_type_params',
           'static A m(b, c)',
-          [ParserErrorCode.EXPECTED_TOKEN],
-          'static A m(b, c);',
-          allFailing: true,
-          expectedErrorsInValidCode: [ParserErrorCode.MISSING_FUNCTION_BODY],
+          [ParserErrorCode.MISSING_FUNCTION_BODY],
+          'static A m(b, c) {}',
         ),
         new TestDescriptor(
           'static_type_emptyOptional',
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 a4075e1..e0fdaec 100644
--- a/pkg/front_end/lib/src/fasta/parser/identifier_context.dart
+++ b/pkg/front_end/lib/src/fasta/parser/identifier_context.dart
@@ -60,9 +60,8 @@
 
   /// Identifier is a formal parameter being declared as part of a function,
   /// method, or typedef declaration.
-  static const formalParameterDeclaration = const IdentifierContext(
-      'formalParameterDeclaration',
-      inDeclaration: true);
+  static const formalParameterDeclaration =
+      const FormalParameterDeclarationIdentifierContext();
 
   /// Identifier is the start of a library name (e.g. `foo` in the directive
   /// 'library foo;`).
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 2beb97c..18dd095 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
@@ -298,6 +298,39 @@
   }
 }
 
+/// See [IdentifierContext.formalParameterDeclaration].
+class FormalParameterDeclarationIdentifierContext extends IdentifierContext {
+  const FormalParameterDeclarationIdentifierContext()
+      : super('formalParameterDeclaration', inDeclaration: true);
+
+  @override
+  Token ensureIdentifier(Token token, Parser parser) {
+    Token identifier = token.next;
+    assert(identifier.kind != IDENTIFIER_TOKEN);
+    if (identifier.isIdentifier) {
+      return identifier;
+    }
+
+    // Recovery
+    const followingValues = const [':', '=', ',', '(', ')', '[', ']', '{', '}'];
+    if (looksLikeStartOfNextClassMember(identifier) ||
+        looksLikeStartOfNextStatement(identifier) ||
+        isOneOfOrEof(identifier, followingValues)) {
+      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.importPrefixDeclaration].
 class ImportPrefixIdentifierContext extends IdentifierContext {
   const ImportPrefixIdentifierContext()
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index fc82a91..0264e50 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -1195,12 +1195,25 @@
       }
       token = parseFormalParameter(token, FormalParameterKind.mandatory, kind);
       next = token.next;
-      if (optional(',', next)) {
-        token = next;
-        continue;
+      if (!optional(',', next)) {
+        Token next = token.next;
+        if (optional(')', next)) {
+          token = next;
+        } else {
+          // Recovery
+          if (begin.endGroup.isSynthetic) {
+            // Scanner has already reported a missing `)` error,
+            // but placed the `)` in the wrong location, so move it.
+            token = rewriter.moveSynthetic(token, begin.endGroup);
+          } else {
+            reportRecoverableError(
+                next, fasta.templateExpectedButGot.withArguments(')'));
+            token = begin.endGroup;
+          }
+        }
+        break;
       }
-      token = ensureCloseParen(token, begin);
-      break;
+      token = next;
     }
     assert(optional(')', token));
     listener.endFormalParameters(parameterCount, begin, token, kind);
@@ -2013,8 +2026,6 @@
       followingValues = [';'];
     } else if (context == IdentifierContext.constructorReferenceContinuation) {
       followingValues = ['.', ',', '(', ')', '[', ']', '}', ';'];
-    } else if (context == IdentifierContext.formalParameterDeclaration) {
-      followingValues = [':', '=', ',', '(', ')', '[', ']', '{', '}'];
     } else if (context == IdentifierContext.labelDeclaration) {
       followingValues = [':'];
     } else if (context == IdentifierContext.literalSymbol ||
@@ -2041,8 +2052,6 @@
       return false;
     }
 
-    List<String> classMemberKeywords() =>
-        <String>['const', 'final', 'var', 'void'];
     List<String> statementKeywords() => <String>[
           'const',
           'do',
@@ -2054,19 +2063,6 @@
           'void',
           'while'
         ];
-    List<String> topLevelKeywords() => <String>[
-          'class',
-          'const',
-          'enum',
-          'export',
-          'final',
-          'import',
-          'library',
-          'part',
-          'typedef',
-          'var',
-          'void'
-        ];
 
     // TODO(brianwilkerson): At the moment, this test is entirely based on data
     // that can be represented declaratively. If that proves to be sufficient,
@@ -2074,12 +2070,7 @@
     // could create a method to test whether a given token matches one of the
     // patterns.
     List<String> initialKeywords;
-    if (context == IdentifierContext.formalParameterDeclaration) {
-      initialKeywords = topLevelKeywords()
-        ..addAll(classMemberKeywords())
-        ..addAll(statementKeywords())
-        ..add('covariant');
-    } else if (context == IdentifierContext.labelDeclaration) {
+    if (context == IdentifierContext.labelDeclaration) {
       initialKeywords = statementKeywords();
     } else if (context == IdentifierContext.localAccessorDeclaration) {
       initialKeywords = statementKeywords();