Improve fasta parser conditional import recovery

Change-Id: I5479078d8d2c3de10b74ed04001fdd852a427c5c
Reviewed-on: https://dart-review.googlesource.com/51561
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
diff --git a/pkg/analyzer/lib/src/dart/ast/utilities.dart b/pkg/analyzer/lib/src/dart/ast/utilities.dart
index 3156fe9..56eb475 100644
--- a/pkg/analyzer/lib/src/dart/ast/utilities.dart
+++ b/pkg/analyzer/lib/src/dart/ast/utilities.dart
@@ -1667,6 +1667,7 @@
         _isEqualNodeLists(node.metadata, other.metadata) &&
         isEqualTokens(node.keyword, other.keyword) &&
         isEqualNodes(node.uri, other.uri) &&
+        _isEqualNodeLists(node.configurations, other.configurations) &&
         isEqualTokens(node.deferredKeyword, other.deferredKeyword) &&
         isEqualTokens(node.asKeyword, other.asKeyword) &&
         isEqualNodes(node.prefix, other.prefix) &&
diff --git a/pkg/analyzer/test/src/fasta/recovery/partial_code/import_directive_test.dart b/pkg/analyzer/test/src/fasta/recovery/partial_code/import_directive_test.dart
index d3764ef..eb6dfc6 100644
--- a/pkg/analyzer/test/src/fasta/recovery/partial_code/import_directive_test.dart
+++ b/pkg/analyzer/test/src/fasta/recovery/partial_code/import_directive_test.dart
@@ -12,6 +12,8 @@
 
 class ImportDirectivesTest extends PartialCodeTest {
   buildAll() {
+    List<String> allExceptEof =
+        PartialCodeTest.prePartSuffixes.map((t) => t.name).toList();
     buildTests(
         'import_directive',
         [
@@ -25,6 +27,55 @@
               [ParserErrorCode.EXPECTED_TOKEN], "import '';"),
           new TestDescriptor('fullUri', "import 'a.dart'",
               [ParserErrorCode.EXPECTED_TOKEN], "import 'a.dart';"),
+          new TestDescriptor(
+              'if',
+              "import 'a.dart' if",
+              [
+                ParserErrorCode.EXPECTED_TOKEN,
+                ParserErrorCode.EXPECTED_TOKEN,
+                ParserErrorCode.EXPECTED_STRING_LITERAL
+              ],
+              "import 'a.dart' if (_s_) '';"),
+          new TestDescriptor(
+              'ifParen',
+              "import 'a.dart' if (",
+              [
+                ParserErrorCode.MISSING_IDENTIFIER,
+                ScannerErrorCode.EXPECTED_TOKEN,
+                ParserErrorCode.EXPECTED_STRING_LITERAL,
+                ParserErrorCode.EXPECTED_TOKEN
+              ],
+              "import 'a.dart' if (_s_) '';",
+              failing: allExceptEof),
+          new TestDescriptor(
+              'ifId',
+              "import 'a.dart' if (b",
+              [
+                ScannerErrorCode.EXPECTED_TOKEN,
+                ParserErrorCode.EXPECTED_TOKEN,
+                ParserErrorCode.EXPECTED_STRING_LITERAL
+              ],
+              "import 'a.dart' if (b) '';",
+              failing: allExceptEof),
+          new TestDescriptor(
+              'ifEquals',
+              "import 'a.dart' if (b ==",
+              [
+                ParserErrorCode.EXPECTED_STRING_LITERAL,
+                ParserErrorCode.EXPECTED_TOKEN,
+                ScannerErrorCode.EXPECTED_TOKEN,
+                ParserErrorCode.EXPECTED_STRING_LITERAL
+              ],
+              "import 'a.dart' if (b == '') '';",
+              failing: allExceptEof),
+          new TestDescriptor(
+              'ifCondition',
+              "import 'a.dart' if (b)",
+              [
+                ParserErrorCode.EXPECTED_TOKEN,
+                ParserErrorCode.EXPECTED_STRING_LITERAL
+              ],
+              "import 'a.dart' if (b) '';"),
         ],
         PartialCodeTest.prePartSuffixes);
   }
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 d17a6a1..8922d33 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
@@ -68,7 +68,7 @@
     }
 
     if (looksLikeStartOfNextDeclaration(identifier) ||
-        isOneOfOrEof(identifier, const ['.', '=='])) {
+        isOneOfOrEof(identifier, const ['.', '==', ')'])) {
       identifier = parser.insertSyntheticIdentifier(token, this,
           message: fasta.templateExpectedIdentifier.withArguments(identifier));
     } else {
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index 0eb697f..394e0d5 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -753,17 +753,50 @@
   /// ```
   Token parseConditionalUri(Token token) {
     Token ifKeyword = token = token.next;
-    listener.beginConditionalUri(ifKeyword);
-    token = expect('if', token);
-    Token leftParen = token;
-    expect('(', token);
-    token = parseDottedName(token).next;
-    Token equalitySign;
-    if (optional('==', token)) {
-      equalitySign = token;
-      token = ensureLiteralString(token).next;
+    assert(optional('if', token));
+    listener.beginConditionalUri(token);
+    Token leftParen = token.next;
+    if (!optional('(', leftParen)) {
+      reportRecoverableError(
+          leftParen, fasta.templateExpectedButGot.withArguments('('));
+
+      int offset = leftParen.charOffset;
+      BeginToken openParen =
+          new SyntheticBeginToken(TokenType.OPEN_PAREN, offset);
+      Token next = openParen
+          .setNext(new SyntheticStringToken(TokenType.IDENTIFIER, '', offset));
+      next = next.setNext(new SyntheticToken(TokenType.CLOSE_PAREN, offset));
+      openParen.endGroup = next;
+
+      token.setNext(openParen);
+      next.setNext(leftParen);
+      leftParen = openParen;
     }
-    expect(')', token);
+    token = leftParen;
+    token = parseDottedName(token);
+    Token next = token.next;
+    Token equalitySign;
+    if (optional('==', next)) {
+      equalitySign = next;
+      token = ensureLiteralString(next);
+      next = token.next;
+    }
+    if (next != leftParen.endGroup) {
+      reportRecoverableErrorWithToken(next, fasta.templateUnexpectedToken);
+      Token endGroup = leftParen.endGroup;
+      if (endGroup.isSynthetic) {
+        // The scanner did not place the synthetic ')' correctly, so move it.
+
+        // TODO(danrubel): Its costly to find the token before the endGroup.
+        // Consider beforeSynthetic field that points to the previous token
+        // only for synthetic tokens such as ')', '}', ']' so that the parser
+        // can easily move these to the correct location.
+      }
+      next = endGroup;
+    }
+    token = next;
+    assert(optional(')', token));
+
     token = ensureLiteralString(token);
     listener.endConditionalUri(ifKeyword, leftParen, equalitySign);
     return token;
@@ -3175,7 +3208,7 @@
     if (!identical(next.kind, STRING_TOKEN)) {
       Message message = fasta.templateExpectedString.withArguments(next);
       Token newToken =
-          new SyntheticStringToken(TokenType.STRING, '""', token.charOffset, 0);
+          new SyntheticStringToken(TokenType.STRING, '""', next.charOffset, 0);
       rewriteAndRecover(token, message, newToken);
     }
     return parseLiteralString(token);