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