diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 2fbd0c3e1412a..45880f855fd7e 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2921,7 +2921,10 @@ namespace ts { function computeCatchClause(node: CatchClause, subtreeFlags: TransformFlags) { let transformFlags = subtreeFlags; - if (node.variableDeclaration && isBindingPattern(node.variableDeclaration.name)) { + if (!node.variableDeclaration) { + transformFlags |= TransformFlags.AssertESNext; + } + else if (isBindingPattern(node.variableDeclaration.name)) { transformFlags |= TransformFlags.AssertES2015; } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4de0c7a2a6c37..f3979d8044181 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -481,7 +481,7 @@ namespace ts { Type, ResolvedBaseConstructorType, DeclaredType, - ResolvedReturnType + ResolvedReturnType, } const enum CheckMode { diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index de08b209d3e4e..24769afd5d45a 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -2131,10 +2131,12 @@ namespace ts { function emitCatchClause(node: CatchClause) { const openParenPos = writeToken(SyntaxKind.CatchKeyword, node.pos); write(" "); - writeToken(SyntaxKind.OpenParenToken, openParenPos); - emit(node.variableDeclaration); - writeToken(SyntaxKind.CloseParenToken, node.variableDeclaration ? node.variableDeclaration.end : openParenPos); - write(" "); + if (node.variableDeclaration) { + writeToken(SyntaxKind.OpenParenToken, openParenPos); + emit(node.variableDeclaration); + writeToken(SyntaxKind.CloseParenToken, node.variableDeclaration.end); + write(" "); + } emit(node.block); } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 34727e4c41422..47df0a9bdc27a 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -2128,14 +2128,14 @@ namespace ts { : node; } - export function createCatchClause(variableDeclaration: string | VariableDeclaration, block: Block) { + export function createCatchClause(variableDeclaration: string | VariableDeclaration | undefined, block: Block) { const node = createSynthesizedNode(SyntaxKind.CatchClause); node.variableDeclaration = typeof variableDeclaration === "string" ? createVariableDeclaration(variableDeclaration) : variableDeclaration; node.block = block; return node; } - export function updateCatchClause(node: CatchClause, variableDeclaration: VariableDeclaration, block: Block) { + export function updateCatchClause(node: CatchClause, variableDeclaration: VariableDeclaration | undefined, block: Block) { return node.variableDeclaration !== variableDeclaration || node.block !== block ? updateNode(createCatchClause(variableDeclaration, block), node) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 34670cd28349a..155ad3999bfa3 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -4778,11 +4778,16 @@ namespace ts { function parseCatchClause(): CatchClause { const result = createNode(SyntaxKind.CatchClause); parseExpected(SyntaxKind.CatchKeyword); - if (parseExpected(SyntaxKind.OpenParenToken)) { + + if (parseOptional(SyntaxKind.OpenParenToken)) { result.variableDeclaration = parseVariableDeclaration(); + parseExpected(SyntaxKind.CloseParenToken); + } + else { + // Keep shape of node to avoid degrading performance. + result.variableDeclaration = undefined; } - parseExpected(SyntaxKind.CloseParenToken); result.block = parseBlock(/*ignoreMissingOpenBrace*/ false); return finishNode(result); } diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index f97de018d4ad0..2b0b175e2a56f 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -2491,7 +2491,7 @@ namespace ts { const catchVariable = getGeneratedNameForNode(errorRecord); const returnMethod = createTempVariable(/*recordTempVariable*/ undefined); const values = createValuesHelper(context, expression, node.expression); - const next = createCall(createPropertyAccess(iterator, "next" ), /*typeArguments*/ undefined, []); + const next = createCall(createPropertyAccess(iterator, "next"), /*typeArguments*/ undefined, []); hoistVariableDeclaration(errorRecord); hoistVariableDeclaration(returnMethod); @@ -3173,6 +3173,7 @@ namespace ts { function visitCatchClause(node: CatchClause): CatchClause { const ancestorFacts = enterSubtree(HierarchyFacts.BlockScopeExcludes, HierarchyFacts.BlockScopeIncludes); let updated: CatchClause; + Debug.assert(!!node.variableDeclaration, "Catch clause variable should always be present when downleveling ES2015."); if (isBindingPattern(node.variableDeclaration.name)) { const temp = createTempVariable(/*recordTempVariable*/ undefined); const newVariableDeclaration = createVariableDeclaration(temp); diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 3d8840019326d..8d71016b0519c 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -101,6 +101,8 @@ namespace ts { return visitExpressionStatement(node as ExpressionStatement); case SyntaxKind.ParenthesizedExpression: return visitParenthesizedExpression(node as ParenthesizedExpression, noDestructuringValue); + case SyntaxKind.CatchClause: + return visitCatchClause(node as CatchClause); default: return visitEachChild(node, visitor, context); } @@ -212,6 +214,17 @@ namespace ts { return visitEachChild(node, noDestructuringValue ? visitorNoDestructuringValue : visitor, context); } + function visitCatchClause(node: CatchClause): CatchClause { + if (!node.variableDeclaration) { + return updateCatchClause( + node, + createVariableDeclaration(createTempVariable(/*recordTempVariable*/ undefined)), + visitNode(node.block, visitor, isBlock) + ); + } + return visitEachChild(node, visitor, context); + } + /** * Visits a BinaryExpression that contains a destructuring assignment. * diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 09f1b5cfcc53c..ca70a6fe20921 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1808,7 +1808,7 @@ namespace ts { export interface CatchClause extends Node { kind: SyntaxKind.CatchClause; parent?: TryStatement; - variableDeclaration: VariableDeclaration; + variableDeclaration?: VariableDeclaration; block: Block; } @@ -4001,7 +4001,6 @@ namespace ts { ContainsBindingPattern = 1 << 23, ContainsYield = 1 << 24, ContainsHoistedDeclarationOrCompletion = 1 << 25, - ContainsDynamicImport = 1 << 26, // Please leave this as 1 << 29. diff --git a/tests/baselines/reference/constructorWithIncompleteTypeAnnotation.errors.txt b/tests/baselines/reference/constructorWithIncompleteTypeAnnotation.errors.txt index 1d37b20a3b3e8..708f175c00250 100644 --- a/tests/baselines/reference/constructorWithIncompleteTypeAnnotation.errors.txt +++ b/tests/baselines/reference/constructorWithIncompleteTypeAnnotation.errors.txt @@ -34,7 +34,7 @@ tests/cases/compiler/constructorWithIncompleteTypeAnnotation.ts(138,13): error T tests/cases/compiler/constructorWithIncompleteTypeAnnotation.ts(141,32): error TS1005: '{' expected. tests/cases/compiler/constructorWithIncompleteTypeAnnotation.ts(143,13): error TS1005: 'try' expected. tests/cases/compiler/constructorWithIncompleteTypeAnnotation.ts(159,24): error TS1109: Expression expected. -tests/cases/compiler/constructorWithIncompleteTypeAnnotation.ts(159,30): error TS1005: '(' expected. +tests/cases/compiler/constructorWithIncompleteTypeAnnotation.ts(159,30): error TS1005: '{' expected. tests/cases/compiler/constructorWithIncompleteTypeAnnotation.ts(159,31): error TS2304: Cannot find name 'Property'. tests/cases/compiler/constructorWithIncompleteTypeAnnotation.ts(166,13): error TS2365: Operator '+=' cannot be applied to types 'number' and 'void'. tests/cases/compiler/constructorWithIncompleteTypeAnnotation.ts(180,40): error TS2447: The '^' operator is not allowed for boolean types. Consider using '!==' instead. @@ -323,7 +323,7 @@ tests/cases/compiler/constructorWithIncompleteTypeAnnotation.ts(261,1): error TS ~~~~~ !!! error TS1109: Expression expected. ~ -!!! error TS1005: '(' expected. +!!! error TS1005: '{' expected. ~~~~~~~~ !!! error TS2304: Cannot find name 'Property'. retVal += c.Member(); diff --git a/tests/baselines/reference/constructorWithIncompleteTypeAnnotation.js b/tests/baselines/reference/constructorWithIncompleteTypeAnnotation.js index 8e9dc6da68831..f33c4b6d3161e 100644 --- a/tests/baselines/reference/constructorWithIncompleteTypeAnnotation.js +++ b/tests/baselines/reference/constructorWithIncompleteTypeAnnotation.js @@ -441,7 +441,7 @@ var BasicFeatures = (function () { var xx = c; retVal += ; try { } - catch () { } + catch (_a) { } Property; retVal += c.Member(); retVal += xx.Foo() ? 0 : 1; diff --git a/tests/baselines/reference/emitter.noCatchBinding.esnext.js b/tests/baselines/reference/emitter.noCatchBinding.esnext.js new file mode 100644 index 0000000000000..f47a947ca1a48 --- /dev/null +++ b/tests/baselines/reference/emitter.noCatchBinding.esnext.js @@ -0,0 +1,22 @@ +//// [emitter.noCatchBinding.esnext.ts] +function f() { + try { } catch { } + try { } catch { + try { } catch { } + } + try { } catch { } finally { } +} + +//// [emitter.noCatchBinding.esnext.js] +function f() { + try { } + catch { } + try { } + catch { + try { } + catch { } + } + try { } + catch { } + finally { } +} diff --git a/tests/baselines/reference/emitter.noCatchBinding.esnext.symbols b/tests/baselines/reference/emitter.noCatchBinding.esnext.symbols new file mode 100644 index 0000000000000..91242f5b01cee --- /dev/null +++ b/tests/baselines/reference/emitter.noCatchBinding.esnext.symbols @@ -0,0 +1,10 @@ +=== tests/cases/conformance/emitter/esnext/noCatchBinding/emitter.noCatchBinding.esnext.ts === +function f() { +>f : Symbol(f, Decl(emitter.noCatchBinding.esnext.ts, 0, 0)) + + try { } catch { } + try { } catch { + try { } catch { } + } + try { } catch { } finally { } +} diff --git a/tests/baselines/reference/emitter.noCatchBinding.esnext.types b/tests/baselines/reference/emitter.noCatchBinding.esnext.types new file mode 100644 index 0000000000000..70c2b728a5dc6 --- /dev/null +++ b/tests/baselines/reference/emitter.noCatchBinding.esnext.types @@ -0,0 +1,10 @@ +=== tests/cases/conformance/emitter/esnext/noCatchBinding/emitter.noCatchBinding.esnext.ts === +function f() { +>f : () => void + + try { } catch { } + try { } catch { + try { } catch { } + } + try { } catch { } finally { } +} diff --git a/tests/baselines/reference/invalidTryStatements2.errors.txt b/tests/baselines/reference/invalidTryStatements2.errors.txt index f2e88cfdc22a6..a7c436236f2e4 100644 --- a/tests/baselines/reference/invalidTryStatements2.errors.txt +++ b/tests/baselines/reference/invalidTryStatements2.errors.txt @@ -1,24 +1,23 @@ -tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts(3,13): error TS1005: '(' expected. -tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts(6,5): error TS1005: 'try' expected. -tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts(12,5): error TS1005: 'try' expected. -tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts(13,5): error TS1005: 'try' expected. -tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts(22,5): error TS1005: 'try' expected. -tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts(26,5): error TS1005: 'try' expected. +tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts(2,5): error TS1005: 'try' expected. +tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts(6,12): error TS1005: 'finally' expected. +tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts(10,5): error TS1005: 'try' expected. +tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts(11,5): error TS1005: 'try' expected. +tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts(15,5): error TS1005: 'try' expected. +tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts(17,5): error TS1005: 'try' expected. +tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts(19,20): error TS1003: Identifier expected. -==== tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts (6 errors) ==== +==== tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts (7 errors) ==== function fn() { - try { - } catch { // syntax error, missing '(x)' - ~ -!!! error TS1005: '(' expected. - } - catch(x) { } // error missing try ~~~~~ !!! error TS1005: 'try' expected. - finally{ } // potential error; can be absorbed by the 'catch' + finally { } // potential error; can be absorbed by the 'catch' + + try { }; // error missing finally + ~ +!!! error TS1005: 'finally' expected. } function fn2() { @@ -28,22 +27,18 @@ tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts(26,5): catch (x) { } // error missing try ~~~~~ !!! error TS1005: 'try' expected. + + try { } finally { } // statement is here, so the 'catch' clause above doesn't absorb errors from the 'finally' clause below - // no error - try { - } - finally { - } - - // error missing try - finally { + finally { } // error missing try ~~~~~~~ !!! error TS1005: 'try' expected. - } - - // error missing try - catch (x) { + + catch (x) { } // error missing try ~~~~~ !!! error TS1005: 'try' expected. - } + + try { } catch () { } // error missing catch binding + ~ +!!! error TS1003: Identifier expected. } \ No newline at end of file diff --git a/tests/baselines/reference/invalidTryStatements2.js b/tests/baselines/reference/invalidTryStatements2.js index 118b607817ad4..50aff00c51555 100644 --- a/tests/baselines/reference/invalidTryStatements2.js +++ b/tests/baselines/reference/invalidTryStatements2.js @@ -1,43 +1,34 @@ //// [invalidTryStatements2.ts] function fn() { - try { - } catch { // syntax error, missing '(x)' - } - catch(x) { } // error missing try - finally{ } // potential error; can be absorbed by the 'catch' + finally { } // potential error; can be absorbed by the 'catch' + + try { }; // error missing finally } function fn2() { finally { } // error missing try catch (x) { } // error missing try + + try { } finally { } // statement is here, so the 'catch' clause above doesn't absorb errors from the 'finally' clause below - // no error - try { - } - finally { - } - - // error missing try - finally { - } + finally { } // error missing try + + catch (x) { } // error missing try - // error missing try - catch (x) { - } + try { } catch () { } // error missing catch binding } //// [invalidTryStatements2.js] function fn() { - try { - } - catch () { - } try { } catch (x) { } // error missing try finally { } // potential error; can be absorbed by the 'catch' + try { } + finally { } + ; // error missing finally } function fn2() { try { @@ -46,19 +37,14 @@ function fn2() { try { } catch (x) { } // error missing try - // no error + try { } + finally { } // statement is here, so the 'catch' clause above doesn't absorb errors from the 'finally' clause below try { } - finally { - } - // error missing try - try { - } - finally { - } - // error missing try + finally { } // error missing try try { } - catch (x) { - } + catch (x) { } // error missing try + try { } + catch () { } // error missing catch binding } diff --git a/tests/baselines/reference/tryStatements.js b/tests/baselines/reference/tryStatements.js index 723014c1b5259..64fe2cf06a763 100644 --- a/tests/baselines/reference/tryStatements.js +++ b/tests/baselines/reference/tryStatements.js @@ -1,26 +1,47 @@ //// [tryStatements.ts] function fn() { - try { + try { } catch { } - } catch (x) { - var x: any; + try { } catch { + try { } catch { + try { } catch { } + } + try { } catch { } } + try { } catch (x) { var x: any; } + try { } finally { } - try { }catch(z){ } finally { } + try { } catch { } finally { } + + try { } catch (z) { } finally { } } //// [tryStatements.js] function fn() { - try { + try { } + catch (_a) { } + try { } + catch (_b) { + try { } + catch (_c) { + try { } + catch (_d) { } + } + try { } + catch (_e) { } } + try { } catch (x) { var x; } try { } finally { } try { } + catch (_f) { } + finally { } + try { } catch (z) { } finally { } } diff --git a/tests/baselines/reference/tryStatements.symbols b/tests/baselines/reference/tryStatements.symbols index 945ca2d9d8894..69e9918a98b42 100644 --- a/tests/baselines/reference/tryStatements.symbols +++ b/tests/baselines/reference/tryStatements.symbols @@ -2,17 +2,23 @@ function fn() { >fn : Symbol(fn, Decl(tryStatements.ts, 0, 0)) - try { + try { } catch { } - } catch (x) { ->x : Symbol(x, Decl(tryStatements.ts, 3, 13)) - - var x: any; ->x : Symbol(x, Decl(tryStatements.ts, 4, 11)) + try { } catch { + try { } catch { + try { } catch { } + } + try { } catch { } } + try { } catch (x) { var x: any; } +>x : Symbol(x, Decl(tryStatements.ts, 10, 19)) +>x : Symbol(x, Decl(tryStatements.ts, 10, 27)) + try { } finally { } - try { }catch(z){ } finally { } ->z : Symbol(z, Decl(tryStatements.ts, 9, 17)) + try { } catch { } finally { } + + try { } catch (z) { } finally { } +>z : Symbol(z, Decl(tryStatements.ts, 16, 19)) } diff --git a/tests/baselines/reference/tryStatements.types b/tests/baselines/reference/tryStatements.types index 07bc3997d8316..05c0256813540 100644 --- a/tests/baselines/reference/tryStatements.types +++ b/tests/baselines/reference/tryStatements.types @@ -2,17 +2,23 @@ function fn() { >fn : () => void - try { + try { } catch { } - } catch (x) { ->x : any + try { } catch { + try { } catch { + try { } catch { } + } + try { } catch { } + } - var x: any; + try { } catch (x) { var x: any; } +>x : any >x : any - } try { } finally { } - try { }catch(z){ } finally { } + try { } catch { } finally { } + + try { } catch (z) { } finally { } >z : any } diff --git a/tests/cases/conformance/emitter/esnext/noCatchBinding/emitter.noCatchBinding.esnext.ts b/tests/cases/conformance/emitter/esnext/noCatchBinding/emitter.noCatchBinding.esnext.ts new file mode 100644 index 0000000000000..8e87b3c8c8fc6 --- /dev/null +++ b/tests/cases/conformance/emitter/esnext/noCatchBinding/emitter.noCatchBinding.esnext.ts @@ -0,0 +1,8 @@ +// @target: esnext +function f() { + try { } catch { } + try { } catch { + try { } catch { } + } + try { } catch { } finally { } +} \ No newline at end of file diff --git a/tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts b/tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts index 6937e509845ac..7fb1b135c906b 100644 --- a/tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts +++ b/tests/cases/conformance/statements/tryStatements/invalidTryStatements2.ts @@ -1,28 +1,20 @@ function fn() { - try { - } catch { // syntax error, missing '(x)' - } - catch(x) { } // error missing try - finally{ } // potential error; can be absorbed by the 'catch' + finally { } // potential error; can be absorbed by the 'catch' + + try { }; // error missing finally } function fn2() { finally { } // error missing try catch (x) { } // error missing try + + try { } finally { } // statement is here, so the 'catch' clause above doesn't absorb errors from the 'finally' clause below - // no error - try { - } - finally { - } - - // error missing try - finally { - } + finally { } // error missing try + + catch (x) { } // error missing try - // error missing try - catch (x) { - } + try { } catch () { } // error missing catch binding } \ No newline at end of file diff --git a/tests/cases/conformance/statements/tryStatements/tryStatements.ts b/tests/cases/conformance/statements/tryStatements/tryStatements.ts index 691c046d6d14d..0a47ba7a94bef 100644 --- a/tests/cases/conformance/statements/tryStatements/tryStatements.ts +++ b/tests/cases/conformance/statements/tryStatements/tryStatements.ts @@ -1,11 +1,19 @@ + function fn() { - try { + try { } catch { } - } catch (x) { - var x: any; + try { } catch { + try { } catch { + try { } catch { } + } + try { } catch { } } + try { } catch (x) { var x: any; } + try { } finally { } - try { }catch(z){ } finally { } + try { } catch { } finally { } + + try { } catch (z) { } finally { } } \ No newline at end of file