1/*@internal*/ 2namespace ts { 3 export function transformES2020(context: TransformationContext) { 4 const { 5 factory, 6 hoistVariableDeclaration, 7 } = context; 8 9 return chainBundle(context, transformSourceFile); 10 11 function transformSourceFile(node: SourceFile) { 12 if (node.isDeclarationFile) { 13 return node; 14 } 15 16 return visitEachChild(node, visitor, context); 17 } 18 19 function visitor(node: Node): VisitResult<Node> { 20 if ((node.transformFlags & TransformFlags.ContainsES2020) === 0) { 21 return node; 22 } 23 switch (node.kind) { 24 case SyntaxKind.CallExpression: { 25 const updated = visitNonOptionalCallExpression(node as CallExpression, /*captureThisArg*/ false); 26 Debug.assertNotNode(updated, isSyntheticReference); 27 return updated; 28 } 29 case SyntaxKind.PropertyAccessExpression: 30 case SyntaxKind.ElementAccessExpression: 31 if (isOptionalChain(node)) { 32 const updated = visitOptionalExpression(node, /*captureThisArg*/ false, /*isDelete*/ false); 33 Debug.assertNotNode(updated, isSyntheticReference); 34 return updated; 35 } 36 return visitEachChild(node, visitor, context); 37 case SyntaxKind.BinaryExpression: 38 if ((node as BinaryExpression).operatorToken.kind === SyntaxKind.QuestionQuestionToken) { 39 return transformNullishCoalescingExpression(node as BinaryExpression); 40 } 41 return visitEachChild(node, visitor, context); 42 case SyntaxKind.DeleteExpression: 43 return visitDeleteExpression(node as DeleteExpression); 44 default: 45 return visitEachChild(node, visitor, context); 46 } 47 } 48 49 function flattenChain(chain: OptionalChain) { 50 Debug.assertNotNode(chain, isNonNullChain); 51 const links: OptionalChain[] = [chain]; 52 while (!chain.questionDotToken && !isTaggedTemplateExpression(chain)) { 53 chain = cast(skipPartiallyEmittedExpressions(chain.expression), isOptionalChain); 54 Debug.assertNotNode(chain, isNonNullChain); 55 links.unshift(chain); 56 } 57 return { expression: chain.expression, chain: links }; 58 } 59 60 function visitNonOptionalParenthesizedExpression(node: ParenthesizedExpression, captureThisArg: boolean, isDelete: boolean): Expression { 61 const expression = visitNonOptionalExpression(node.expression, captureThisArg, isDelete); 62 if (isSyntheticReference(expression)) { 63 // `(a.b)` -> { expression `((_a = a).b)`, thisArg: `_a` } 64 // `(a[b])` -> { expression `((_a = a)[b])`, thisArg: `_a` } 65 return factory.createSyntheticReferenceExpression(factory.updateParenthesizedExpression(node, expression.expression), expression.thisArg); 66 } 67 return factory.updateParenthesizedExpression(node, expression); 68 } 69 70 function visitNonOptionalPropertyOrElementAccessExpression(node: AccessExpression, captureThisArg: boolean, isDelete: boolean): Expression { 71 if (isOptionalChain(node)) { 72 // If `node` is an optional chain, then it is the outermost chain of an optional expression. 73 return visitOptionalExpression(node, captureThisArg, isDelete); 74 } 75 76 let expression: Expression = visitNode(node.expression, visitor, isExpression); 77 Debug.assertNotNode(expression, isSyntheticReference); 78 79 let thisArg: Expression | undefined; 80 if (captureThisArg) { 81 if (!isSimpleCopiableExpression(expression)) { 82 thisArg = factory.createTempVariable(hoistVariableDeclaration); 83 expression = factory.createAssignment(thisArg, expression); 84 } 85 else { 86 thisArg = expression; 87 } 88 } 89 90 expression = node.kind === SyntaxKind.PropertyAccessExpression 91 ? factory.updatePropertyAccessExpression(node, expression, visitNode(node.name, visitor, isIdentifier)) 92 : factory.updateElementAccessExpression(node, expression, visitNode(node.argumentExpression, visitor, isExpression)); 93 return thisArg ? factory.createSyntheticReferenceExpression(expression, thisArg) : expression; 94 } 95 96 function visitNonOptionalCallExpression(node: CallExpression, captureThisArg: boolean): Expression { 97 if (isOptionalChain(node)) { 98 // If `node` is an optional chain, then it is the outermost chain of an optional expression. 99 return visitOptionalExpression(node, captureThisArg, /*isDelete*/ false); 100 } 101 if (isParenthesizedExpression(node.expression) && isOptionalChain(skipParentheses(node.expression))) { 102 // capture thisArg for calls of parenthesized optional chains like `(foo?.bar)()` 103 const expression = visitNonOptionalParenthesizedExpression(node.expression, /*captureThisArg*/ true, /*isDelete*/ false); 104 const args = visitNodes(node.arguments, visitor, isExpression); 105 if (isSyntheticReference(expression)) { 106 return setTextRange(factory.createFunctionCallCall(expression.expression, expression.thisArg, args), node); 107 } 108 return factory.updateCallExpression(node, expression, /*typeArguments*/ undefined, args); 109 } 110 return visitEachChild(node, visitor, context); 111 } 112 113 function visitNonOptionalExpression(node: Expression, captureThisArg: boolean, isDelete: boolean): Expression { 114 switch (node.kind) { 115 case SyntaxKind.ParenthesizedExpression: return visitNonOptionalParenthesizedExpression(node as ParenthesizedExpression, captureThisArg, isDelete); 116 case SyntaxKind.PropertyAccessExpression: 117 case SyntaxKind.ElementAccessExpression: return visitNonOptionalPropertyOrElementAccessExpression(node as AccessExpression, captureThisArg, isDelete); 118 case SyntaxKind.CallExpression: return visitNonOptionalCallExpression(node as CallExpression, captureThisArg); 119 default: return visitNode(node, visitor, isExpression); 120 } 121 } 122 123 function visitOptionalExpression(node: OptionalChain, captureThisArg: boolean, isDelete: boolean): Expression { 124 const { expression, chain } = flattenChain(node); 125 const left = visitNonOptionalExpression(skipPartiallyEmittedExpressions(expression), isCallChain(chain[0]), /*isDelete*/ false); 126 let leftThisArg = isSyntheticReference(left) ? left.thisArg : undefined; 127 let capturedLeft = isSyntheticReference(left) ? left.expression : left; 128 let leftExpression = factory.restoreOuterExpressions(expression, capturedLeft, OuterExpressionKinds.PartiallyEmittedExpressions); 129 if (!isSimpleCopiableExpression(capturedLeft)) { 130 capturedLeft = factory.createTempVariable(hoistVariableDeclaration); 131 leftExpression = factory.createAssignment(capturedLeft, leftExpression); 132 } 133 let rightExpression = capturedLeft; 134 let thisArg: Expression | undefined; 135 for (let i = 0; i < chain.length; i++) { 136 const segment = chain[i]; 137 switch (segment.kind) { 138 case SyntaxKind.PropertyAccessExpression: 139 case SyntaxKind.ElementAccessExpression: 140 if (i === chain.length - 1 && captureThisArg) { 141 if (!isSimpleCopiableExpression(rightExpression)) { 142 thisArg = factory.createTempVariable(hoistVariableDeclaration); 143 rightExpression = factory.createAssignment(thisArg, rightExpression); 144 } 145 else { 146 thisArg = rightExpression; 147 } 148 } 149 rightExpression = segment.kind === SyntaxKind.PropertyAccessExpression 150 ? factory.createPropertyAccessExpression(rightExpression, visitNode(segment.name, visitor, isIdentifier)) 151 : factory.createElementAccessExpression(rightExpression, visitNode(segment.argumentExpression, visitor, isExpression)); 152 break; 153 case SyntaxKind.CallExpression: 154 if (i === 0 && leftThisArg) { 155 if (!isGeneratedIdentifier(leftThisArg)) { 156 leftThisArg = factory.cloneNode(leftThisArg); 157 addEmitFlags(leftThisArg, EmitFlags.NoComments); 158 } 159 rightExpression = factory.createFunctionCallCall( 160 rightExpression, 161 leftThisArg.kind === SyntaxKind.SuperKeyword ? factory.createThis() : leftThisArg, 162 visitNodes(segment.arguments, visitor, isExpression) 163 ); 164 } 165 else { 166 rightExpression = factory.createCallExpression( 167 rightExpression, 168 /*typeArguments*/ undefined, 169 visitNodes(segment.arguments, visitor, isExpression) 170 ); 171 } 172 break; 173 } 174 setOriginalNode(rightExpression, segment); 175 } 176 177 const target = isDelete 178 ? factory.createConditionalExpression(createNotNullCondition(leftExpression, capturedLeft, /*invert*/ true), /*questionToken*/ undefined, factory.createTrue(), /*colonToken*/ undefined, factory.createDeleteExpression(rightExpression)) 179 : factory.createConditionalExpression(createNotNullCondition(leftExpression, capturedLeft, /*invert*/ true), /*questionToken*/ undefined, factory.createVoidZero(), /*colonToken*/ undefined, rightExpression); 180 setTextRange(target, node); 181 return thisArg ? factory.createSyntheticReferenceExpression(target, thisArg) : target; 182 } 183 184 function createNotNullCondition(left: Expression, right: Expression, invert?: boolean) { 185 return factory.createBinaryExpression( 186 factory.createBinaryExpression( 187 left, 188 factory.createToken(invert ? SyntaxKind.EqualsEqualsEqualsToken : SyntaxKind.ExclamationEqualsEqualsToken), 189 factory.createNull() 190 ), 191 factory.createToken(invert ? SyntaxKind.BarBarToken : SyntaxKind.AmpersandAmpersandToken), 192 factory.createBinaryExpression( 193 right, 194 factory.createToken(invert ? SyntaxKind.EqualsEqualsEqualsToken : SyntaxKind.ExclamationEqualsEqualsToken), 195 factory.createVoidZero() 196 ) 197 ); 198 } 199 200 function transformNullishCoalescingExpression(node: BinaryExpression) { 201 let left = visitNode(node.left, visitor, isExpression); 202 let right = left; 203 if (!isSimpleCopiableExpression(left)) { 204 right = factory.createTempVariable(hoistVariableDeclaration); 205 left = factory.createAssignment(right, left); 206 } 207 return setTextRange(factory.createConditionalExpression( 208 createNotNullCondition(left, right), 209 /*questionToken*/ undefined, 210 right, 211 /*colonToken*/ undefined, 212 visitNode(node.right, visitor, isExpression), 213 ), node); 214 } 215 216 function visitDeleteExpression(node: DeleteExpression) { 217 return isOptionalChain(skipParentheses(node.expression)) 218 ? setOriginalNode(visitNonOptionalExpression(node.expression, /*captureThisArg*/ false, /*isDelete*/ true), node) 219 : factory.updateDeleteExpression(node, visitNode(node.expression, visitor, isExpression)); 220 } 221 } 222} 223