diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 86911417cb6..7bec07a0e76 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -4024,6 +4024,11 @@ namespace ts { const binaryOperatorPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, binaryOperator); const binaryOperatorAssociativity = getOperatorAssociativity(SyntaxKind.BinaryExpression, binaryOperator); const emittedOperand = skipPartiallyEmittedExpressions(operand); + if (!isLeftSideOfBinary && operand.kind === SyntaxKind.ArrowFunction && binaryOperatorPrecedence > 4) { + // We need to parenthesize arrow functions on the right side to avoid it being + // parsed as parenthesized expression: `a && (() => {})` + return true; + } const operandPrecedence = getExpressionPrecedence(emittedOperand); switch (compareValues(operandPrecedence, binaryOperatorPrecedence)) { case Comparison.LessThan: diff --git a/src/testRunner/unittests/factory.ts b/src/testRunner/unittests/factory.ts index e17311147ad..402c399d728 100644 --- a/src/testRunner/unittests/factory.ts +++ b/src/testRunner/unittests/factory.ts @@ -1,5 +1,8 @@ namespace ts { describe("FactoryAPI", () => { + function assertSyntaxKind(node: Node, expected: SyntaxKind) { + assert.strictEqual(node.kind, expected, `Actual: ${Debug.showSyntaxKind(node)} Expected: ${(ts as any).SyntaxKind[expected]}`); + } describe("createExportAssignment", () => { it("parenthesizes default export if necessary", () => { function checkExpression(expression: Expression) { @@ -9,7 +12,7 @@ namespace ts { /*isExportEquals*/ false, expression, ); - assert.strictEqual(node.expression.kind, SyntaxKind.ParenthesizedExpression); + assertSyntaxKind(node.expression, SyntaxKind.ParenthesizedExpression); } const clazz = createClassExpression(/*modifiers*/ undefined, "C", /*typeParameters*/ undefined, /*heritageClauses*/ undefined, [ @@ -39,7 +42,7 @@ namespace ts { /*equalsGreaterThanToken*/ undefined, body, ); - assert.strictEqual(node.body.kind, SyntaxKind.ParenthesizedExpression); + assertSyntaxKind(node.body, SyntaxKind.ParenthesizedExpression); } checkBody(createObjectLiteral()); @@ -50,5 +53,30 @@ namespace ts { checkBody(createBinary(createLiteral("a"), SyntaxKind.CommaToken, createLiteral("b"))); }); }); + + describe("createBinaryExpression", () => { + it("parenthesizes arrow function in RHS if necessary", () => { + const lhs = createIdentifier("foo"); + const rhs = createArrowFunction( + /*modifiers*/ undefined, + /*typeParameters*/ undefined, + [], + /*type*/ undefined, + /*equalsGreaterThanToken*/ undefined, + createBlock([]), + ); + function checkRhs(operator: BinaryOperator, expectParens: boolean) { + const node = createBinary(lhs, operator, rhs); + assertSyntaxKind(node.right, expectParens ? SyntaxKind.ParenthesizedExpression : SyntaxKind.ArrowFunction); + } + + checkRhs(SyntaxKind.CommaToken, /*expectParens*/ false); + checkRhs(SyntaxKind.EqualsToken, /*expectParens*/ false); + checkRhs(SyntaxKind.PlusEqualsToken, /*expectParens*/ false); + checkRhs(SyntaxKind.BarBarToken, /*expectParens*/ true); + checkRhs(SyntaxKind.AmpersandAmpersandToken, /*expectParens*/ true); + checkRhs(SyntaxKind.EqualsEqualsToken, /*expectParens*/ true); + }); + }); }); }