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