Better error recovery when errant semicolon found in a class.

This commit is contained in:
Daniel Rosenwasser 2014-07-15 14:24:44 -07:00
parent 9ecf01b57a
commit cece68098a
11 changed files with 61 additions and 188 deletions

View File

@ -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 = <NodeArray<T>>[];
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() {

View File

@ -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; }

View File

@ -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.
}

View File

@ -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();

View File

@ -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.
!!! Cannot compile external modules unless the '--module' flag is provided.

View File

@ -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.

View File

@ -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.

View File

@ -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<T, boolean>): Query<T> {
~~~~~~~~~~~~~~~~~~~~
@ -22,8 +22,6 @@
~
!!! Unexpected token. A constructor, method, accessor, or property was expected.
}
~
!!! Declaration or statement expected.
}
~
!!! Declaration or statement expected.

View File

@ -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;
}

View File

@ -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.

View File

@ -0,0 +1,4 @@
class C {
public f() { };
private m;
}