• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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