Improve literal list recovery

Change-Id: Ie0e7934d139e84b96add2f0e56051295849ce1bc
Reviewed-on: https://dart-review.googlesource.com/58580
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
diff --git a/pkg/analyzer/test/src/fasta/recovery/missing_code_test.dart b/pkg/analyzer/test/src/fasta/recovery/missing_code_test.dart
index d312b78..492f026 100644
--- a/pkg/analyzer/test/src/fasta/recovery/missing_code_test.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/missing_code_test.dart
@@ -29,7 +29,6 @@
 ''');
   }
 
-  @failingTest
   void test_missingComma() {
     testRecovery('''
 f() => [a, b c];
diff --git a/pkg/analyzer/test/src/fasta/recovery/paired_tokens_test.dart b/pkg/analyzer/test/src/fasta/recovery/paired_tokens_test.dart
index fdd1f54..edc6d29 100644
--- a/pkg/analyzer/test/src/fasta/recovery/paired_tokens_test.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/paired_tokens_test.dart
@@ -238,9 +238,7 @@
 ''');
   }
 
-  @failingTest
   void test_listLiteral_inner_last() {
-    // Parser crashes
     testRecovery('''
 var x = [[0], [1];
 ''', [ScannerErrorCode.EXPECTED_TOKEN], '''
@@ -248,13 +246,19 @@
 ''');
   }
 
-  @failingTest
   void test_listLiteral_inner_notLast() {
-    // Parser crashes
     testRecovery('''
 var x = [[0], [1, [2]];
 ''', [ScannerErrorCode.EXPECTED_TOKEN], '''
-var x = [[0], [1], [2]];
+var x = [[0], [1, [2]]];
+''');
+  }
+
+  void test_listLiteral_missing_comma() {
+    testRecovery('''
+var x = [0 1];
+''', [ParserErrorCode.EXPECTED_TOKEN], '''
+var x = [0, 1];
 ''');
   }
 
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index 24cce44..b1e9a0a 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -4279,25 +4279,55 @@
     Token beginToken = token = token.next;
     assert(optional('[', token) || optional('[]', token));
     int count = 0;
-    if (optional('[', token)) {
-      bool old = mayParseFunctionExpressions;
-      mayParseFunctionExpressions = true;
-      do {
-        if (optional(']', token.next)) {
-          token = token.next;
+    if (optional('[]', token)) {
+      token = rewriteSquareBrackets(beforeToken).next;
+      listener.handleLiteralList(0, token, constKeyword, token.next);
+      return token.next;
+    }
+    bool old = mayParseFunctionExpressions;
+    mayParseFunctionExpressions = true;
+    while (true) {
+      Token next = token.next;
+      if (optional(']', next)) {
+        token = next;
+        break;
+      }
+      token = parseExpression(token);
+      next = token.next;
+      ++count;
+      if (!optional(',', next)) {
+        if (optional(']', next)) {
+          token = next;
           break;
         }
-        token = parseExpression(token).next;
-        ++count;
-      } while (optional(',', token));
-      mayParseFunctionExpressions = old;
-      listener.handleLiteralList(count, beginToken, constKeyword, token);
-      expect(']', token);
-      return token;
+
+        // Recovery
+        if (!isExpressionStartForRecovery(next)) {
+          if (beginToken.endGroup.isSynthetic) {
+            // The scanner has already reported an error,
+            // but inserted `]` in the wrong place.
+            token = rewriter.moveSynthetic(token, beginToken.endGroup);
+          } else {
+            // Report an error and jump to the end of the list.
+            reportRecoverableError(
+                next, fasta.templateExpectedButGot.withArguments(']'));
+            token = beginToken.endGroup;
+          }
+          break;
+        }
+        // This looks like the start of an expression.
+        // Report an error, insert the comma, and continue parsing.
+        next = rewriteAndRecover(
+                token,
+                fasta.templateExpectedButGot.withArguments(','),
+                new SyntheticToken(TokenType.COMMA, next.offset))
+            .next;
+      }
+      token = next;
     }
-    token = rewriteSquareBrackets(beforeToken).next;
-    listener.handleLiteralList(0, token, constKeyword, token.next);
-    return token.next;
+    mayParseFunctionExpressions = old;
+    listener.handleLiteralList(count, beginToken, constKeyword, token);
+    return token;
   }
 
   /// This method parses the portion of a map literal that starts with the left