Gracefully handle 'catch' and 'finally' blocks without a preceding 'try' block.

Fixes #216.

As a note of this fix, when a 'catch' block is followed by a 'finally' block, only the 'catch' keyword gets an error reported on it.
This commit is contained in:
Daniel Rosenwasser 2014-07-25 18:10:27 -07:00
parent e4256d827d
commit efb6db8757
5 changed files with 58 additions and 9 deletions

View File

@ -160,6 +160,8 @@ module ts {
Constructor_implementation_expected: { code: 2240, category: DiagnosticCategory.Error, key: "Constructor implementation expected." },
An_export_assignment_cannot_be_used_in_a_module_with_other_exported_elements: { code: 2245, category: DiagnosticCategory.Error, key: "An export assignment cannot be used in a module with other exported elements." },
A_parameter_property_is_only_allowed_in_a_constructor_implementation: { code: 2246, category: DiagnosticCategory.Error, key: "A parameter property is only allowed in a constructor implementation." },
A_catch_clause_must_be_preceded_by_a_try_statement: { code: 2249, category: DiagnosticCategory.Error, key: "A 'catch' clause must be preceded by a 'try' statement." },
A_finally_block_must_be_preceded_by_a_try_statement: { code: 2250, category: DiagnosticCategory.Error, key: "A 'finally' block must be preceded by a 'try' statement." },
Circular_definition_of_import_alias_0: { code: 3000, category: DiagnosticCategory.Error, key: "Circular definition of import alias '{0}'." },
Cannot_find_name_0: { code: 3001, category: DiagnosticCategory.Error, key: "Cannot find name '{0}'." },
Module_0_has_no_exported_member_1: { code: 3002, category: DiagnosticCategory.Error, key: "Module '{0}' has no exported member '{1}'." },

View File

@ -632,6 +632,15 @@
"category": "Error",
"code": 2246
},
"A 'catch' clause must be preceded by a 'try' statement.": {
"category": "Error",
"code": 2249
},
"A 'finally' block must be preceded by a 'try' statement.": {
"category": "Error",
"code": 2250
},
"Circular definition of import alias '{0}'.": {
"category": "Error",
"code": 3000

View File

@ -2542,6 +2542,35 @@ module ts {
return finishNode(node);
}
// This function is used for parsing 'catch'/'finally' blocks
// in spite of them missing a 'try' statement.
function parseCatchOrFinallyBlocksMissingTryStatement(): TryStatement {
Debug.assert(token === SyntaxKind.CatchKeyword || token === SyntaxKind.FinallyKeyword,
"'parseCatchOrFinallyBlocksMissingTryStatement' should only be called when the current token is a 'catch' or 'finally' keyword.");
// We're just going to return a bogus TryStatement.
var node = <TryStatement>createNode(SyntaxKind.TryStatement);
node.tryBlock = <Block>createNode(SyntaxKind.Block);
node.tryBlock.statements = createMissingList<Statement>();
if (token === SyntaxKind.CatchKeyword) {
error(Diagnostics.A_catch_clause_must_be_preceded_by_a_try_statement);
node.catchBlock = parseCatchBlock();
}
if (token === SyntaxKind.FinallyKeyword) {
// Only report an error on the 'finally' block if we haven't on the 'catch' block.
if (node.catchBlock === undefined) {
error(Diagnostics.A_finally_block_must_be_preceded_by_a_try_statement);
}
node.finallyBlock = parseTokenAndBlock(SyntaxKind.FinallyKeyword, SyntaxKind.FinallyBlock);
}
return finishNode(node);
}
function parseTokenAndBlock(token: SyntaxKind, kind: SyntaxKind): Block {
var pos = getNodePos();
parseExpected(token);
@ -2646,6 +2675,10 @@ module ts {
case SyntaxKind.ThrowKeyword:
case SyntaxKind.TryKeyword:
case SyntaxKind.DebuggerKeyword:
// 'catch' and 'finally' do not actually indicate that the code is part of a statement,
// however, we say they are here so that we may gracefully parse them and error later.
case SyntaxKind.CatchKeyword:
case SyntaxKind.FinallyKeyword:
return true;
case SyntaxKind.InterfaceKeyword:
case SyntaxKind.ClassKeyword:
@ -2653,13 +2686,17 @@ module ts {
case SyntaxKind.EnumKeyword:
// When followed by an identifier, these do not start a statement but might
// instead be following declarations
if (isDeclaration()) return false;
if (isDeclaration()) {
return false;
}
case SyntaxKind.PublicKeyword:
case SyntaxKind.PrivateKeyword:
case SyntaxKind.StaticKeyword:
// When followed by an identifier or keyword, these do not start a statement but
// might instead be following type members
if (lookAhead(() => nextToken() >= SyntaxKind.Identifier)) return false;
if (lookAhead(() => nextToken() >= SyntaxKind.Identifier)) {
return false;
}
default:
return isExpression();
}
@ -2697,6 +2734,9 @@ module ts {
return parseThrowStatement();
case SyntaxKind.TryKeyword:
return parseTryStatement();
case SyntaxKind.CatchKeyword:
case SyntaxKind.FinallyKeyword:
return parseCatchOrFinallyBlocksMissingTryStatement();
case SyntaxKind.DebuggerKeyword:
return parseDebuggerStatement();
default:

View File

@ -1,4 +1,4 @@
==== tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts (4 errors) ====
==== tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts (2 errors) ====
function fn() {
try {
} catch { // syntax error, missing '(x)'
@ -8,11 +8,7 @@
catch(x) { } // error missing try
~~~~~
!!! Statement expected.
~
!!! '=>' expected.
!!! A 'catch' clause must be preceded by a 'try' statement.
finally{ } // error missing try
~~~~~~~
!!! Statement expected.
}

View File

@ -1,6 +1,8 @@
==== tests/cases/conformance/parser/ecmascript5/MissingTokens/parserMissingToken1.ts (2 errors) ====
==== tests/cases/conformance/parser/ecmascript5/MissingTokens/parserMissingToken1.ts (3 errors) ====
a / finally
~~~~~~~
!!! Expression expected.
!!! '{' expected.
~
!!! Cannot find name 'a'.