Improve parse literal string recovery

This fixes another fasta parser exception
and improves recovery when parsing string literals.

Change-Id: I704b7fcb9dcb2cd01e75623a179f95e6097ccec4
Reviewed-on: https://dart-review.googlesource.com/63000
Commit-Queue: Dan Rubel <danrubel@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index fd94be1..2363720 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -1843,18 +1843,27 @@
   }
 
   Token parseStringPart(Token token) {
-    token = token.next;
-    while (token.kind != STRING_TOKEN) {
-      if (token is ErrorToken) {
-        reportErrorToken(token, true);
-      } else {
-        token = reportUnrecoverableErrorWithToken(
-            token, fasta.templateExpectedString);
+    Token next = token.next;
+    if (next.kind != STRING_TOKEN) {
+      bool errorReported = false;
+      while (next is ErrorToken) {
+        errorReported = true;
+        reportErrorToken(next, true);
+        token = next;
+        next = token.next;
       }
-      token = token.next;
+      if (next.kind != STRING_TOKEN) {
+        if (!errorReported) {
+          reportRecoverableErrorWithToken(next, fasta.templateExpectedString);
+        }
+        next = rewriter
+            .insertTokenAfter(token,
+                new SyntheticStringToken(TokenType.STRING, '', next.charOffset))
+            .next;
+      }
     }
-    listener.handleStringPart(token);
-    return token;
+    listener.handleStringPart(next);
+    return next;
   }
 
   /// Insert a synthetic identifier after the given [token] and create an error
diff --git a/pkg/front_end/testcases/regress/issue_29976.dart.direct.expect b/pkg/front_end/testcases/regress/issue_29976.dart.direct.expect
index d2f376d..3c4327b 100644
--- a/pkg/front_end/testcases/regress/issue_29976.dart.direct.expect
+++ b/pkg/front_end/testcases/regress/issue_29976.dart.direct.expect
@@ -1,8 +1,21 @@
 library;
 import self as self;
+import "dart:core" as core;
 
-static method #main() → dynamic {
-  throw "pkg/front_end/testcases/regress/issue_29976.dart:8:3: Error: Expected a String, but got ')'.
+static const field dynamic #errors = const <dynamic>["pkg/front_end/testcases/regress/issue_29976.dart:7:14: Error: The non-ASCII character '\u0233' (U+00E9) can't be used in identifiers, only in strings and comments.
+Try using an US-ASCII letter, a digit, '_' (an underscore), or '\$' (a dollar sign).
+    \"x\${x*\"'\"\u0233'}x
+             ^", "pkg/front_end/testcases/regress/issue_29976.dart:7:15: Error: String starting with ' must end with '.
+    \"x\${x*\"'\"\u0233'}x
+              ^", "pkg/front_end/testcases/regress/issue_29976.dart:7:7: Error: Can't find '}' to match '\${'.
+    \"x\${x*\"'\"\u0233'}x
+      ^", "pkg/front_end/testcases/regress/issue_29976.dart:10:1: Error: String starting with \" must end with \".", "pkg/front_end/testcases/regress/issue_29976.dart:10:1: Error: Expected a declaration, but got ''.", "pkg/front_end/testcases/regress/issue_29976.dart:7:14: Error: Expected '}' before this.
+    \"x\${x*\"'\"\u0233'}x
+             ^", "pkg/front_end/testcases/regress/issue_29976.dart:8:3: Error: Expected a String, but got ')'.
   )
-  ^";
+  ^", "pkg/front_end/testcases/regress/issue_29976.dart:9:1: Error: Expected ';' before this.
+}
+^"]/* from null */;
+static method main() → void {
+  throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#f, 32, const <core::Type>[], core::List::unmodifiable<dynamic>(<dynamic>["x${(let dynamic _ = null in throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#x, 33, const <core::Type>[], const <dynamic>[], core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{})))).*("'")}"]), core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{})));
 }
diff --git a/pkg/front_end/testcases/regress/issue_29976.dart.direct.transformed.expect b/pkg/front_end/testcases/regress/issue_29976.dart.direct.transformed.expect
index d2f376d..3c4327b 100644
--- a/pkg/front_end/testcases/regress/issue_29976.dart.direct.transformed.expect
+++ b/pkg/front_end/testcases/regress/issue_29976.dart.direct.transformed.expect
@@ -1,8 +1,21 @@
 library;
 import self as self;
+import "dart:core" as core;
 
