Improve fasta parser try/catch error messages and recovery

Change-Id: I6e591fa5d03c73aa6f3e8060f2ca310f68be9a78
Reviewed-on: https://dart-review.googlesource.com/48709
Commit-Queue: Dan Rubel <danrubel@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart
index 6b1d4d1..1d0416c 100644
--- a/pkg/analyzer/lib/error/error.dart
+++ b/pkg/analyzer/lib/error/error.dart
@@ -321,6 +321,7 @@
   ParserErrorCode.ANNOTATION_ON_ENUM_CONSTANT,
   ParserErrorCode.ASYNC_KEYWORD_USED_AS_IDENTIFIER,
   ParserErrorCode.BREAK_OUTSIDE_OF_LOOP,
+  ParserErrorCode.CATCH_SYNTAX,
   ParserErrorCode.CLASS_IN_CLASS,
   ParserErrorCode.COLON_IN_PLACE_OF_IN,
   ParserErrorCode.CONSTRUCTOR_WITH_RETURN_TYPE,
diff --git a/pkg/analyzer/lib/src/dart/error/syntactic_errors.dart b/pkg/analyzer/lib/src/dart/error/syntactic_errors.dart
index 98e8ea6..87d69d3 100644
--- a/pkg/analyzer/lib/src/dart/error/syntactic_errors.dart
+++ b/pkg/analyzer/lib/src/dart/error/syntactic_errors.dart
@@ -67,6 +67,12 @@
       "A break statement can't be used outside of a loop or switch statement.",
       correction: "Try removing the break statement.");
 
+  static const ParserErrorCode CATCH_SYNTAX = const ParserErrorCode(
+      'CATCH_SYNTAX',
+      "'catch' must be followed by '(identifier)' or '(identifier, identifier)'.",
+      correction:
+          "No types are needed, the first is given by 'on', the second is always 'StackTrace'.");
+
   static const ParserErrorCode CLASS_IN_CLASS = const ParserErrorCode(
       'CLASS_IN_CLASS', "Classes can't be declared inside other classes.",
       correction: "Try moving the class to the top-level.");
diff --git a/pkg/analyzer/lib/src/fasta/error_converter.dart b/pkg/analyzer/lib/src/fasta/error_converter.dart
index c517b31..8fe08ec 100644
--- a/pkg/analyzer/lib/src/fasta/error_converter.dart
+++ b/pkg/analyzer/lib/src/fasta/error_converter.dart
@@ -52,6 +52,10 @@
             length,
             [lexeme()]);
         return;
+      case "CATCH_SYNTAX":
+        errorReporter?.reportErrorForOffset(
+            ParserErrorCode.CATCH_SYNTAX, offset, length);
+        return;
       case "CLASS_IN_CLASS":
         errorReporter?.reportErrorForOffset(
             ParserErrorCode.CLASS_IN_CLASS, offset, length);
diff --git a/pkg/analyzer/test/generated/parser_test.dart b/pkg/analyzer/test/generated/parser_test.dart
index 18fa2ba..ef46fe2 100644
--- a/pkg/analyzer/test/generated/parser_test.dart
+++ b/pkg/analyzer/test/generated/parser_test.dart
@@ -15196,6 +15196,77 @@
     expect(statement.finallyBlock, isNull);
   }
 
