From f8b84957c69e37864ce5bbfbc3b2efc3191b223f Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Tue, 15 Jul 2014 14:24:44 -0700 Subject: [PATCH 1/2] Better error recovery when errant semicolon found in a class. --- src/compiler/parser.ts | 41 ++++--- .../reference/ambientGetters.errors.txt | 4 +- .../reference/es6ClassTest3.errors.txt | 34 +----- .../reference/exportDeclareClass1.errors.txt | 18 +-- .../reference/parser0_004152.errors.txt | 14 +-- ...EqualsGreaterThanAfterFunction2.errors.txt | 4 +- .../parserErrantSemicolonInClass1.errors.txt | 112 +----------------- .../parserMissingLambdaOpenBrace1.errors.txt | 4 +- ...sWhenHittingUnexpectedSemicolon.errors.txt | 8 ++ .../reference/primitiveMembers.errors.txt | 6 +- ...sRecoversWhenHittingUnexpectedSemicolon.ts | 4 + 11 files changed, 61 insertions(+), 188 deletions(-) create mode 100644 tests/baselines/reference/parsingClassRecoversWhenHittingUnexpectedSemicolon.errors.txt create mode 100644 tests/cases/compiler/parsingClassRecoversWhenHittingUnexpectedSemicolon.ts diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index d3e942be2fe57..2a047cff7b5ea 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -619,14 +619,14 @@ module ts { } // True if positioned at the start of a list element - function isListElement(kind: ParsingContext): boolean { + function isListElement(kind: ParsingContext, inErrorRecovery: boolean): boolean { switch (kind) { case ParsingContext.SourceElements: case ParsingContext.ModuleElements: - return isSourceElement(); + return isSourceElement(inErrorRecovery); case ParsingContext.BlockStatements: case ParsingContext.SwitchClauseStatements: - return isStatement(); + return isStatement(inErrorRecovery); case ParsingContext.SwitchClauses: return token === SyntaxKind.CaseKeyword || token === SyntaxKind.DefaultKeyword; case ParsingContext.TypeMembers: @@ -650,6 +650,8 @@ module ts { case ParsingContext.TypeArguments: return isType(); } + + Debug.fail("Non-exhaustive case in 'isListElement'."); } // True if positioned at a list terminator @@ -717,10 +719,12 @@ module ts { } // True if positioned at element or terminator of the current list or any enclosing list - function isInParsingContext(): boolean { + function isInSomeParsingContext(): boolean { for (var kind = 0; kind < ParsingContext.Count; kind++) { if (parsingContext & (1 << kind)) { - if (isListElement(kind) || isListTerminator(kind)) return true; + if (isListElement(kind, /* inErrorRecovery */ true) || isListTerminator(kind)) { + return true; + } } } @@ -734,12 +738,12 @@ module ts { var result = >[]; result.pos = getNodePos(); while (!isListTerminator(kind)) { - if (isListElement(kind)) { + if (isListElement(kind, /* inErrorRecovery */ false)) { result.push(parseElement()); } else { error(parsingContextErrors(kind)); - if (isInParsingContext()) { + if (isInSomeParsingContext()) { break; } nextToken(); @@ -761,7 +765,7 @@ module ts { var errorCountBeforeParsingList = file.syntacticErrors.length; var commaStart = -1; // Meaning the previous token was not a comma while (true) { - if (isListElement(kind)) { + if (isListElement(kind, /* inErrorRecovery */ false)) { result.push(parseElement()); commaStart = scanner.getTokenPos(); if (parseOptional(SyntaxKind.CommaToken)) { @@ -791,7 +795,7 @@ module ts { } else { error(parsingContextErrors(kind)); - if (token !== SyntaxKind.CommaToken && isInParsingContext()) { + if (token !== SyntaxKind.CommaToken && isInSomeParsingContext()) { break; } nextToken(); @@ -2066,12 +2070,19 @@ module ts { return finishNode(node); } - function isStatement(): boolean { + function isStatement(inErrorRecovery: boolean): boolean { switch (token) { + case SyntaxKind.SemicolonToken: + // If we're in error recovery, then we don't want to treat ';' as an empty statement. + // The problem is that ';' can show up in far too many contexts, and if we see one + // and assume it's a statement, then we may bail out innapropriately from whatever + // we're parsing. For example, if we have a semicolon in the middle of a class, then + // we really don't want to assume the class is over and we're on a statement in the + // outer module. We just want to consume and move on. + return !inErrorRecovery; case SyntaxKind.OpenBraceToken: case SyntaxKind.VarKeyword: case SyntaxKind.FunctionKeyword: - case SyntaxKind.SemicolonToken: case SyntaxKind.IfKeyword: case SyntaxKind.DoKeyword: case SyntaxKind.WhileKeyword: @@ -2103,8 +2114,8 @@ module ts { } } - function isStatementOrFunction(): boolean { - return token === SyntaxKind.FunctionKeyword || isStatement(); + function isStatementOrFunction(inErrorRecovery: boolean): boolean { + return token === SyntaxKind.FunctionKeyword || isStatement(inErrorRecovery); } function parseStatement(): Statement { @@ -2813,8 +2824,8 @@ module ts { return result; } - function isSourceElement(): boolean { - return isDeclaration() || isStatement(); + function isSourceElement(inErrorRecovery: boolean): boolean { + return isDeclaration() || isStatement(inErrorRecovery); } function parseSourceElement() { diff --git a/tests/baselines/reference/ambientGetters.errors.txt b/tests/baselines/reference/ambientGetters.errors.txt index a09ef7cf8cfc5..05c405810740b 100644 --- a/tests/baselines/reference/ambientGetters.errors.txt +++ b/tests/baselines/reference/ambientGetters.errors.txt @@ -1,12 +1,10 @@ -==== tests/cases/compiler/ambientGetters.ts (3 errors) ==== +==== tests/cases/compiler/ambientGetters.ts (2 errors) ==== declare class A { get length() : number; ~ !!! '{' expected. } - ~ -!!! Declaration or statement expected. declare class B { get length() { return 0; } diff --git a/tests/baselines/reference/es6ClassTest3.errors.txt b/tests/baselines/reference/es6ClassTest3.errors.txt index d16568b7c2470..d666351dc9a59 100644 --- a/tests/baselines/reference/es6ClassTest3.errors.txt +++ b/tests/baselines/reference/es6ClassTest3.errors.txt @@ -1,45 +1,19 @@ -==== tests/cases/compiler/es6ClassTest3.ts (15 errors) ==== +==== tests/cases/compiler/es6ClassTest3.ts (2 errors) ==== module M { class Visibility { public foo() { }; ~ !!! Unexpected token. A constructor, method, accessor, or property was expected. private bar() { }; - ~~~~~~~ -!!! Declaration or statement expected. - ~ -!!! ';' expected. - ~~~ -!!! Cannot find name 'bar'. + ~ +!!! Unexpected token. A constructor, method, accessor, or property was expected. private x: number; - ~~~~~~~ -!!! Declaration or statement expected. - ~~~~~~ -!!! Cannot find name 'number'. public y: number; - ~~~~~~ -!!! Declaration or statement expected. - ~~~~~~ -!!! Cannot find name 'number'. public z: number; - ~~~~~~ -!!! Declaration or statement expected. - ~~~~~~ -!!! Cannot find name 'number'. constructor() { - ~ -!!! ';' expected. - ~~~~~~~~~~~ -!!! Cannot find name 'constructor'. this.x = 1; - ~~~~ -!!! 'this' cannot be referenced in a module body. this.y = 2; - ~~~~ -!!! 'this' cannot be referenced in a module body. } } - } - ~ -!!! Declaration or statement expected. \ No newline at end of file + } \ No newline at end of file diff --git a/tests/baselines/reference/exportDeclareClass1.errors.txt b/tests/baselines/reference/exportDeclareClass1.errors.txt index dee40634d35d2..6ee2bc0e09045 100644 --- a/tests/baselines/reference/exportDeclareClass1.errors.txt +++ b/tests/baselines/reference/exportDeclareClass1.errors.txt @@ -1,4 +1,4 @@ -==== tests/cases/compiler/exportDeclareClass1.ts (9 errors) ==== +==== tests/cases/compiler/exportDeclareClass1.ts (4 errors) ==== export declare class eaC { static tF() { }; ~ @@ -6,21 +6,11 @@ ~ !!! Unexpected token. A constructor, method, accessor, or property was expected. static tsF(param:any) { }; - ~~~~~~ -!!! Declaration or statement expected. - ~ -!!! ',' expected. ~ -!!! ';' expected. - ~~~ -!!! Cannot find name 'tsF'. - ~~~~~ -!!! Cannot find name 'param'. - ~~~ -!!! Cannot find name 'any'. +!!! A function implementation cannot be declared in an ambient context. + ~ +!!! Unexpected token. A constructor, method, accessor, or property was expected. }; - ~ -!!! Declaration or statement expected. export declare class eaC2 { static tF(); diff --git a/tests/baselines/reference/parser0_004152.errors.txt b/tests/baselines/reference/parser0_004152.errors.txt index 81e6d44af87fb..07c7126d8b565 100644 --- a/tests/baselines/reference/parser0_004152.errors.txt +++ b/tests/baselines/reference/parser0_004152.errors.txt @@ -1,4 +1,4 @@ -==== tests/cases/conformance/parser/ecmascript5/Fuzz/parser0_004152.ts (38 errors) ==== +==== tests/cases/conformance/parser/ecmascript5/Fuzz/parser0_004152.ts (34 errors) ==== export class Game { ~~~~~~~~~~~~~~~~~~~ private position = new DisplayPosition([), 3, 3, 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 0], NoMove, 0); @@ -40,8 +40,7 @@ !!! ';' expected. ~ !!! Unexpected token. A constructor, method, accessor, or property was expected. - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! Cannot compile external modules unless the '--module' flag is provided. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~ !!! Cannot find name 'DisplayPosition'. ~ @@ -69,14 +68,9 @@ ~ !!! Duplicate identifier '0'. private prevConfig: SeedCoords[][]; - ~~~~~~~ -!!! Declaration or statement expected. - ~ -!!! Expression expected. - ~ -!!! Expression expected. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~ !!! Cannot find name 'SeedCoords'. } ~ -!!! Declaration or statement expected. \ No newline at end of file +!!! Cannot compile external modules unless the '--module' flag is provided. \ No newline at end of file diff --git a/tests/baselines/reference/parserEqualsGreaterThanAfterFunction2.errors.txt b/tests/baselines/reference/parserEqualsGreaterThanAfterFunction2.errors.txt index e9566b75e132b..ef853280dafc7 100644 --- a/tests/baselines/reference/parserEqualsGreaterThanAfterFunction2.errors.txt +++ b/tests/baselines/reference/parserEqualsGreaterThanAfterFunction2.errors.txt @@ -1,4 +1,4 @@ -==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserEqualsGreaterThanAfterFunction2.ts (4 errors) ==== +==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserEqualsGreaterThanAfterFunction2.ts (5 errors) ==== function (a => b; ~ !!! Identifier expected. @@ -6,5 +6,7 @@ !!! ',' expected. ~ !!! ',' expected. + +!!! ')' expected. ~~~~~~~~~~~~~~~~~ !!! Function implementation expected. \ No newline at end of file diff --git a/tests/baselines/reference/parserErrantSemicolonInClass1.errors.txt b/tests/baselines/reference/parserErrantSemicolonInClass1.errors.txt index 70df299aca6d9..3caf228c1e0cd 100644 --- a/tests/baselines/reference/parserErrantSemicolonInClass1.errors.txt +++ b/tests/baselines/reference/parserErrantSemicolonInClass1.errors.txt @@ -1,4 +1,4 @@ -==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserErrantSemicolonInClass1.ts (56 errors) ==== +==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserErrantSemicolonInClass1.ts (5 errors) ==== class a { //constructor (); constructor (n: number); @@ -12,137 +12,35 @@ !!! Unexpected token. A constructor, method, accessor, or property was expected. public pv; - ~~~~~~ -!!! Declaration or statement expected. - ~~ -!!! Cannot find name 'pv'. public get d() { - ~~~~~~ -!!! Declaration or statement expected. ~ -!!! ';' expected. - ~ -!!! ';' expected. - ~~~ -!!! Cannot find name 'get'. - ~ -!!! Cannot find name 'd'. +!!! Accessors are only available when targeting ECMAScript 5 and higher. return 30; - ~~~~~~~~~~ -!!! 'return' statement has no containing function. } public set d() { - ~~~~~~ -!!! Declaration or statement expected. - ~ -!!! ';' expected. - ~ -!!! ';' expected. - ~~~ -!!! Cannot find name 'set'. ~ -!!! Cannot find name 'd'. +!!! Accessors are only available when targeting ECMAScript 5 and higher. } public static get p2() { - ~~~~~~ -!!! Declaration or statement expected. - ~~~~~~ -!!! Declaration or statement expected. ~~ -!!! ';' expected. - ~ -!!! ';' expected. - ~~~ -!!! Cannot find name 'get'. - ~~ -!!! Cannot find name 'p2'. +!!! Accessors are only available when targeting ECMAScript 5 and higher. return { x: 30, y: 40 }; - ~~~~~~~~~~~~~~~~~~~~~~~~ -!!! 'return' statement has no containing function. } private static d2() { - ~~~~~~~ -!!! Declaration or statement expected. - ~~~~~~ -!!! Declaration or statement expected. - ~ -!!! ';' expected. - ~~ -!!! Cannot find name 'd2'. } private static get p3() { - ~~~~~~~ -!!! Declaration or statement expected. - ~~~~~~ -!!! Declaration or statement expected. - ~~ -!!! ';' expected. - ~ -!!! ';' expected. - ~~~ -!!! Cannot find name 'get'. ~~ -!!! Cannot find name 'p3'. +!!! Accessors are only available when targeting ECMAScript 5 and higher. return "string"; - ~~~~~~~~~~~~~~~~ -!!! 'return' statement has no containing function. } private pv3; - ~~~~~~~ -!!! Declaration or statement expected. - ~~~ -!!! Cannot find name 'pv3'. private foo(n: number): string; - ~~~~~~~ -!!! Declaration or statement expected. - ~ -!!! ',' expected. - ~ -!!! ';' expected. - ~~~ -!!! Cannot find name 'foo'. - ~ -!!! Cannot find name 'n'. - ~~~~~~ -!!! Cannot find name 'number'. - ~~~~~~ -!!! Cannot find name 'string'. private foo(s: string): string; - ~~~~~~~ -!!! Declaration or statement expected. - ~ -!!! ',' expected. - ~ -!!! ';' expected. - ~~~ -!!! Cannot find name 'foo'. - ~ -!!! Cannot find name 's'. - ~~~~~~ -!!! Cannot find name 'string'. - ~~~~~~ -!!! Cannot find name 'string'. private foo(ns: any) { - ~~~~~~~ -!!! Declaration or statement expected. - ~ -!!! ',' expected. - ~ -!!! ';' expected. - ~~~ -!!! Cannot find name 'foo'. - ~~ -!!! Cannot find name 'ns'. - ~~~ -!!! Cannot find name 'any'. return ns.toString(); - ~~~~~~~~~~~~~~~~~~~~~ -!!! 'return' statement has no containing function. } } - ~ -!!! Declaration or statement expected. \ No newline at end of file diff --git a/tests/baselines/reference/parserMissingLambdaOpenBrace1.errors.txt b/tests/baselines/reference/parserMissingLambdaOpenBrace1.errors.txt index 7db5f08033c1c..472e7e237a39d 100644 --- a/tests/baselines/reference/parserMissingLambdaOpenBrace1.errors.txt +++ b/tests/baselines/reference/parserMissingLambdaOpenBrace1.errors.txt @@ -1,4 +1,4 @@ -==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserMissingLambdaOpenBrace1.ts (10 errors) ==== +==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/parserMissingLambdaOpenBrace1.ts (9 errors) ==== class C { where(filter: Iterator): Query { ~~~~~~~~~~~~~~~~~~~~ @@ -22,8 +22,6 @@ ~ !!! Unexpected token. A constructor, method, accessor, or property was expected. } - ~ -!!! Declaration or statement expected. } ~ !!! Declaration or statement expected. \ No newline at end of file diff --git a/tests/baselines/reference/parsingClassRecoversWhenHittingUnexpectedSemicolon.errors.txt b/tests/baselines/reference/parsingClassRecoversWhenHittingUnexpectedSemicolon.errors.txt new file mode 100644 index 0000000000000..0780b807ead49 --- /dev/null +++ b/tests/baselines/reference/parsingClassRecoversWhenHittingUnexpectedSemicolon.errors.txt @@ -0,0 +1,8 @@ +==== tests/cases/compiler/parsingClassRecoversWhenHittingUnexpectedSemicolon.ts (1 errors) ==== + class C { + public f() { }; + ~ +!!! Unexpected token. A constructor, method, accessor, or property was expected. + private m; + } + \ No newline at end of file diff --git a/tests/baselines/reference/primitiveMembers.errors.txt b/tests/baselines/reference/primitiveMembers.errors.txt index 3134927df802c..325dcbd1f6caf 100644 --- a/tests/baselines/reference/primitiveMembers.errors.txt +++ b/tests/baselines/reference/primitiveMembers.errors.txt @@ -1,4 +1,4 @@ -==== tests/cases/compiler/primitiveMembers.ts (6 errors) ==== +==== tests/cases/compiler/primitiveMembers.ts (4 errors) ==== var x = 5; var r = /yo/; r.source; @@ -29,13 +29,9 @@ class baz { public bar(): void { }; } ~ !!! Unexpected token. A constructor, method, accessor, or property was expected. - ~ -!!! Declaration or statement expected. class foo extends baz { public bar(){ return undefined}; } ~ !!! Unexpected token. A constructor, method, accessor, or property was expected. - ~ -!!! Declaration or statement expected. diff --git a/tests/cases/compiler/parsingClassRecoversWhenHittingUnexpectedSemicolon.ts b/tests/cases/compiler/parsingClassRecoversWhenHittingUnexpectedSemicolon.ts new file mode 100644 index 0000000000000..4ec21368fb8c7 --- /dev/null +++ b/tests/cases/compiler/parsingClassRecoversWhenHittingUnexpectedSemicolon.ts @@ -0,0 +1,4 @@ +class C { + public f() { }; + private m; +} From 1c30393cd23dab6fad87684815d492d56b2c4090 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Tue, 15 Jul 2014 14:28:08 -0700 Subject: [PATCH 2/2] Removed unnecessary check for commas in 'parseDelimitedList'. --- src/compiler/parser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 2a047cff7b5ea..5268fb0701223 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -795,7 +795,7 @@ module ts { } else { error(parsingContextErrors(kind)); - if (token !== SyntaxKind.CommaToken && isInSomeParsingContext()) { + if (isInSomeParsingContext()) { break; } nextToken();