• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* @internal */
2namespace ts {
3    export function createParenthesizerRules(factory: NodeFactory): ParenthesizerRules {
4        interface BinaryPlusExpression extends BinaryExpression {
5            cachedLiteralKind: SyntaxKind;
6        }
7
8        return {
9            parenthesizeLeftSideOfBinary,
10            parenthesizeRightSideOfBinary,
11            parenthesizeExpressionOfComputedPropertyName,
12            parenthesizeConditionOfConditionalExpression,
13            parenthesizeBranchOfConditionalExpression,
14            parenthesizeExpressionOfExportDefault,
15            parenthesizeExpressionOfNew,
16            parenthesizeLeftSideOfAccess,
17            parenthesizeOperandOfPostfixUnary,
18            parenthesizeOperandOfPrefixUnary,
19            parenthesizeExpressionsOfCommaDelimitedList,
20            parenthesizeExpressionForDisallowedComma,
21            parenthesizeExpressionOfExpressionStatement,
22            parenthesizeConciseBodyOfArrowFunction,
23            parenthesizeMemberOfConditionalType,
24            parenthesizeMemberOfElementType,
25            parenthesizeElementTypeOfArrayType,
26            parenthesizeConstituentTypesOfUnionOrIntersectionType,
27            parenthesizeTypeArguments,
28        };
29
30        /**
31         * Determines whether the operand to a BinaryExpression needs to be parenthesized.
32         *
33         * @param binaryOperator The operator for the BinaryExpression.
34         * @param operand The operand for the BinaryExpression.
35         * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the
36         *                           BinaryExpression.
37         */
38        function binaryOperandNeedsParentheses(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean, leftOperand: Expression | undefined) {
39            // If the operand has lower precedence, then it needs to be parenthesized to preserve the
40            // intent of the expression. For example, if the operand is `a + b` and the operator is
41            // `*`, then we need to parenthesize the operand to preserve the intended order of
42            // operations: `(a + b) * x`.
43            //
44            // If the operand has higher precedence, then it does not need to be parenthesized. For
45            // example, if the operand is `a * b` and the operator is `+`, then we do not need to
46            // parenthesize to preserve the intended order of operations: `a * b + x`.
47            //
48            // If the operand has the same precedence, then we need to check the associativity of
49            // the operator based on whether this is the left or right operand of the expression.
50            //
51            // For example, if `a / d` is on the right of operator `*`, we need to parenthesize
52            // to preserve the intended order of operations: `x * (a / d)`
53            //
54            // If `a ** d` is on the left of operator `**`, we need to parenthesize to preserve
55            // the intended order of operations: `(a ** b) ** c`
56            const binaryOperatorPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, binaryOperator);
57            const binaryOperatorAssociativity = getOperatorAssociativity(SyntaxKind.BinaryExpression, binaryOperator);
58            const emittedOperand = skipPartiallyEmittedExpressions(operand);
59            if (!isLeftSideOfBinary && operand.kind === SyntaxKind.ArrowFunction && binaryOperatorPrecedence > OperatorPrecedence.Assignment) {
60                // We need to parenthesize arrow functions on the right side to avoid it being
61                // parsed as parenthesized expression: `a && (() => {})`
62                return true;
63            }
64            const operandPrecedence = getExpressionPrecedence(emittedOperand);
65            switch (compareValues(operandPrecedence, binaryOperatorPrecedence)) {
66                case Comparison.LessThan:
67                    // If the operand is the right side of a right-associative binary operation
68                    // and is a yield expression, then we do not need parentheses.
69                    if (!isLeftSideOfBinary
70                        && binaryOperatorAssociativity === Associativity.Right
71                        && operand.kind === SyntaxKind.YieldExpression) {
72                        return false;
73                    }
74
75                    return true;
76
77                case Comparison.GreaterThan:
78                    return false;
79
80                case Comparison.EqualTo:
81                    if (isLeftSideOfBinary) {
82                        // No need to parenthesize the left operand when the binary operator is
83                        // left associative:
84                        //  (a*b)/x    -> a*b/x
85                        //  (a**b)/x   -> a**b/x
86                        //
87                        // Parentheses are needed for the left operand when the binary operator is
88                        // right associative:
89                        //  (a/b)**x   -> (a/b)**x
90                        //  (a**b)**x  -> (a**b)**x
91                        return binaryOperatorAssociativity === Associativity.Right;
92                    }
93                    else {
94                        if (isBinaryExpression(emittedOperand)
95                            && emittedOperand.operatorToken.kind === binaryOperator) {
96                            // No need to parenthesize the right operand when the binary operator and
97                            // operand are the same and one of the following:
98                            //  x*(a*b)     => x*a*b
99                            //  x|(a|b)     => x|a|b
100                            //  x&(a&b)     => x&a&b
101                            //  x^(a^b)     => x^a^b
102                            if (operatorHasAssociativeProperty(binaryOperator)) {
103                                return false;
104                            }
105
106                            // No need to parenthesize the right operand when the binary operator
107                            // is plus (+) if both the left and right operands consist solely of either
108                            // literals of the same kind or binary plus (+) expressions for literals of
109                            // the same kind (recursively).
110                            //  "a"+(1+2)       => "a"+(1+2)
111                            //  "a"+("b"+"c")   => "a"+"b"+"c"
112                            if (binaryOperator === SyntaxKind.PlusToken) {
113                                const leftKind = leftOperand ? getLiteralKindOfBinaryPlusOperand(leftOperand) : SyntaxKind.Unknown;
114                                if (isLiteralKind(leftKind) && leftKind === getLiteralKindOfBinaryPlusOperand(emittedOperand)) {
115                                    return false;
116                                }
117                            }
118                        }
119
120                        // No need to parenthesize the right operand when the operand is right
121                        // associative:
122                        //  x/(a**b)    -> x/a**b
123                        //  x**(a**b)   -> x**a**b
124                        //
125                        // Parentheses are needed for the right operand when the operand is left
126                        // associative:
127                        //  x/(a*b)     -> x/(a*b)
128                        //  x**(a/b)    -> x**(a/b)
129                        const operandAssociativity = getExpressionAssociativity(emittedOperand);
130                        return operandAssociativity === Associativity.Left;
131                    }
132            }
133        }
134
135        /**
136         * Determines whether a binary operator is mathematically associative.
137         *
138         * @param binaryOperator The binary operator.
139         */
140        function operatorHasAssociativeProperty(binaryOperator: SyntaxKind) {
141            // The following operators are associative in JavaScript:
142            //  (a*b)*c     -> a*(b*c)  -> a*b*c
143            //  (a|b)|c     -> a|(b|c)  -> a|b|c
144            //  (a&b)&c     -> a&(b&c)  -> a&b&c
145            //  (a^b)^c     -> a^(b^c)  -> a^b^c
146            //
147            // While addition is associative in mathematics, JavaScript's `+` is not
148            // guaranteed to be associative as it is overloaded with string concatenation.
149            return binaryOperator === SyntaxKind.AsteriskToken
150                || binaryOperator === SyntaxKind.BarToken
151                || binaryOperator === SyntaxKind.AmpersandToken
152                || binaryOperator === SyntaxKind.CaretToken;
153        }
154
155        /**
156         * This function determines whether an expression consists of a homogeneous set of
157         * literal expressions or binary plus expressions that all share the same literal kind.
158         * It is used to determine whether the right-hand operand of a binary plus expression can be
159         * emitted without parentheses.
160         */
161        function getLiteralKindOfBinaryPlusOperand(node: Expression): SyntaxKind {
162            node = skipPartiallyEmittedExpressions(node);
163
164            if (isLiteralKind(node.kind)) {
165                return node.kind;
166            }
167
168            if (node.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>node).operatorToken.kind === SyntaxKind.PlusToken) {
169                if ((<BinaryPlusExpression>node).cachedLiteralKind !== undefined) {
170                    return (<BinaryPlusExpression>node).cachedLiteralKind;
171                }
172
173                const leftKind = getLiteralKindOfBinaryPlusOperand((<BinaryExpression>node).left);
174                const literalKind = isLiteralKind(leftKind)
175                    && leftKind === getLiteralKindOfBinaryPlusOperand((<BinaryExpression>node).right)
176                        ? leftKind
177                        : SyntaxKind.Unknown;
178
179                (<BinaryPlusExpression>node).cachedLiteralKind = literalKind;
180                return literalKind;
181            }
182
183            return SyntaxKind.Unknown;
184        }
185
186        /**
187         * Wraps the operand to a BinaryExpression in parentheses if they are needed to preserve the intended
188         * order of operations.
189         *
190         * @param binaryOperator The operator for the BinaryExpression.
191         * @param operand The operand for the BinaryExpression.
192         * @param isLeftSideOfBinary A value indicating whether the operand is the left side of the
193         *                           BinaryExpression.
194         */
195        function parenthesizeBinaryOperand(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean, leftOperand?: Expression) {
196            const skipped = skipPartiallyEmittedExpressions(operand);
197
198            // If the resulting expression is already parenthesized, we do not need to do any further processing.
199            if (skipped.kind === SyntaxKind.ParenthesizedExpression) {
200                return operand;
201            }
202
203            return binaryOperandNeedsParentheses(binaryOperator, operand, isLeftSideOfBinary, leftOperand)
204                ? factory.createParenthesizedExpression(operand)
205                : operand;
206        }
207
208
209        function parenthesizeLeftSideOfBinary(binaryOperator: SyntaxKind, leftSide: Expression): Expression {
210            return parenthesizeBinaryOperand(binaryOperator, leftSide, /*isLeftSideOfBinary*/ true);
211        }
212
213        function parenthesizeRightSideOfBinary(binaryOperator: SyntaxKind, leftSide: Expression, rightSide: Expression): Expression {
214            return parenthesizeBinaryOperand(binaryOperator, rightSide, /*isLeftSideOfBinary*/ false, leftSide);
215        }
216
217        function parenthesizeExpressionOfComputedPropertyName(expression: Expression): Expression {
218            return isCommaSequence(expression) ? factory.createParenthesizedExpression(expression) : expression;
219        }
220
221        function parenthesizeConditionOfConditionalExpression(condition: Expression): Expression {
222            const conditionalPrecedence = getOperatorPrecedence(SyntaxKind.ConditionalExpression, SyntaxKind.QuestionToken);
223            const emittedCondition = skipPartiallyEmittedExpressions(condition);
224            const conditionPrecedence = getExpressionPrecedence(emittedCondition);
225            if (compareValues(conditionPrecedence, conditionalPrecedence) !== Comparison.GreaterThan) {
226                return factory.createParenthesizedExpression(condition);
227            }
228            return condition;
229        }
230
231        function parenthesizeBranchOfConditionalExpression(branch: Expression): Expression {
232            // per ES grammar both 'whenTrue' and 'whenFalse' parts of conditional expression are assignment expressions
233            // so in case when comma expression is introduced as a part of previous transformations
234            // if should be wrapped in parens since comma operator has the lowest precedence
235            const emittedExpression = skipPartiallyEmittedExpressions(branch);
236            return isCommaSequence(emittedExpression)
237                ? factory.createParenthesizedExpression(branch)
238                : branch;
239        }
240
241        /**
242         *  [Per the spec](https://tc39.github.io/ecma262/#prod-ExportDeclaration), `export default` accepts _AssigmentExpression_ but
243         *  has a lookahead restriction for `function`, `async function`, and `class`.
244         *
245         * Basically, that means we need to parenthesize in the following cases:
246         *
247         * - BinaryExpression of CommaToken
248         * - CommaList (synthetic list of multiple comma expressions)
249         * - FunctionExpression
250         * - ClassExpression
251         */
252        function parenthesizeExpressionOfExportDefault(expression: Expression): Expression {
253            const check = skipPartiallyEmittedExpressions(expression);
254            let needsParens = isCommaSequence(check);
255            if (!needsParens) {
256                switch (getLeftmostExpression(check, /*stopAtCallExpression*/ false).kind) {
257                    case SyntaxKind.ClassExpression:
258                    case SyntaxKind.FunctionExpression:
259                        needsParens = true;
260                }
261            }
262            return needsParens ? factory.createParenthesizedExpression(expression) : expression;
263        }
264
265        /**
266         * Wraps an expression in parentheses if it is needed in order to use the expression
267         * as the expression of a `NewExpression` node.
268         */
269        function parenthesizeExpressionOfNew(expression: Expression): LeftHandSideExpression {
270            const leftmostExpr = getLeftmostExpression(expression, /*stopAtCallExpressions*/ true);
271            switch (leftmostExpr.kind) {
272                case SyntaxKind.CallExpression:
273                    return factory.createParenthesizedExpression(expression);
274
275                case SyntaxKind.NewExpression:
276                    return !(leftmostExpr as NewExpression).arguments
277                        ? factory.createParenthesizedExpression(expression)
278                        : expression as LeftHandSideExpression; // TODO(rbuckton): Verify this assertion holds
279            }
280
281            return parenthesizeLeftSideOfAccess(expression);
282        }
283
284        /**
285         * Wraps an expression in parentheses if it is needed in order to use the expression for
286         * property or element access.
287         */
288        function parenthesizeLeftSideOfAccess(expression: Expression): LeftHandSideExpression {
289            // isLeftHandSideExpression is almost the correct criterion for when it is not necessary
290            // to parenthesize the expression before a dot. The known exception is:
291            //
292            //    NewExpression:
293            //       new C.x        -> not the same as (new C).x
294            //
295            const emittedExpression = skipPartiallyEmittedExpressions(expression);
296            if (isLeftHandSideExpression(emittedExpression)
297                && (emittedExpression.kind !== SyntaxKind.NewExpression || (<NewExpression>emittedExpression).arguments)) {
298                // TODO(rbuckton): Verify whether this assertion holds.
299                return expression as LeftHandSideExpression;
300            }
301
302            // TODO(rbuckton): Verifiy whether `setTextRange` is needed.
303            return setTextRange(factory.createParenthesizedExpression(expression), expression);
304        }
305
306        function parenthesizeOperandOfPostfixUnary(operand: Expression): LeftHandSideExpression {
307            // TODO(rbuckton): Verifiy whether `setTextRange` is needed.
308            return isLeftHandSideExpression(operand) ? operand : setTextRange(factory.createParenthesizedExpression(operand), operand);
309        }
310
311        function parenthesizeOperandOfPrefixUnary(operand: Expression): UnaryExpression {
312            // TODO(rbuckton): Verifiy whether `setTextRange` is needed.
313            return isUnaryExpression(operand) ? operand : setTextRange(factory.createParenthesizedExpression(operand), operand);
314        }
315
316        function parenthesizeExpressionsOfCommaDelimitedList(elements: NodeArray<Expression>): NodeArray<Expression> {
317            const result = sameMap(elements, parenthesizeExpressionForDisallowedComma);
318            return setTextRange(factory.createNodeArray(result, elements.hasTrailingComma), elements);
319        }
320
321        function parenthesizeExpressionForDisallowedComma(expression: Expression): Expression {
322            const emittedExpression = skipPartiallyEmittedExpressions(expression);
323            const expressionPrecedence = getExpressionPrecedence(emittedExpression);
324            const commaPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, SyntaxKind.CommaToken);
325            // TODO(rbuckton): Verifiy whether `setTextRange` is needed.
326            return expressionPrecedence > commaPrecedence ? expression : setTextRange(factory.createParenthesizedExpression(expression), expression);
327        }
328
329        function parenthesizeExpressionOfExpressionStatement(expression: Expression): Expression {
330            const emittedExpression = skipPartiallyEmittedExpressions(expression);
331            if (isCallExpression(emittedExpression)) {
332                const callee = emittedExpression.expression;
333                const kind = skipPartiallyEmittedExpressions(callee).kind;
334                if (kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction) {
335                    // TODO(rbuckton): Verifiy whether `setTextRange` is needed.
336                    const updated = factory.updateCallExpression(
337                        emittedExpression,
338                        setTextRange(factory.createParenthesizedExpression(callee), callee),
339                        emittedExpression.typeArguments,
340                        emittedExpression.arguments
341                    );
342                    return factory.restoreOuterExpressions(expression, updated, OuterExpressionKinds.PartiallyEmittedExpressions);
343                }
344            }
345
346            const leftmostExpressionKind = getLeftmostExpression(emittedExpression, /*stopAtCallExpressions*/ false).kind;
347            if (leftmostExpressionKind === SyntaxKind.ObjectLiteralExpression || leftmostExpressionKind === SyntaxKind.FunctionExpression) {
348                // TODO(rbuckton): Verifiy whether `setTextRange` is needed.
349                return setTextRange(factory.createParenthesizedExpression(expression), expression);
350            }
351
352            return expression;
353        }
354
355        function parenthesizeConciseBodyOfArrowFunction(body: ConciseBody): ConciseBody {
356            if (!isBlock(body) && (isCommaSequence(body) || getLeftmostExpression(body, /*stopAtCallExpressions*/ false).kind === SyntaxKind.ObjectLiteralExpression)) {
357                // TODO(rbuckton): Verifiy whether `setTextRange` is needed.
358                return setTextRange(factory.createParenthesizedExpression(body), body);
359            }
360
361            return body;
362        }
363
364        function parenthesizeMemberOfConditionalType(member: TypeNode): TypeNode {
365            return member.kind === SyntaxKind.ConditionalType ? factory.createParenthesizedType(member) : member;
366        }
367
368        function parenthesizeMemberOfElementType(member: TypeNode): TypeNode {
369            switch (member.kind) {
370                case SyntaxKind.UnionType:
371                case SyntaxKind.IntersectionType:
372                case SyntaxKind.FunctionType:
373                case SyntaxKind.ConstructorType:
374                    return factory.createParenthesizedType(member);
375            }
376            return parenthesizeMemberOfConditionalType(member);
377        }
378
379        function parenthesizeElementTypeOfArrayType(member: TypeNode): TypeNode {
380            switch (member.kind) {
381                case SyntaxKind.TypeQuery:
382                case SyntaxKind.TypeOperator:
383                case SyntaxKind.InferType:
384                    return factory.createParenthesizedType(member);
385            }
386            return parenthesizeMemberOfElementType(member);
387        }
388
389        function parenthesizeConstituentTypesOfUnionOrIntersectionType(members: readonly TypeNode[]): NodeArray<TypeNode> {
390            return factory.createNodeArray(sameMap(members, parenthesizeMemberOfElementType));
391
392        }
393
394        function parenthesizeOrdinalTypeArgument(node: TypeNode, i: number) {
395            return i === 0 && isFunctionOrConstructorTypeNode(node) && node.typeParameters ? factory.createParenthesizedType(node) : node;
396        }
397
398        function parenthesizeTypeArguments(typeArguments: NodeArray<TypeNode> | undefined): NodeArray<TypeNode> | undefined {
399            if (some(typeArguments)) {
400                return factory.createNodeArray(sameMap(typeArguments, parenthesizeOrdinalTypeArgument));
401            }
402        }
403    }
404
405    export const nullParenthesizerRules: ParenthesizerRules = {
406        parenthesizeLeftSideOfBinary: (_binaryOperator, leftSide) => leftSide,
407        parenthesizeRightSideOfBinary: (_binaryOperator, _leftSide, rightSide) => rightSide,
408        parenthesizeExpressionOfComputedPropertyName: identity,
409        parenthesizeConditionOfConditionalExpression: identity,
410        parenthesizeBranchOfConditionalExpression: identity,
411        parenthesizeExpressionOfExportDefault: identity,
412        parenthesizeExpressionOfNew: expression => cast(expression, isLeftHandSideExpression),
413        parenthesizeLeftSideOfAccess: expression => cast(expression, isLeftHandSideExpression),
414        parenthesizeOperandOfPostfixUnary: operand => cast(operand, isLeftHandSideExpression),
415        parenthesizeOperandOfPrefixUnary: operand => cast(operand, isUnaryExpression),
416        parenthesizeExpressionsOfCommaDelimitedList: nodes => cast(nodes, isNodeArray),
417        parenthesizeExpressionForDisallowedComma: identity,
418        parenthesizeExpressionOfExpressionStatement: identity,
419        parenthesizeConciseBodyOfArrowFunction: identity,
420        parenthesizeMemberOfConditionalType: identity,
421        parenthesizeMemberOfElementType: identity,
422        parenthesizeElementTypeOfArrayType: identity,
423        parenthesizeConstituentTypesOfUnionOrIntersectionType: nodes => cast(nodes, isNodeArray),
424        parenthesizeTypeArguments: nodes => nodes && cast(nodes, isNodeArray),
425    };
426}