From bfbc8f4ff45cd002bbe237264f50e36d90c8d328 Mon Sep 17 00:00:00 2001 From: "zundel@google.com" Date: Sat, 7 Apr 2012 17:25:37 +0000 Subject: [PATCH] Improve parser recovery inside of a block Review URL: https://chromiumcodereview.appspot.com//10024009 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@6305 260f80e4-7a28-3924-810f-c04153c831b5 --- .../dart/compiler/parser/DartParser.java | 107 ++++++----- .../compiler/parser/ParserRecoveryTest.java | 168 +++++++++++++++--- 2 files changed, 205 insertions(+), 70 deletions(-) diff --git a/compiler/java/com/google/dart/compiler/parser/DartParser.java b/compiler/java/com/google/dart/compiler/parser/DartParser.java index 2d6611da1b9..87042360b69 100644 --- a/compiler/java/com/google/dart/compiler/parser/DartParser.java +++ b/compiler/java/com/google/dart/compiler/parser/DartParser.java @@ -113,7 +113,7 @@ public class DartParser extends CompletionHooksParserBase { private final boolean isDietParse; private final Set prefixes; private final boolean corelibParse; - private Set errorHistory = new HashSet(); + private final Set errorHistory = new HashSet(); private boolean isParsingInterface; private boolean isTopLevelAbstract; private DartScanner.Position topLevelAbstractModifierPosition; @@ -161,7 +161,7 @@ public class DartParser extends CompletionHooksParserBase { STATIC_KEYWORD, TYPEDEF_KEYWORD }; - + public DartParser(Source source, String sourceCode, DartCompilerListener listener) { @@ -278,12 +278,12 @@ public class DartParser extends CompletionHooksParserBase { if (optional(Token.CLASS)) { isParsingClass = true; node = done(parseClass()); - } else if (peekPseudoKeyword(0, INTERFACE_KEYWORD) + } else if (peekPseudoKeyword(0, INTERFACE_KEYWORD) && peek(1).equals(Token.IDENTIFIER)) { consume(Token.IDENTIFIER); isParsingInterface = true; node = done(parseClass()); - } else if (peekPseudoKeyword(0, TYPEDEF_KEYWORD) + } else if (peekPseudoKeyword(0, TYPEDEF_KEYWORD) && (peek(1).equals(Token.IDENTIFIER) || peek(1).equals(Token.VOID))) { consume(Token.IDENTIFIER); node = done(parseFunctionTypeAlias()); @@ -329,7 +329,7 @@ public class DartParser extends CompletionHooksParserBase { || peekPseudoKeyword(n, INTERFACE_KEYWORD) || peekPseudoKeyword(n, TYPEDEF_KEYWORD); } - + /** * 'interface' and 'typedef' are valid to use as names of fields and methods, so you can't * just blindly recover when you see them in any context. This does a further test to make @@ -340,10 +340,10 @@ public class DartParser extends CompletionHooksParserBase { if (peek(0).equals(Token.CLASS)) { return true; } - if (peekPseudoKeyword(0, INTERFACE_KEYWORD) + if (peekPseudoKeyword(0, INTERFACE_KEYWORD) && peek(1).equals(Token.IDENTIFIER)) { return true; - } else if (peekPseudoKeyword(0, TYPEDEF_KEYWORD) + } else if (peekPseudoKeyword(0, TYPEDEF_KEYWORD) && (peek(1).equals(Token.IDENTIFIER) || peek(1).equals(Token.VOID))) { return true; } @@ -958,7 +958,7 @@ public class DartParser extends CompletionHooksParserBase { member = parseMethodOrAccessor(modifiers, null); break; } - + member = parseFieldDeclaration(modifiers, null); expectStatmentTerminator(); break; @@ -990,13 +990,13 @@ public class DartParser extends CompletionHooksParserBase { && peek(1) != Token.ASSIGN && peek(1) != Token.SEMICOLON) { type = parseTypeAnnotation(); - + // Check again for malformed method starting with 'final': final String ^ foo() { } if (peek(0).equals(Token.IDENTIFIER) && looksLikeMethodOrAccessorDefinition()) { reportError(position(), ParserErrorCode.FINAL_IS_NOT_ALLOWED_ON_A_METHOD_DEFINITION); member = parseMethodOrAccessor(modifiers, null); break; - } + } } member = parseFieldDeclaration(modifiers, type); expectStatmentTerminator(); @@ -1061,8 +1061,8 @@ public class DartParser extends CompletionHooksParserBase { * | set identifier ( * | operator ( * | operator ( - * | identifier ( - * | identifier DOT identifier ( + * | identifier ( + * | identifier DOT identifier ( * | identifier DOT identifier DOT identifier ( * * @return true if the signature of a method has been found. No tokens are consumed. @@ -1085,40 +1085,40 @@ public class DartParser extends CompletionHooksParserBase { if (peekPseudoKeyword(0, NEGATE_KEYWORD) && peek(1).equals(Token.LPAREN)) { return true; } - // TODO(zundel): Look for valid operator overload tokens. For now just assuming + // TODO(zundel): Look for valid operator overload tokens. For now just assuming // non-idents are good enough // operator ??? ( if (!(peek(0).equals(Token.IDENTIFIER) && peek(1).equals(Token.LPAREN))) { return true; } if (peek(0).equals(Token.LBRACK) && peek(1).equals(Token.RBRACK)) { - // operator [] ( + // operator [] ( if (peek(2).equals(Token.LPAREN)) { return true; } - // operator []= ( + // operator []= ( if (peek(2).equals(Token.ASSIGN) && peek(3).equals(Token.LPAREN)) { return true; } } return false; } - + if (peekPseudoKeyword(0, GETTER_KEYWORD) || peekPseudoKeyword(0, SETTER_KEYWORD)) { next(); // Using 'get' or 'set' as a field name is valid if (peek(0).equals(Token.SEMICOLON) || peek(0).equals(Token.ASSIGN)) { return false; - } + } // Using 'get' or 'set' as a method name is valid (but discouraged) if (peek(0).equals(Token.LPAREN)) { return true; - } + } // normal case: get foo ( if (peek(0).equals(Token.IDENTIFIER) && peek(1).equals(Token.LPAREN)) { return true; - } + } return false; } @@ -1227,7 +1227,7 @@ public class DartParser extends CompletionHooksParserBase { found = true; break; } - } + } StringBuilder buf = new StringBuilder(); buf.append(operation.getSyntax()); if (found) { @@ -1858,9 +1858,9 @@ public class DartParser extends CompletionHooksParserBase { Position prevPositionStart = ctx.getTokenLocation().getBegin(); Position prevPositionEnd = ctx.getTokenLocation().getEnd(); Token token = next(); - if (lastResult instanceof DartSuperExpression + if (lastResult instanceof DartSuperExpression && (token == Token.AND || token == Token.OR)) { - reportErrorAtPosition(prevPositionStart, prevPositionEnd, + reportErrorAtPosition(prevPositionStart, prevPositionEnd, ParserErrorCode.SUPER_IS_NOT_VALID_AS_A_BOOLEAN_OPERAND); } DartExpression right; @@ -1879,7 +1879,7 @@ public class DartParser extends CompletionHooksParserBase { if (right instanceof DartSuperExpression) { reportError(position(), ParserErrorCode.SUPER_CANNOT_BE_USED_AS_THE_SECOND_OPERAND); } - + lastResult = right; result = doneWithoutConsuming(new DartBinaryExpression(token, result, right)); if ((token == Token.IS) @@ -1924,7 +1924,7 @@ public class DartParser extends CompletionHooksParserBase { expect(Token.LPAREN); // SEMICOLON is for error recovery boolean namedArgumentParsed = false; - while (!match(Token.RPAREN) && !match(Token.EOS) && !match(Token.SEMICOLON)) { + outer: while (!match(Token.RPAREN) && !match(Token.EOS) && !match(Token.SEMICOLON)) { beginParameter(); DartExpression expression; if (peek(1) == Token.COLON) { @@ -1946,9 +1946,19 @@ public class DartParser extends CompletionHooksParserBase { case RPAREN: break; default: - // Make sure that the parser's state is advanced. Token actual = peek(0); - ctx.advance(); + Set terminals = collectTerminalAnnotations(); + if (terminals.contains(actual) || looksLikeTopLevelKeyword()) { + // Looks like a method already on the stack could use this token. + ctx.begin(); + reportError(ctx.getTokenLocation().getEnd(), + ParserErrorCode.EXPECTED_COMMA_OR_RIGHT_PAREN, actual); + ctx.rollback(); + break outer; + } else { + // Advance the parser state if no other method on the stack can use this token. + ctx.advance(); + } reportError(ctx.getTokenLocation().getEnd(), ParserErrorCode.EXPECTED_COMMA_OR_RIGHT_PAREN, actual); break; @@ -1972,9 +1982,9 @@ public class DartParser extends CompletionHooksParserBase { private DartExpression parseConditionalExpression() { beginConditionalExpression(); DartExpression result = parseBinaryExpression(4); - if (result instanceof DartSuperExpression) { + if (result instanceof DartSuperExpression) { reportError(position(), ParserErrorCode.SUPER_IS_NOT_VALID_ALONE_OR_AS_A_BOOLEAN_OPERAND); - } + } if (peek(0) != Token.CONDITIONAL) { return done(result); } @@ -2078,7 +2088,7 @@ public class DartParser extends CompletionHooksParserBase { case STRING_SEGMENT: case STRING_EMBED_EXP_START: return parseStringInterpolation(); - + default: DartExpression expression = parseExpression(); reportError(position(), ParserErrorCode.EXPECTED_STRING_LITERAL); @@ -2182,7 +2192,13 @@ public class DartParser extends CompletionHooksParserBase { if (unexpectedString == null && unexpected != Token.EOS) { unexpectedString = unexpected.getSyntax(); } - next(); + + // Don't eat tokens that could be used to successfully terminate a non-terminal + // further up the stack. + Set terminals = collectTerminalAnnotations(); + if (!looksLikeTopLevelKeyword() && !terminals.contains(unexpected)) { + next(); + } reportUnexpectedToken(position(), null, unexpected); StringBuilder tokenStr = new StringBuilder(); if (unexpectedString != null) { @@ -2324,7 +2340,7 @@ public class DartParser extends CompletionHooksParserBase { ensureAssignable(result); consume(token); result = doneWithoutConsuming(new DartUnaryExpression(token, result, false)); - } + } return done(result); } @@ -2368,8 +2384,8 @@ public class DartParser extends CompletionHooksParserBase { private class DartStringInterpolationBuilder { - private List strings = new ArrayList(); - private List expressions = new ArrayList(); + private final List strings = new ArrayList(); + private final List expressions = new ArrayList(); private LastSeenNode lastSeen = LastSeenNode.NONE; DartStringInterpolationBuilder() { @@ -2942,6 +2958,7 @@ public class DartParser extends CompletionHooksParserBase { * ; * */ + @Terminals(tokens={Token.RBRACE}) private DartBlock parseBlock() { if (isDietParse) { expect(Token.LBRACE); @@ -2963,15 +2980,16 @@ public class DartParser extends CompletionHooksParserBase { // Return an empty block so we don't generate unparseable code. return emptyBlock; } else { - beginBlock(); - List statements = new ArrayList(); Token nextToken = peek(0); if (!nextToken.equals(Token.LBRACE) && (looksLikeTopLevelKeyword() || nextToken.equals(Token.RBRACE))) { + beginBlock(); // Allow recovery back to the top level. reportErrorWithoutAdvancing(ParserErrorCode.UNEXPECTED_TOKEN); return done(new DartBlock(new ArrayList())); - } + } + beginBlock(); + List statements = new ArrayList(); expect(Token.LBRACE); while (!match(Token.RBRACE) && !EOS()) { if (looksLikeTopLevelKeyword()) { @@ -3266,12 +3284,12 @@ public class DartParser extends CompletionHooksParserBase { // Things that we know can't be valid statements get a synthetic error statement case RBRACE: - case CLASS: + case CLASS: // no need to create a separate parser event as the AST node is enough beginEmptyStatement(); // TODO(jat): other tokens that should be caught here? return done(parseErrorStatement()); - + case IDENTIFIER: // We have already eliminated function declarations earlier, so check for: // a) variable declarations; @@ -3399,7 +3417,8 @@ public class DartParser extends CompletionHooksParserBase { */ private void expectCloseParen() { int parenCount = 1; - switch (peek(0)) { + Token nextToken = peek(0); + switch (nextToken) { case RPAREN: expect(Token.RPAREN); return; @@ -3407,14 +3426,20 @@ public class DartParser extends CompletionHooksParserBase { case EOS: case LBRACE: case SEMICOLON: - reportErrorWithoutAdvancing(ParserErrorCode.UNEXPECTED_TOKEN); + reportError(position(), ParserErrorCode.EXPECTED_TOKEN, Token.RPAREN.getSyntax(), + nextToken.getSyntax()); return; case LPAREN: ++parenCount; //$FALL-THROUGH$ default: - reportErrorWithoutAdvancing(ParserErrorCode.UNEXPECTED_TOKEN); + reportError(position(), ParserErrorCode.EXPECTED_TOKEN, Token.RPAREN.getSyntax(), + nextToken.getSyntax()); + Set terminals = this.collectTerminalAnnotations(); + if (terminals.contains(nextToken) || looksLikeTopLevelKeyword()) { + return; + } break; } diff --git a/compiler/javatests/com/google/dart/compiler/parser/ParserRecoveryTest.java b/compiler/javatests/com/google/dart/compiler/parser/ParserRecoveryTest.java index f53e5597467..1140763e68d 100644 --- a/compiler/javatests/com/google/dart/compiler/parser/ParserRecoveryTest.java +++ b/compiler/javatests/com/google/dart/compiler/parser/ParserRecoveryTest.java @@ -5,11 +5,17 @@ package com.google.dart.compiler.parser; import com.google.common.base.Joiner; +import com.google.dart.compiler.ast.DartBinaryExpression; import com.google.dart.compiler.ast.DartClass; +import com.google.dart.compiler.ast.DartExprStmt; import com.google.dart.compiler.ast.DartFieldDefinition; import com.google.dart.compiler.ast.DartIdentifier; import com.google.dart.compiler.ast.DartMethodDefinition; +import com.google.dart.compiler.ast.DartPropertyAccess; import com.google.dart.compiler.ast.DartUnit; +import com.google.dart.compiler.ast.DartUnqualifiedInvocation; +import com.google.dart.compiler.ast.DartVariable; +import com.google.dart.compiler.ast.DartVariableStatement; public class ParserRecoveryTest extends AbstractParserTest { @@ -17,7 +23,7 @@ public class ParserRecoveryTest extends AbstractParserTest { public void testStringsErrors() { // Implemented elsewhere } - + public void testVarOnMethodDefinition() { // This syntax is illegal, and should produce errors, but since it is a common error, // we want to make sure it produce a valid AST for editor users @@ -37,9 +43,9 @@ public class ParserRecoveryTest extends AbstractParserTest { assertEquals("f2", ((DartIdentifier)(f2.getName())).getName()); // Make sure that parsing continue DartMethodDefinition f3 = (DartMethodDefinition)(A.getMembers().get(1)); - assertEquals("f3", ((DartIdentifier)(f3.getName())).getName()); + assertEquals("f3", ((DartIdentifier)(f3.getName())).getName()); } - + public void testFinalOnMethodDefinition() { // This syntax is illegal, and should produce errors, but since it is a common error, // we want to make sure it produce a valid AST for editor users @@ -62,9 +68,9 @@ public class ParserRecoveryTest extends AbstractParserTest { DartMethodDefinition f3 = (DartMethodDefinition)(A.getMembers().get(1)); assertEquals("f3", ((DartIdentifier)(f3.getName())).getName()); DartMethodDefinition f4 = (DartMethodDefinition)(A.getMembers().get(2)); - assertEquals("f4", ((DartIdentifier)(f4.getName())).getName()); + assertEquals("f4", ((DartIdentifier)(f4.getName())).getName()); } - + public void testRecoverToTopLevel1() { DartUnit unit = parseUnitUnspecifiedErrors("phony_recover_to_toplevel1.dart", Joiner.on("\n").join( @@ -104,8 +110,8 @@ public class ParserRecoveryTest extends AbstractParserTest { assertEquals("B", B.getName().getName()); DartFieldDefinition B_b = (DartFieldDefinition)B.getMembers().get(0); assertEquals("b", B_b.getFields().get(0).getName().getName()); - } - + } + public void testRecoverToTopLevel3() { DartUnit unit = parseUnitUnspecifiedErrors("phony_recover_to_toplevel3.dart", Joiner.on("\n").join( @@ -125,8 +131,8 @@ public class ParserRecoveryTest extends AbstractParserTest { assertEquals("B", B.getName().getName()); DartFieldDefinition B_b = (DartFieldDefinition)B.getMembers().get(0); assertEquals("b", B_b.getFields().get(0).getName().getName()); - } - + } + public void testRecoverToTopLevel4() { DartUnit unit = parseUnitUnspecifiedErrors("phony_recover_to_toplevel4.dart", Joiner.on("\n").join( @@ -146,8 +152,8 @@ public class ParserRecoveryTest extends AbstractParserTest { assertEquals("B", B.getName().getName()); DartFieldDefinition B_b = (DartFieldDefinition)B.getMembers().get(0); assertEquals("b", B_b.getFields().get(0).getName().getName()); - } - + } + public void testRecoverToTopLevel5() { DartUnit unit = parseUnitUnspecifiedErrors("phony_recover_to_toplevel5.dart", Joiner.on("\n").join( @@ -167,8 +173,8 @@ public class ParserRecoveryTest extends AbstractParserTest { assertEquals("B", B.getName().getName()); DartFieldDefinition B_b = (DartFieldDefinition)B.getMembers().get(0); assertEquals("b", B_b.getFields().get(0).getName().getName()); - } - + } + public void testRecoverToTopLevel6() { DartUnit unit = parseUnitUnspecifiedErrors("phony_recover_to_toplevel6.dart", Joiner.on("\n").join( @@ -188,8 +194,8 @@ public class ParserRecoveryTest extends AbstractParserTest { assertEquals("B", B.getName().getName()); DartFieldDefinition B_b = (DartFieldDefinition)B.getMembers().get(0); assertEquals("b", B_b.getFields().get(0).getName().getName()); - } - + } + public void testRecoverToTopLevel7() { DartUnit unit = parseUnitUnspecifiedErrors("phony_recover_to_toplevel7.dart", Joiner.on("\n").join( @@ -209,8 +215,8 @@ public class ParserRecoveryTest extends AbstractParserTest { assertEquals("B", B.getName().getName()); DartFieldDefinition B_b = (DartFieldDefinition)B.getMembers().get(0); assertEquals("b", B_b.getFields().get(0).getName().getName()); - } - + } + public void testRecoverToTopLevel8() { DartUnit unit = parseUnitUnspecifiedErrors("phony_recover_to_toplevel8.dart", Joiner.on("\n").join( @@ -232,8 +238,8 @@ public class ParserRecoveryTest extends AbstractParserTest { assertEquals("B", B.getName().getName()); DartFieldDefinition B_b = (DartFieldDefinition)B.getMembers().get(0); assertEquals("b", B_b.getFields().get(0).getName().getName()); - } - + } + public void testReservedWordClass() { DartUnit unit = parseUnitUnspecifiedErrors("phony_reserved_word_class", Joiner.on("\n").join( @@ -247,21 +253,21 @@ public class ParserRecoveryTest extends AbstractParserTest { assertEquals("foo", foo.getName().getName()); DartMethodDefinition mainMethod = (DartMethodDefinition)unit.getTopLevelNodes().get(1); assertEquals("main", ((DartIdentifier)mainMethod.getName()).getName()); - // The recovery on 'int class' closes the main method, assuming int class = 10 is a + // The recovery on 'int class' closes the main method, assuming int class = 10 is a // new toplevel so 'print' ends up as a bogus top level node. DartClass bar = (DartClass)unit.getTopLevelNodes().get(3); - assertEquals("bar", bar.getName().getName()); + assertEquals("bar", bar.getName().getName()); } - + public void testBadOperatorRecovery() { DartUnit unit = parseUnit("phony_bad_operator_recovery", Joiner.on("\n").join( "class foo {", " operator / (arg) {}", - " operator /= (arg) {}", + " operator /= (arg) {}", " operator ][ (arg) {}", " operator === (arg) {}", - " operator + (arg) {}", + " operator + (arg) {}", "}"), ParserErrorCode.OPERATOR_IS_NOT_USER_DEFINABLE, 3, 12, ParserErrorCode.OPERATOR_IS_NOT_USER_DEFINABLE, 4, 12, @@ -275,11 +281,11 @@ public class ParserRecoveryTest extends AbstractParserTest { DartMethodDefinition opNonsense = (DartMethodDefinition)foo.getMembers().get(2); assertEquals("][", ((DartIdentifier)opNonsense.getName()).getName()); DartMethodDefinition opEquiv = (DartMethodDefinition)foo.getMembers().get(3); - assertEquals("===", ((DartIdentifier)opEquiv.getName()).getName()); + assertEquals("===", ((DartIdentifier)opEquiv.getName()).getName()); DartMethodDefinition opPlus = (DartMethodDefinition)foo.getMembers().get(4); assertEquals("+", ((DartIdentifier)opPlus.getName()).getName()); } - + public void testPropertyAccessInArgumentListRecovery1() { DartUnit unit = parseUnitUnspecifiedErrors("phony_property_access1.dart", Joiner.on("\n").join( @@ -293,7 +299,7 @@ public class ParserRecoveryTest extends AbstractParserTest { DartFieldDefinition after = (DartFieldDefinition)unit.getTopLevelNodes().get(2); assertEquals("after", after.getFields().get(0).getName().getName()); } - + public void testPropertyAccessInArgumentListRecovery2() { DartUnit unit = parseUnitUnspecifiedErrors("phony_property_access2.dart", Joiner.on("\n").join( @@ -307,7 +313,7 @@ public class ParserRecoveryTest extends AbstractParserTest { DartFieldDefinition after = (DartFieldDefinition)unit.getTopLevelNodes().get(2); assertEquals("after", after.getFields().get(0).getName().getName()); } - + public void testPropertyAccessInArgumentListRecovery3() { DartUnit unit = parseUnitUnspecifiedErrors("phony_property_access3.dart", Joiner.on("\n").join( @@ -321,7 +327,7 @@ public class ParserRecoveryTest extends AbstractParserTest { DartFieldDefinition after = (DartFieldDefinition)unit.getTopLevelNodes().get(2); assertEquals("after", after.getFields().get(0).getName().getName()); } - + public void testPropertyAccessInArgumentListRecovery4() { DartUnit unit = parseUnitUnspecifiedErrors("phony_property_access4.dart", Joiner.on("\n").join( @@ -335,4 +341,108 @@ public class ParserRecoveryTest extends AbstractParserTest { DartFieldDefinition after = (DartFieldDefinition)unit.getTopLevelNodes().get(2); assertEquals("after", after.getFields().get(0).getName().getName()); } + + public void testRecoverInBlock1() { + DartUnit unit = parseUnitUnspecifiedErrors("phony_recover_in_block1.dart", + Joiner.on("\n").join( + "var before;", + "bad() { foo( }", // unterminated invocation + "var after;")); + DartFieldDefinition before = (DartFieldDefinition)unit.getTopLevelNodes().get(0); + assertEquals("before", before.getFields().get(0).getName().getName()); + DartMethodDefinition bad = (DartMethodDefinition)unit.getTopLevelNodes().get(1); + assertEquals("bad", ((DartIdentifier)bad.getName()).getName()); + DartUnqualifiedInvocation invocation = (DartUnqualifiedInvocation) + ((DartExprStmt)bad.getFunction().getBody().getStatements().get(0)).getExpression(); + assertEquals("foo", invocation.getTarget().getName()); + DartFieldDefinition after = (DartFieldDefinition)unit.getTopLevelNodes().get(2); + assertEquals("after", after.getFields().get(0).getName().getName()); + } + + public void testRecoverInBlock2() { + DartUnit unit = parseUnitUnspecifiedErrors("phony_recover_in_block2.dart", + Joiner.on("\n").join( + "var before;", + "bad() { foo }", // unterminated statement + "var after;")); + DartFieldDefinition before = (DartFieldDefinition)unit.getTopLevelNodes().get(0); + assertEquals("before", before.getFields().get(0).getName().getName()); + DartMethodDefinition bad = (DartMethodDefinition)unit.getTopLevelNodes().get(1); + assertEquals("bad", ((DartIdentifier)bad.getName()).getName()); + DartIdentifier ident = (DartIdentifier) + ((DartExprStmt)bad.getFunction().getBody().getStatements().get(0)).getExpression(); + assertEquals("foo", ident.getName()); + DartFieldDefinition after = (DartFieldDefinition)unit.getTopLevelNodes().get(2); + assertEquals("after", after.getFields().get(0).getName().getName()); + } + + public void testRecoverInBlock3() { + DartUnit unit = parseUnitUnspecifiedErrors("phony_recover_in_block3.dart", + Joiner.on("\n").join( + "var before;", + "bad() { foo. }", // unterminated statement + "var after;")); + DartFieldDefinition before = (DartFieldDefinition)unit.getTopLevelNodes().get(0); + assertEquals("before", before.getFields().get(0).getName().getName()); + DartMethodDefinition bad = (DartMethodDefinition)unit.getTopLevelNodes().get(1); + assertEquals("bad", ((DartIdentifier)bad.getName()).getName()); + DartPropertyAccess prop = (DartPropertyAccess) + ((DartExprStmt)bad.getFunction().getBody().getStatements().get(0)).getExpression(); + assertEquals("foo", ((DartIdentifier)prop.getQualifier()).getName()); + DartFieldDefinition after = (DartFieldDefinition)unit.getTopLevelNodes().get(2); + assertEquals("after", after.getFields().get(0).getName().getName()); + } + + public void testRecoverInBlock4() { + DartUnit unit = parseUnitUnspecifiedErrors("phony_recover_in_block4.dart", + Joiner.on("\n").join( + "var before;", + "bad() { var foo = }", // unterminated variable decl + "var after;")); + DartFieldDefinition before = (DartFieldDefinition)unit.getTopLevelNodes().get(0); + assertEquals("before", before.getFields().get(0).getName().getName()); + DartMethodDefinition bad = (DartMethodDefinition)unit.getTopLevelNodes().get(1); + assertEquals("bad", ((DartIdentifier)bad.getName()).getName()); + DartVariable foo = ((DartVariableStatement)bad.getFunction().getBody().getStatements().get(0)) + .getVariables().get(0); + assertEquals("foo", foo.getVariableName()); + DartFieldDefinition after = (DartFieldDefinition)unit.getTopLevelNodes().get(2); + assertEquals("after", after.getFields().get(0).getName().getName()); + } + + public void testRecoverInBlock5() { + DartUnit unit = parseUnitUnspecifiedErrors("phony_recover_in_block5.dart", + Joiner.on("\n").join( + "var before;", + "bad() { foo(bar. }", // unterminated invocation + "var after;")); + DartFieldDefinition before = (DartFieldDefinition)unit.getTopLevelNodes().get(0); + assertEquals("before", before.getFields().get(0).getName().getName()); + DartMethodDefinition bad = (DartMethodDefinition)unit.getTopLevelNodes().get(1); + assertEquals("bad", ((DartIdentifier)bad.getName()).getName()); + DartUnqualifiedInvocation invocation = (DartUnqualifiedInvocation) + ((DartExprStmt)bad.getFunction().getBody().getStatements().get(0)).getExpression(); + assertEquals("foo", invocation.getTarget().getName()); + DartPropertyAccess arg0 = (DartPropertyAccess)invocation.getArguments().get(0); + assertEquals("bar", ((DartIdentifier)arg0.getQualifier()).getName()); + DartFieldDefinition after = (DartFieldDefinition)unit.getTopLevelNodes().get(2); + assertEquals("after", after.getFields().get(0).getName().getName()); + } + + public void testRecoverInBlock6() { + DartUnit unit = parseUnitUnspecifiedErrors("phony_recover_in_block6.dart", + Joiner.on("\n").join( + "var before;", + "bad() { foo + }", // incomplete expression + "var after;")); + DartFieldDefinition before = (DartFieldDefinition)unit.getTopLevelNodes().get(0); + assertEquals("before", before.getFields().get(0).getName().getName()); + DartMethodDefinition bad = (DartMethodDefinition)unit.getTopLevelNodes().get(1); + assertEquals("bad", ((DartIdentifier)bad.getName()).getName()); + DartBinaryExpression expr = (DartBinaryExpression) + ((DartExprStmt)bad.getFunction().getBody().getStatements().get(0)).getExpression(); + assertEquals("foo", ((DartIdentifier)expr.getArg1()).getName()); + DartFieldDefinition after = (DartFieldDefinition)unit.getTopLevelNodes().get(2); + assertEquals("after", after.getFields().get(0).getName().getName()); + } }