Skip to content

Commit efb6db8

Browse files
Gracefully handle 'catch' and 'finally' blocks without a preceding 'try' block.
Fixes microsoft#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.
1 parent e4256d8 commit efb6db8

5 files changed

Lines changed: 58 additions & 9 deletions

File tree

src/compiler/diagnosticInformationMap.generated.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@ module ts {
160160
Constructor_implementation_expected: { code: 2240, category: DiagnosticCategory.Error, key: "Constructor implementation expected." },
161161
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." },
162162
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." },
163+
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." },
164+
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." },
163165
Circular_definition_of_import_alias_0: { code: 3000, category: DiagnosticCategory.Error, key: "Circular definition of import alias '{0}'." },
164166
Cannot_find_name_0: { code: 3001, category: DiagnosticCategory.Error, key: "Cannot find name '{0}'." },
165167
Module_0_has_no_exported_member_1: { code: 3002, category: DiagnosticCategory.Error, key: "Module '{0}' has no exported member '{1}'." },

src/compiler/diagnosticMessages.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,15 @@
632632
"category": "Error",
633633
"code": 2246
634634
},
635+
"A 'catch' clause must be preceded by a 'try' statement.": {
636+
"category": "Error",
637+
"code": 2249
638+
},
639+
"A 'finally' block must be preceded by a 'try' statement.": {
640+
"category": "Error",
641+
"code": 2250
642+
},
643+
635644
"Circular definition of import alias '{0}'.": {
636645
"category": "Error",
637646
"code": 3000

src/compiler/parser.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2542,6 +2542,35 @@ module ts {
25422542
return finishNode(node);
25432543
}
25442544

2545+
// This function is used for parsing 'catch'/'finally' blocks
2546+
// in spite of them missing a 'try' statement.
2547+
function parseCatchOrFinallyBlocksMissingTryStatement(): TryStatement {
2548+
2549+
Debug.assert(token === SyntaxKind.CatchKeyword || token === SyntaxKind.FinallyKeyword,
2550+
"'parseCatchOrFinallyBlocksMissingTryStatement' should only be called when the current token is a 'catch' or 'finally' keyword.");
2551+
2552+
// We're just going to return a bogus TryStatement.
2553+
var node = <TryStatement>createNode(SyntaxKind.TryStatement);
2554+
node.tryBlock = <Block>createNode(SyntaxKind.Block);
2555+
node.tryBlock.statements = createMissingList<Statement>();
2556+
2557+
if (token === SyntaxKind.CatchKeyword) {
2558+
error(Diagnostics.A_catch_clause_must_be_preceded_by_a_try_statement);
2559+
node.catchBlock = parseCatchBlock();
2560+
}
2561+
2562+
if (token === SyntaxKind.FinallyKeyword) {
2563+
// Only report an error on the 'finally' block if we haven't on the 'catch' block.
2564+
if (node.catchBlock === undefined) {
2565+
error(Diagnostics.A_finally_block_must_be_preceded_by_a_try_statement);
2566+
}
2567+
2568+
node.finallyBlock = parseTokenAndBlock(SyntaxKind.FinallyKeyword, SyntaxKind.FinallyBlock);
2569+
}
2570+
2571+
return finishNode(node);
2572+
}
2573+
25452574
function parseTokenAndBlock(token: SyntaxKind, kind: SyntaxKind): Block {
25462575
var pos = getNodePos();
25472576
parseExpected(token);
@@ -2646,20 +2675,28 @@ module ts {
26462675
case SyntaxKind.ThrowKeyword:
26472676
case SyntaxKind.TryKeyword:
26482677
case SyntaxKind.DebuggerKeyword:
2678+
// 'catch' and 'finally' do not actually indicate that the code is part of a statement,
2679+
// however, we say they are here so that we may gracefully parse them and error later.
2680+
case SyntaxKind.CatchKeyword:
2681+
case SyntaxKind.FinallyKeyword:
26492682
return true;
26502683
case SyntaxKind.InterfaceKeyword:
26512684
case SyntaxKind.ClassKeyword:
26522685
case SyntaxKind.ModuleKeyword:
26532686
case SyntaxKind.EnumKeyword:
26542687
// When followed by an identifier, these do not start a statement but might
26552688
// instead be following declarations
2656-
if (isDeclaration()) return false;
2689+
if (isDeclaration()) {
2690+
return false;
2691+
}
26572692
case SyntaxKind.PublicKeyword:
26582693
case SyntaxKind.PrivateKeyword:
26592694
case SyntaxKind.StaticKeyword:
26602695
// When followed by an identifier or keyword, these do not start a statement but
26612696
// might instead be following type members
2662-
if (lookAhead(() => nextToken() >= SyntaxKind.Identifier)) return false;
2697+
if (lookAhead(() => nextToken() >= SyntaxKind.Identifier)) {
2698+
return false;
2699+
}
26632700
default:
26642701
return isExpression();
26652702
}
@@ -2697,6 +2734,9 @@ module ts {
26972734
return parseThrowStatement();
26982735
case SyntaxKind.TryKeyword:
26992736
return parseTryStatement();
2737+
case SyntaxKind.CatchKeyword:
2738+
case SyntaxKind.FinallyKeyword:
2739+
return parseCatchOrFinallyBlocksMissingTryStatement();
27002740
case SyntaxKind.DebuggerKeyword:
27012741
return parseDebuggerStatement();
27022742
default:
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
==== tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts (4 errors) ====
1+
==== tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts (2 errors) ====
22
function fn() {
33
try {
44
} catch { // syntax error, missing '(x)'
@@ -8,11 +8,7 @@
88

99
catch(x) { } // error missing try
1010
~~~~~
11-
!!! Statement expected.
12-
~
13-
!!! '=>' expected.
11+
!!! A 'catch' clause must be preceded by a 'try' statement.
1412

1513
finally{ } // error missing try
16-
~~~~~~~
17-
!!! Statement expected.
1814
}
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
==== tests/cases/conformance/parser/ecmascript5/MissingTokens/parserMissingToken1.ts (2 errors) ====
1+
==== tests/cases/conformance/parser/ecmascript5/MissingTokens/parserMissingToken1.ts (3 errors) ====
22
a / finally
33
~~~~~~~
44
!!! Expression expected.
5+
6+
!!! '{' expected.
57
~
68
!!! Cannot find name 'a'.

0 commit comments

Comments
 (0)