From f383c3c42dbb47b87d92bfae88c5a9b7ffd7eb1b Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 1 Apr 2019 14:11:30 -0700 Subject: [PATCH 1/4] Add test for parenthesized variable and function keyword within ternary --- .../parserParenthesizedVariableAndFunctionInTernary.ts | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 tests/cases/conformance/parser/ecmascript5/parserParenthesizedVariableAndFunctionInTernary.ts diff --git a/tests/cases/conformance/parser/ecmascript5/parserParenthesizedVariableAndFunctionInTernary.ts b/tests/cases/conformance/parser/ecmascript5/parserParenthesizedVariableAndFunctionInTernary.ts new file mode 100644 index 00000000000..c2bca11d934 --- /dev/null +++ b/tests/cases/conformance/parser/ecmascript5/parserParenthesizedVariableAndFunctionInTernary.ts @@ -0,0 +1,2 @@ +let a: any; +const c = true ? (a) : function() {}; From d198c5906fd2049982133e8fa542b146ca1f253e Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 2 Apr 2019 10:58:11 -0700 Subject: [PATCH 2/4] Be stricter while parsing arrow function heads in conditional expressions --- src/compiler/parser.ts | 6 ++++-- src/compiler/types.ts | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 03d0f166b81..7193e9083e6 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -3695,7 +3695,7 @@ namespace ts { // - "a ? (b): c" will have "(b):" parsed as a signature with a return type annotation. // // So we need just a bit of lookahead to ensure that it can only be a signature. - if (!allowAmbiguity && token() !== SyntaxKind.EqualsGreaterThanToken && token() !== SyntaxKind.OpenBraceToken) { + if (!allowAmbiguity && token() !== SyntaxKind.EqualsGreaterThanToken && (contextFlags & NodeFlags.InConditionalWhenTrue || token() !== SyntaxKind.OpenBraceToken)) { // Returning undefined here will cause our caller to rewind to where we started from. return undefined; } @@ -3747,7 +3747,9 @@ namespace ts { const node = createNode(SyntaxKind.ConditionalExpression, leftOperand.pos); node.condition = leftOperand; node.questionToken = questionToken; - node.whenTrue = doOutsideOfContext(disallowInAndDecoratorContext, parseAssignmentExpressionOrHigher); + node.whenTrue = doInsideOfContext( + NodeFlags.InConditionalWhenTrue, + () => doOutsideOfContext(disallowInAndDecoratorContext, parseAssignmentExpressionOrHigher)); node.colonToken = parseExpectedToken(SyntaxKind.ColonToken); node.whenFalse = nodeIsPresent(node.colonToken) ? parseAssignmentExpressionOrHigher() diff --git a/src/compiler/types.ts b/src/compiler/types.ts index bcc3218a017..e24f3cbc695 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -553,6 +553,7 @@ namespace ts { /* @internal */ Ambient = 1 << 22, // If node was inside an ambient context -- a declaration file, or inside something with the `declare` modifier. /* @internal */ InWithStatement = 1 << 23, // If any ancestor of node was the `statement` of a WithStatement (not the `expression`) JsonFile = 1 << 24, // If node was parsed in a Json + /* @internal */ InConditionalWhenTrue = 1 << 25, // If node was parsed in the true side of a ConditionalExpression BlockScoped = Let | Const, From 96990800e3f4548ce7b96752b04a0665cb1e0949 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 1 Apr 2019 14:13:09 -0700 Subject: [PATCH 3/4] Add new baselines --- ...arserParenthesizedVariableAndFunctionInTernary.js | 8 ++++++++ ...ParenthesizedVariableAndFunctionInTernary.symbols | 8 ++++++++ ...erParenthesizedVariableAndFunctionInTernary.types | 12 ++++++++++++ 3 files changed, 28 insertions(+) create mode 100644 tests/baselines/reference/parserParenthesizedVariableAndFunctionInTernary.js create mode 100644 tests/baselines/reference/parserParenthesizedVariableAndFunctionInTernary.symbols create mode 100644 tests/baselines/reference/parserParenthesizedVariableAndFunctionInTernary.types diff --git a/tests/baselines/reference/parserParenthesizedVariableAndFunctionInTernary.js b/tests/baselines/reference/parserParenthesizedVariableAndFunctionInTernary.js new file mode 100644 index 00000000000..dde6826c59c --- /dev/null +++ b/tests/baselines/reference/parserParenthesizedVariableAndFunctionInTernary.js @@ -0,0 +1,8 @@ +//// [parserParenthesizedVariableAndFunctionInTernary.ts] +let a: any; +const c = true ? (a) : function() {}; + + +//// [parserParenthesizedVariableAndFunctionInTernary.js] +var a; +var c = true ? (a) : function () { }; diff --git a/tests/baselines/reference/parserParenthesizedVariableAndFunctionInTernary.symbols b/tests/baselines/reference/parserParenthesizedVariableAndFunctionInTernary.symbols new file mode 100644 index 00000000000..3b64c950750 --- /dev/null +++ b/tests/baselines/reference/parserParenthesizedVariableAndFunctionInTernary.symbols @@ -0,0 +1,8 @@ +=== tests/cases/conformance/parser/ecmascript5/parserParenthesizedVariableAndFunctionInTernary.ts === +let a: any; +>a : Symbol(a, Decl(parserParenthesizedVariableAndFunctionInTernary.ts, 0, 3)) + +const c = true ? (a) : function() {}; +>c : Symbol(c, Decl(parserParenthesizedVariableAndFunctionInTernary.ts, 1, 5)) +>a : Symbol(a, Decl(parserParenthesizedVariableAndFunctionInTernary.ts, 0, 3)) + diff --git a/tests/baselines/reference/parserParenthesizedVariableAndFunctionInTernary.types b/tests/baselines/reference/parserParenthesizedVariableAndFunctionInTernary.types new file mode 100644 index 00000000000..d3cd1f4fbb6 --- /dev/null +++ b/tests/baselines/reference/parserParenthesizedVariableAndFunctionInTernary.types @@ -0,0 +1,12 @@ +=== tests/cases/conformance/parser/ecmascript5/parserParenthesizedVariableAndFunctionInTernary.ts === +let a: any; +>a : any + +const c = true ? (a) : function() {}; +>c : any +>true ? (a) : function() {} : any +>true : true +>(a) : any +>a : any +>function() {} : () => void + From 96660f6c00bab6b3aa8384888644c4811dd9d314 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 2 Apr 2019 11:31:05 -0700 Subject: [PATCH 4/4] Just look at the current node instead of context --- src/compiler/parser.ts | 8 ++++---- src/compiler/types.ts | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 7193e9083e6..dd06d98cdbc 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -3693,9 +3693,11 @@ namespace ts { // - "(x = 10)" is an assignment expression parsed as a signature with a default parameter value. // - "(x,y)" is a comma expression parsed as a signature with two parameters. // - "a ? (b): c" will have "(b):" parsed as a signature with a return type annotation. + // - "a ? (b): function() {}" will too, since function() is a valid JSDoc function type. // // So we need just a bit of lookahead to ensure that it can only be a signature. - if (!allowAmbiguity && token() !== SyntaxKind.EqualsGreaterThanToken && (contextFlags & NodeFlags.InConditionalWhenTrue || token() !== SyntaxKind.OpenBraceToken)) { + const hasJSDocFunctionType = node.type && isJSDocFunctionType(node.type); + if (!allowAmbiguity && token() !== SyntaxKind.EqualsGreaterThanToken && (hasJSDocFunctionType || token() !== SyntaxKind.OpenBraceToken)) { // Returning undefined here will cause our caller to rewind to where we started from. return undefined; } @@ -3747,9 +3749,7 @@ namespace ts { const node = createNode(SyntaxKind.ConditionalExpression, leftOperand.pos); node.condition = leftOperand; node.questionToken = questionToken; - node.whenTrue = doInsideOfContext( - NodeFlags.InConditionalWhenTrue, - () => doOutsideOfContext(disallowInAndDecoratorContext, parseAssignmentExpressionOrHigher)); + node.whenTrue = doOutsideOfContext(disallowInAndDecoratorContext, parseAssignmentExpressionOrHigher); node.colonToken = parseExpectedToken(SyntaxKind.ColonToken); node.whenFalse = nodeIsPresent(node.colonToken) ? parseAssignmentExpressionOrHigher() diff --git a/src/compiler/types.ts b/src/compiler/types.ts index e24f3cbc695..bcc3218a017 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -553,7 +553,6 @@ namespace ts { /* @internal */ Ambient = 1 << 22, // If node was inside an ambient context -- a declaration file, or inside something with the `declare` modifier. /* @internal */ InWithStatement = 1 << 23, // If any ancestor of node was the `statement` of a WithStatement (not the `expression`) JsonFile = 1 << 24, // If node was parsed in a Json - /* @internal */ InConditionalWhenTrue = 1 << 25, // If node was parsed in the true side of a ConditionalExpression BlockScoped = Let | Const,