-static method #main() → dynamic {
-  throw "pkg/front_end/testcases/regress/issue_29976.dart:8:3: Error: Expected a String, but got ')'.
+static const field dynamic #errors = const <dynamic>["pkg/front_end/testcases/regress/issue_29976.dart:7:14: Error: The non-ASCII character '\u0233' (U+00E9) can't be used in identifiers, only in strings and comments.
+Try using an US-ASCII letter, a digit, '_' (an underscore), or '\$' (a dollar sign).
+    \"x\${x*\"'\"\u0233'}x
+             ^", "pkg/front_end/testcases/regress/issue_29976.dart:7:15: Error: String starting with ' must end with '.
+    \"x\${x*\"'\"\u0233'}x
+              ^", "pkg/front_end/testcases/regress/issue_29976.dart:7:7: Error: Can't find '}' to match '\${'.
+    \"x\${x*\"'\"\u0233'}x
+      ^", "pkg/front_end/testcases/regress/issue_29976.dart:10:1: Error: String starting with \" must end with \".", "pkg/front_end/testcases/regress/issue_29976.dart:10:1: Error: Expected a declaration, but got ''.", "pkg/front_end/testcases/regress/issue_29976.dart:7:14: Error: Expected '}' before this.
+    \"x\${x*\"'\"\u0233'}x
+             ^", "pkg/front_end/testcases/regress/issue_29976.dart:8:3: Error: Expected a String, but got ')'.
   )
-  ^";
+  ^", "pkg/front_end/testcases/regress/issue_29976.dart:9:1: Error: Expected ';' before this.
+}
+^"]/* from null */;
+static method main() → void {
+  throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#f, 32, const <core::Type>[], core::List::unmodifiable<dynamic>(<dynamic>["x${(let dynamic _ = null in throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#x, 33, const <core::Type>[], const <dynamic>[], core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{})))).*("'")}"]), core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{})));
 }
diff --git a/pkg/front_end/testcases/regress/issue_29976.dart.strong.expect b/pkg/front_end/testcases/regress/issue_29976.dart.strong.expect
index d2f376d..75e88e2 100644
--- a/pkg/front_end/testcases/regress/issue_29976.dart.strong.expect
+++ b/pkg/front_end/testcases/regress/issue_29976.dart.strong.expect
@@ -1,8 +1,25 @@
 library;
 import self as self;
+import "dart:core" as core;
 
-static method #main() → dynamic {
-  throw "pkg/front_end/testcases/regress/issue_29976.dart:8:3: Error: Expected a String, but got ')'.
+static const field dynamic #errors = const <dynamic>["pkg/front_end/testcases/regress/issue_29976.dart:7:14: Error: The non-ASCII character '\u0233' (U+00E9) can't be used in identifiers, only in strings and comments.
+Try using an US-ASCII letter, a digit, '_' (an underscore), or '\$' (a dollar sign).
+    \"x\${x*\"'\"\u0233'}x
+             ^", "pkg/front_end/testcases/regress/issue_29976.dart:7:15: Error: String starting with ' must end with '.
+    \"x\${x*\"'\"\u0233'}x
+              ^", "pkg/front_end/testcases/regress/issue_29976.dart:7:7: Error: Can't find '}' to match '\${'.
+    \"x\${x*\"'\"\u0233'}x
+      ^", "pkg/front_end/testcases/regress/issue_29976.dart:10:1: Error: String starting with \" must end with \".", "pkg/front_end/testcases/regress/issue_29976.dart:10:1: Error: Expected a declaration, but got ''.", "pkg/front_end/testcases/regress/issue_29976.dart:7:9: Error: Getter not found: 'x'.
+    \"x\${x*\"'\"\u0233'}x
+        ^", "pkg/front_end/testcases/regress/issue_29976.dart:7:14: Error: Expected '}' before this.
+    \"x\${x*\"'\"\u0233'}x
+             ^", "pkg/front_end/testcases/regress/issue_29976.dart:8:3: Error: Expected a String, but got ')'.
   )
-  ^";
+  ^", "pkg/front_end/testcases/regress/issue_29976.dart:6:3: Error: Method not found: 'f'.
+  f(
+  ^", "pkg/front_end/testcases/regress/issue_29976.dart:9:1: Error: Expected ';' before this.
+}
+^"]/* from null */;
+static method main() → void {
+  throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#f, 32, const <core::Type>[], core::List::unmodifiable<dynamic>(<dynamic>["x${(let dynamic _ = null in throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#x, 33, const <core::Type>[], const <dynamic>[], core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{})))).*("'")}"]), core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{})));
 }
diff --git a/pkg/front_end/testcases/strong.status b/pkg/front_end/testcases/strong.status
index d0d42f8..6c44f09 100644
--- a/pkg/front_end/testcases/strong.status
+++ b/pkg/front_end/testcases/strong.status
@@ -196,7 +196,7 @@
 rasta/unsupported_platform_library: Fail
 
 regress/issue_29975: Fail # Issue 29975.
-regress/issue_29976: RuntimeError # Issue 29976.
+regress/issue_29976: TypeCheckError # Issue 29976.
 regress/issue_29982: Fail # Issue 29982.
 regress/issue_30836: RuntimeError # Issue 30836.
 regress/issue_31184: TypeCheckError