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}