+  void test_parseTryStatement_catch_error_missingCatchParam() {
+    var statement = parseStatement('try {} catch () {}') as TryStatement;
+    listener.assertErrors(usingFastaParser
+        ? [expectedError(ParserErrorCode.CATCH_SYNTAX, 14, 1)]
+        : [
+            expectedError(ParserErrorCode.MISSING_IDENTIFIER, 14, 1),
+          ]);
+    expect(statement.tryKeyword, isNotNull);
+    expect(statement.body, isNotNull);
+    NodeList<CatchClause> catchClauses = statement.catchClauses;
+    expect(catchClauses, hasLength(1));
+    CatchClause clause = catchClauses[0];
+    expect(clause.onKeyword, isNull);
+    expect(clause.exceptionType, isNull);
+    expect(clause.catchKeyword, isNotNull);
+    expect(clause.exceptionParameter, isNotNull);
+    expect(clause.comma, isNull);
+    expect(clause.stackTraceParameter, isNull);
+    expect(clause.body, isNotNull);
+    expect(statement.finallyKeyword, isNull);
+    expect(statement.finallyBlock, isNull);
+  }
+
+  void test_parseTryStatement_catch_error_missingCatchTrace() {
+    var statement = parseStatement('try {} catch (e,) {}') as TryStatement;
+    listener.assertErrors(usingFastaParser
+        ? [expectedError(ParserErrorCode.CATCH_SYNTAX, 14, 1)]
+        : [
+            expectedError(ParserErrorCode.MISSING_IDENTIFIER, 14, 1),
+          ]);
+    expect(statement.tryKeyword, isNotNull);
+    expect(statement.body, isNotNull);
+    NodeList<CatchClause> catchClauses = statement.catchClauses;
+    expect(catchClauses, hasLength(1));
+    CatchClause clause = catchClauses[0];
+    expect(clause.onKeyword, isNull);
+    expect(clause.exceptionType, isNull);
+    expect(clause.catchKeyword, isNotNull);
+    expect(clause.exceptionParameter, isNotNull);
+    expect(clause.comma, isNotNull);
+    expect(clause.stackTraceParameter, isNotNull);
+    expect(clause.body, isNotNull);
+    expect(statement.finallyKeyword, isNull);
+    expect(statement.finallyBlock, isNull);
+  }
+
+  void test_parseTryStatement_catch_error_missingCatchParen() {
+    var statement = parseStatement('try {} catch {}') as TryStatement;
+    listener.assertErrors(usingFastaParser
+        ? [expectedError(ParserErrorCode.CATCH_SYNTAX, 13, 1)]
+        : [
+            expectedError(ParserErrorCode.EXPECTED_TOKEN, 13, 1),
+            expectedError(ParserErrorCode.MISSING_IDENTIFIER, 13, 1),
+            expectedError(ParserErrorCode.EXPECTED_TOKEN, 13, 1)
+          ]);
+    expect(statement.tryKeyword, isNotNull);
+    expect(statement.body, isNotNull);
+    NodeList<CatchClause> catchClauses = statement.catchClauses;
+    expect(catchClauses, hasLength(1));
+    CatchClause clause = catchClauses[0];
+    expect(clause.onKeyword, isNull);
+    expect(clause.exceptionType, isNull);
+    expect(clause.catchKeyword, isNotNull);
+    expect(clause.exceptionParameter, isNotNull);
+    expect(clause.comma, isNull);
+    expect(clause.stackTraceParameter, isNull);
+    expect(clause.body, isNotNull);
+    expect(statement.finallyKeyword, isNull);
+    expect(statement.finallyBlock, isNull);
+  }
+
   void test_parseTryStatement_catch_finally() {
     var statement =
         parseStatement('try {} catch (e, s) {} finally {}') as TryStatement;
diff --git a/pkg/analyzer/test/src/fasta/recovery/partial_code/try_statement_test.dart b/pkg/analyzer/test/src/fasta/recovery/partial_code/try_statement_test.dart
index 8b7046d..af85dc1 100644
--- a/pkg/analyzer/test/src/fasta/recovery/partial_code/try_statement_test.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/partial_code/try_statement_test.dart
@@ -53,23 +53,19 @@
           new TestDescriptor(
               'catch',
               'try {} catch',
-              [
-                ParserErrorCode.EXPECTED_TOKEN,
-                ParserErrorCode.MISSING_IDENTIFIER,
-                ParserErrorCode.EXPECTED_TOKEN
-              ],
+              [ParserErrorCode.CATCH_SYNTAX, ParserErrorCode.EXPECTED_TOKEN],
               "try {} catch (e) {}",
-              allFailing: true),
+              failing: ['block']),
           new TestDescriptor(
               'catch_leftParen',
               'try {} catch (',
               [
-                ParserErrorCode.MISSING_IDENTIFIER,
-                ParserErrorCode.EXPECTED_TOKEN,
+                ScannerErrorCode.EXPECTED_TOKEN,
+                ParserErrorCode.CATCH_SYNTAX,
                 ParserErrorCode.EXPECTED_TOKEN
               ],
               "try {} catch (e) {}",
-              allFailing: true),
+              failing: allExceptEof),
           new TestDescriptor(
               'catch_identifier',
               'try {} catch (e',
@@ -80,12 +76,12 @@
               'catch_identifierComma',
               'try {} catch (e, ',
               [
-                ParserErrorCode.MISSING_IDENTIFIER,
+                ParserErrorCode.CATCH_SYNTAX,
                 ParserErrorCode.EXPECTED_TOKEN,
-                ParserErrorCode.EXPECTED_TOKEN
+                ScannerErrorCode.EXPECTED_TOKEN
               ],
               "try {} catch (e, _s_) {}",
-              allFailing: true),
+              failing: allExceptEof),
           new TestDescriptor(
               'catch_identifierCommaIdentifier',
               'try {} catch (e, s',
@@ -101,23 +97,19 @@
           new TestDescriptor(
               'on_catch',
               'try {} on A catch',
-              [
-                ParserErrorCode.EXPECTED_TOKEN,
-                ParserErrorCode.MISSING_IDENTIFIER,
-                ParserErrorCode.EXPECTED_TOKEN
-              ],
+              [ParserErrorCode.CATCH_SYNTAX, ParserErrorCode.EXPECTED_TOKEN],
               "try {} on A catch (e) {}",
-              allFailing: true),
+              failing: ['block']),
           new TestDescriptor(
               'on_catch_leftParen',
               'try {} on A catch (',
               [
-                ParserErrorCode.MISSING_IDENTIFIER,
+                ParserErrorCode.CATCH_SYNTAX,
                 ParserErrorCode.EXPECTED_TOKEN,
-                ParserErrorCode.EXPECTED_TOKEN
+                ScannerErrorCode.EXPECTED_TOKEN
               ],
               "try {} on A catch (e) {}",
-              allFailing: true),
+              failing: allExceptEof),
           new TestDescriptor(
               'on_catch_identifier',
               'try {} on A catch (e',
@@ -128,12 +120,12 @@
               'on_catch_identifierComma',
               'try {} on A catch (e, ',
               [
-                ParserErrorCode.MISSING_IDENTIFIER,
+                ParserErrorCode.CATCH_SYNTAX,
                 ParserErrorCode.EXPECTED_TOKEN,
-                ParserErrorCode.EXPECTED_TOKEN
+                ScannerErrorCode.EXPECTED_TOKEN
               ],
               "try {} on A catch (e, _s_) {}",
-              allFailing: true),
+              failing: allExceptEof),
           new TestDescriptor(
               'on_catch_identifierCommaIdentifier',
               'try {} on A catch (e, s',
diff --git a/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart b/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart
index d351d58..725203e 100644
--- a/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart
+++ b/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart
@@ -480,6 +480,7 @@
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const MessageCode messageCatchSyntax = const MessageCode("CatchSyntax",
+    analyzerCode: "CATCH_SYNTAX",
     dart2jsCode: "*ignored*",
     message:
         r"""'catch' must be followed by '(identifier)' or '(identifier, identifier)'.""",
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index 66a831e..8f72afc 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -6009,29 +6009,51 @@
       Token comma = null;
       if (identical(value, 'catch')) {
         catchKeyword = token;
+
         Token openParens = catchKeyword.next;
-        Token exceptionName = openParens.next;
-        Token commaOrCloseParens = exceptionName.next;
-        Token traceName = commaOrCloseParens.next;
-        Token closeParens = traceName.next;
         if (!optional("(", openParens)) {
-          // Handled below by parseFormalParameters.
-        } else if (!exceptionName.isIdentifier) {
+          reportRecoverableError(openParens, fasta.messageCatchSyntax);
+          BeginToken open = new SyntheticBeginToken(
+              TokenType.OPEN_PAREN, openParens.charOffset);
+          Token identifier = open.setNext(new SyntheticStringToken(
+              TokenType.IDENTIFIER, '', openParens.charOffset));
+          Token close = identifier.setNext(
+              new SyntheticToken(TokenType.CLOSE_PAREN, openParens.charOffset));
+          open.endGroup = close;
+          rewriter.insertTokenAfter(catchKeyword, open);
+          openParens = open;
+        }
+
+        Token exceptionName = openParens.next;
+        if (!exceptionName.isIdentifier) {
           reportRecoverableError(exceptionName, fasta.messageCatchSyntax);
-        } else if (optional(")", commaOrCloseParens)) {
+          if (!exceptionName.isKeywordOrIdentifier) {
+            exceptionName = new SyntheticStringToken(
+                TokenType.IDENTIFIER, '', exceptionName.charOffset, 0);
+            rewriter.insertTokenAfter(openParens, exceptionName);
+          }
+        }
+
+        Token commaOrCloseParens = exceptionName.next;
+        if (optional(")", commaOrCloseParens)) {
           // OK: `catch (identifier)`.
         } else if (!optional(",", commaOrCloseParens)) {
           reportRecoverableError(exceptionName, fasta.messageCatchSyntax);
         } else {
           comma = commaOrCloseParens;
+          Token traceName = comma.next;
           if (!traceName.isIdentifier) {
             reportRecoverableError(exceptionName, fasta.messageCatchSyntax);
-          } else if (!optional(")", closeParens)) {
+            if (!traceName.isKeywordOrIdentifier) {
+              traceName = new SyntheticStringToken(
+                  TokenType.IDENTIFIER, '', traceName.charOffset, 0);
+              rewriter.insertTokenAfter(comma, traceName);
+            }
+          } else if (!optional(")", traceName.next)) {
             reportRecoverableError(exceptionName, fasta.messageCatchSyntax);
           }
         }
-        lastConsumed =
-            parseFormalParametersRequiredOpt(token, MemberKind.Catch);
+        lastConsumed = parseFormalParameters(catchKeyword, MemberKind.Catch);
         token = lastConsumed.next;
       }
       listener.endCatchClause(token);
diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status
index e93ba87..01a8aa0 100644
--- a/pkg/front_end/messages.status
+++ b/pkg/front_end/messages.status
@@ -40,8 +40,6 @@
 CantInferTypeDueToInconsistentOverrides/example: Fail
 CantUseSuperBoundedTypeForInstanceCreation/analyzerCode: Fail
 CantUseSuperBoundedTypeForInstanceCreation/example: Fail
-CatchSyntax/analyzerCode: Fail
-CatchSyntax/example: Fail
 ColonInPlaceOfIn/example: Fail
 ConflictsWithConstructor/analyzerCode: Fail
 ConflictsWithConstructor/example: Fail
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index 295071e..dcdc6d1 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -927,7 +927,12 @@
 CatchSyntax:
   template: "'catch' must be followed by '(identifier)' or '(identifier, identifier)'."
   tip: "No types are needed, the first is given by 'on', the second is always 'StackTrace'."
+  analyzerCode: CATCH_SYNTAX
   dart2jsCode: "*ignored*"
+  statement:
+    - "try {} catch {}"
+    - "try {} catch () {}"
+    - "try {} catch (e,) {}"
 
 SuperNullAware:
   template: "'super' can't be null."