• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import {
2    __String, AccessorDeclaration, addEmitHelper, addEmitHelpers, advancedAsyncSuperHelper, ArrowFunction,
3    asyncSuperHelper, AwaitExpression, BindingElement, Block, CallExpression, CatchClause, chainBundle,
4    ClassDeclaration, concatenate, ConciseBody, ConstructorDeclaration, Debug, ElementAccessExpression, EmitFlags,
5    EmitHint, EmitResolver, Expression, forEach, ForInitializer, ForInStatement, ForOfStatement, ForStatement,
6    FunctionBody, FunctionDeclaration, FunctionExpression, FunctionFlags, FunctionLikeDeclaration,
7    GeneratedIdentifierFlags, GetAccessorDeclaration, getEmitScriptTarget, getEntityNameFromTypeNode, getFunctionFlags,
8    getInitializedVariables, getNodeId, getOriginalNode, insertStatementsAfterStandardPrologue, isBlock, isConciseBody,
9    isEffectiveStrictModeSourceFile, isEntityName, isExpression, isForInitializer, isFunctionLike,
10    isFunctionLikeDeclaration, isIdentifier, isModifierLike, isNodeWithPossibleHoistedDeclaration, isOmittedExpression,
11    isPropertyAccessExpression, isStatement, isSuperProperty, isToken, isVariableDeclarationList,
12    LeftHandSideExpression, map, MethodDeclaration, Node, NodeCheckFlags, NodeFactory, NodeFlags, ParameterDeclaration,
13    PropertyAccessExpression, PropertyAssignment, ScriptTarget, Set, SetAccessorDeclaration, setEmitFlags,
14    setOriginalNode, setSourceMapRange, setTextRange, some, SourceFile, Statement, SyntaxKind, TextRange,
15    TransformationContext, TransformFlags, TypeNode, TypeReferenceSerializationKind, unescapeLeadingUnderscores,
16    VariableDeclaration, VariableDeclarationList, VariableStatement, visitEachChild, visitFunctionBody,
17    visitIterationBody, visitNode, visitNodes, visitParameterList, VisitResult, Bundle,
18} from "../_namespaces/ts";
19
20type SuperContainer = ClassDeclaration | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | ConstructorDeclaration;
21
22const enum ES2017SubstitutionFlags {
23    /** Enables substitutions for async methods with `super` calls. */
24    AsyncMethodsWithSuper = 1 << 0
25}
26
27const enum ContextFlags {
28    NonTopLevel = 1 << 0,
29    HasLexicalThis = 1 << 1
30}
31
32/** @internal */
33export function transformES2017(context: TransformationContext): (x: SourceFile | Bundle) => SourceFile | Bundle {
34    const {
35        factory,
36        getEmitHelperFactory: emitHelpers,
37        resumeLexicalEnvironment,
38        endLexicalEnvironment,
39        hoistVariableDeclaration
40    } = context;
41
42    const resolver = context.getEmitResolver();
43    const compilerOptions = context.getCompilerOptions();
44    const languageVersion = getEmitScriptTarget(compilerOptions);
45
46    /**
47     * Keeps track of whether expression substitution has been enabled for specific edge cases.
48     * They are persisted between each SourceFile transformation and should not be reset.
49     */
50    let enabledSubstitutions: ES2017SubstitutionFlags;
51
52    /**
53     * This keeps track of containers where `super` is valid, for use with
54     * just-in-time substitution for `super` expressions inside of async methods.
55     */
56    let enclosingSuperContainerFlags: NodeCheckFlags = 0;
57
58    let enclosingFunctionParameterNames: Set<__String>;
59
60    /**
61     * Keeps track of property names accessed on super (`super.x`) within async functions.
62     */
63    let capturedSuperProperties: Set<__String>;
64    /** Whether the async function contains an element access on super (`super[x]`). */
65    let hasSuperElementAccess: boolean;
66    /** A set of node IDs for generated super accessors (variable statements). */
67    const substitutedSuperAccessors: boolean[] = [];
68
69    let contextFlags: ContextFlags = 0;
70
71    // Save the previous transformation hooks.
72    const previousOnEmitNode = context.onEmitNode;
73    const previousOnSubstituteNode = context.onSubstituteNode;
74
75    // Set new transformation hooks.
76    context.onEmitNode = onEmitNode;
77    context.onSubstituteNode = onSubstituteNode;
78
79    return chainBundle(context, transformSourceFile);
80
81    function transformSourceFile(node: SourceFile) {
82        if (node.isDeclarationFile) {
83            return node;
84        }
85
86        setContextFlag(ContextFlags.NonTopLevel, false);
87        setContextFlag(ContextFlags.HasLexicalThis, !isEffectiveStrictModeSourceFile(node, compilerOptions));
88        const visited = visitEachChild(node, visitor, context);
89        addEmitHelpers(visited, context.readEmitHelpers());
90        return visited;
91    }
92
93    function setContextFlag(flag: ContextFlags, val: boolean) {
94        contextFlags = val ? contextFlags | flag : contextFlags & ~flag;
95    }
96
97    function inContext(flags: ContextFlags) {
98        return (contextFlags & flags) !== 0;
99    }
100
101    function inTopLevelContext() {
102        return !inContext(ContextFlags.NonTopLevel);
103    }
104
105    function inHasLexicalThisContext() {
106        return inContext(ContextFlags.HasLexicalThis);
107    }
108
109    function doWithContext<T, U>(flags: ContextFlags, cb: (value: T) => U, value: T) {
110        const contextFlagsToSet = flags & ~contextFlags;
111        if (contextFlagsToSet) {
112            setContextFlag(contextFlagsToSet, /*val*/ true);
113            const result = cb(value);
114            setContextFlag(contextFlagsToSet, /*val*/ false);
115            return result;
116        }
117        return cb(value);
118    }
119
120    function visitDefault(node: Node): VisitResult<Node> {
121        return visitEachChild(node, visitor, context);
122    }
123
124    function visitor(node: Node): VisitResult<Node> {
125        if ((node.transformFlags & TransformFlags.ContainsES2017) === 0) {
126            return node;
127        }
128        switch (node.kind) {
129            case SyntaxKind.AsyncKeyword:
130                // ES2017 async modifier should be elided for targets < ES2017
131                return undefined;
132
133            case SyntaxKind.AwaitExpression:
134                return visitAwaitExpression(node as AwaitExpression);
135
136            case SyntaxKind.MethodDeclaration:
137                return doWithContext(ContextFlags.NonTopLevel | ContextFlags.HasLexicalThis, visitMethodDeclaration, node as MethodDeclaration);
138
139            case SyntaxKind.FunctionDeclaration:
140                return doWithContext(ContextFlags.NonTopLevel | ContextFlags.HasLexicalThis, visitFunctionDeclaration, node as FunctionDeclaration);
141
142            case SyntaxKind.FunctionExpression:
143                return doWithContext(ContextFlags.NonTopLevel | ContextFlags.HasLexicalThis, visitFunctionExpression, node as FunctionExpression);
144
145            case SyntaxKind.ArrowFunction:
146                return doWithContext(ContextFlags.NonTopLevel, visitArrowFunction, node as ArrowFunction);
147
148            case SyntaxKind.PropertyAccessExpression:
149                if (capturedSuperProperties && isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.SuperKeyword) {
150                    capturedSuperProperties.add(node.name.escapedText);
151                }
152                return visitEachChild(node, visitor, context);
153
154            case SyntaxKind.ElementAccessExpression:
155                if (capturedSuperProperties && (node as ElementAccessExpression).expression.kind === SyntaxKind.SuperKeyword) {
156                    hasSuperElementAccess = true;
157                }
158                return visitEachChild(node, visitor, context);
159
160            case SyntaxKind.GetAccessor:
161                return doWithContext(ContextFlags.NonTopLevel | ContextFlags.HasLexicalThis, visitGetAccessorDeclaration, node as GetAccessorDeclaration);
162            case SyntaxKind.SetAccessor:
163                return doWithContext(ContextFlags.NonTopLevel | ContextFlags.HasLexicalThis, visitSetAccessorDeclaration, node as SetAccessorDeclaration);
164            case SyntaxKind.Constructor:
165                return doWithContext(ContextFlags.NonTopLevel | ContextFlags.HasLexicalThis, visitConstructorDeclaration, node as ConstructorDeclaration);
166            case SyntaxKind.ClassDeclaration:
167            case SyntaxKind.ClassExpression:
168                return doWithContext(ContextFlags.NonTopLevel | ContextFlags.HasLexicalThis, visitDefault, node);
169
170            default:
171                return visitEachChild(node, visitor, context);
172        }
173    }
174
175    function asyncBodyVisitor(node: Node): VisitResult<Node> {
176        if (isNodeWithPossibleHoistedDeclaration(node)) {
177            switch (node.kind) {
178                case SyntaxKind.VariableStatement:
179                    return visitVariableStatementInAsyncBody(node);
180                case SyntaxKind.ForStatement:
181                    return visitForStatementInAsyncBody(node);
182                case SyntaxKind.ForInStatement:
183                    return visitForInStatementInAsyncBody(node);
184                case SyntaxKind.ForOfStatement:
185                    return visitForOfStatementInAsyncBody(node);
186                case SyntaxKind.CatchClause:
187                    return visitCatchClauseInAsyncBody(node);
188                case SyntaxKind.Block:
189                case SyntaxKind.SwitchStatement:
190                case SyntaxKind.CaseBlock:
191                case SyntaxKind.CaseClause:
192                case SyntaxKind.DefaultClause:
193                case SyntaxKind.TryStatement:
194                case SyntaxKind.DoStatement:
195                case SyntaxKind.WhileStatement:
196                case SyntaxKind.IfStatement:
197                case SyntaxKind.WithStatement:
198                case SyntaxKind.LabeledStatement:
199                    return visitEachChild(node, asyncBodyVisitor, context);
200                default:
201                    return Debug.assertNever(node, "Unhandled node.");
202            }
203        }
204        return visitor(node);
205    }
206
207    function visitCatchClauseInAsyncBody(node: CatchClause) {
208        const catchClauseNames = new Set<__String>();
209        recordDeclarationName(node.variableDeclaration!, catchClauseNames); // TODO: GH#18217
210
211        // names declared in a catch variable are block scoped
212        let catchClauseUnshadowedNames: Set<__String> | undefined;
213        catchClauseNames.forEach((_, escapedName) => {
214            if (enclosingFunctionParameterNames.has(escapedName)) {
215                if (!catchClauseUnshadowedNames) {
216                    catchClauseUnshadowedNames = new Set(enclosingFunctionParameterNames);
217                }
218                catchClauseUnshadowedNames.delete(escapedName);
219            }
220        });
221
222        if (catchClauseUnshadowedNames) {
223            const savedEnclosingFunctionParameterNames = enclosingFunctionParameterNames;
224            enclosingFunctionParameterNames = catchClauseUnshadowedNames;
225            const result = visitEachChild(node, asyncBodyVisitor, context);
226            enclosingFunctionParameterNames = savedEnclosingFunctionParameterNames;
227            return result;
228        }
229        else {
230            return visitEachChild(node, asyncBodyVisitor, context);
231        }
232    }
233
234    function visitVariableStatementInAsyncBody(node: VariableStatement) {
235        if (isVariableDeclarationListWithCollidingName(node.declarationList)) {
236            const expression = visitVariableDeclarationListWithCollidingNames(node.declarationList, /*hasReceiver*/ false);
237            return expression ? factory.createExpressionStatement(expression) : undefined;
238        }
239        return visitEachChild(node, visitor, context);
240    }
241
242    function visitForInStatementInAsyncBody(node: ForInStatement) {
243        return factory.updateForInStatement(
244            node,
245            isVariableDeclarationListWithCollidingName(node.initializer)
246                ? visitVariableDeclarationListWithCollidingNames(node.initializer, /*hasReceiver*/ true)!
247                : visitNode(node.initializer, visitor, isForInitializer),
248            visitNode(node.expression, visitor, isExpression),
249            visitIterationBody(node.statement, asyncBodyVisitor, context)
250        );
251    }
252
253    function visitForOfStatementInAsyncBody(node: ForOfStatement) {
254        return factory.updateForOfStatement(
255            node,
256            visitNode(node.awaitModifier, visitor, isToken),
257            isVariableDeclarationListWithCollidingName(node.initializer)
258                ? visitVariableDeclarationListWithCollidingNames(node.initializer, /*hasReceiver*/ true)!
259                : visitNode(node.initializer, visitor, isForInitializer),
260            visitNode(node.expression, visitor, isExpression),
261            visitIterationBody(node.statement, asyncBodyVisitor, context)
262        );
263    }
264
265    function visitForStatementInAsyncBody(node: ForStatement) {
266        const initializer = node.initializer!; // TODO: GH#18217
267        return factory.updateForStatement(
268            node,
269            isVariableDeclarationListWithCollidingName(initializer)
270                ? visitVariableDeclarationListWithCollidingNames(initializer, /*hasReceiver*/ false)
271                : visitNode(node.initializer, visitor, isForInitializer),
272            visitNode(node.condition, visitor, isExpression),
273            visitNode(node.incrementor, visitor, isExpression),
274            visitIterationBody(node.statement, asyncBodyVisitor, context)
275        );
276    }
277
278    /**
279     * Visits an AwaitExpression node.
280     *
281     * This function will be called any time a ES2017 await expression is encountered.
282     *
283     * @param node The node to visit.
284     */
285    function visitAwaitExpression(node: AwaitExpression): Expression {
286        // do not downlevel a top-level await as it is module syntax...
287        if (inTopLevelContext()) {
288            return visitEachChild(node, visitor, context);
289        }
290        return setOriginalNode(
291            setTextRange(
292                factory.createYieldExpression(
293                    /*asteriskToken*/ undefined,
294                    visitNode(node.expression, visitor, isExpression)
295                ),
296                node
297            ),
298            node
299        );
300    }
301
302    function visitConstructorDeclaration(node: ConstructorDeclaration) {
303        return factory.updateConstructorDeclaration(
304            node,
305            visitNodes(node.modifiers, visitor, isModifierLike),
306            visitParameterList(node.parameters, visitor, context),
307            transformMethodBody(node)
308        );
309    }
310
311    /**
312     * Visits a MethodDeclaration node.
313     *
314     * This function will be called when one of the following conditions are met:
315     * - The node is marked as async
316     *
317     * @param node The node to visit.
318     */
319    function visitMethodDeclaration(node: MethodDeclaration) {
320        return factory.updateMethodDeclaration(
321            node,
322            visitNodes(node.modifiers, visitor, isModifierLike),
323            node.asteriskToken,
324            node.name,
325            /*questionToken*/ undefined,
326            /*typeParameters*/ undefined,
327            visitParameterList(node.parameters, visitor, context),
328            /*type*/ undefined,
329            getFunctionFlags(node) & FunctionFlags.Async
330                ? transformAsyncFunctionBody(node)
331                : transformMethodBody(node)
332        );
333    }
334
335    function visitGetAccessorDeclaration(node: GetAccessorDeclaration) {
336        return factory.updateGetAccessorDeclaration(
337            node,
338            visitNodes(node.modifiers, visitor, isModifierLike),
339            node.name,
340            visitParameterList(node.parameters, visitor, context),
341            /*type*/ undefined,
342            transformMethodBody(node)
343        );
344    }
345
346    function visitSetAccessorDeclaration(node: SetAccessorDeclaration) {
347        return factory.updateSetAccessorDeclaration(
348            node,
349            visitNodes(node.modifiers, visitor, isModifierLike),
350            node.name,
351            visitParameterList(node.parameters, visitor, context),
352            transformMethodBody(node)
353        );
354    }
355
356    /**
357     * Visits a FunctionDeclaration node.
358     *
359     * This function will be called when one of the following conditions are met:
360     * - The node is marked async
361     *
362     * @param node The node to visit.
363     */
364    function visitFunctionDeclaration(node: FunctionDeclaration): VisitResult<Statement> {
365        return factory.updateFunctionDeclaration(
366            node,
367            visitNodes(node.modifiers, visitor, isModifierLike),
368            node.asteriskToken,
369            node.name,
370            /*typeParameters*/ undefined,
371            visitParameterList(node.parameters, visitor, context),
372            /*type*/ undefined,
373            getFunctionFlags(node) & FunctionFlags.Async
374                ? transformAsyncFunctionBody(node)
375                : visitFunctionBody(node.body, visitor, context)
376        );
377    }
378
379    /**
380     * Visits a FunctionExpression node.
381     *
382     * This function will be called when one of the following conditions are met:
383     * - The node is marked async
384     *
385     * @param node The node to visit.
386     */
387    function visitFunctionExpression(node: FunctionExpression): Expression {
388        return factory.updateFunctionExpression(
389            node,
390            visitNodes(node.modifiers, visitor, isModifierLike),
391            node.asteriskToken,
392            node.name,
393            /*typeParameters*/ undefined,
394            visitParameterList(node.parameters, visitor, context),
395            /*type*/ undefined,
396            getFunctionFlags(node) & FunctionFlags.Async
397                ? transformAsyncFunctionBody(node)
398                : visitFunctionBody(node.body, visitor, context)
399        );
400    }
401
402    /**
403     * Visits an ArrowFunction.
404     *
405     * This function will be called when one of the following conditions are met:
406     * - The node is marked async
407     *
408     * @param node The node to visit.
409     */
410    function visitArrowFunction(node: ArrowFunction) {
411        return factory.updateArrowFunction(
412            node,
413            visitNodes(node.modifiers, visitor, isModifierLike),
414            /*typeParameters*/ undefined,
415            visitParameterList(node.parameters, visitor, context),
416            /*type*/ undefined,
417            node.equalsGreaterThanToken,
418            getFunctionFlags(node) & FunctionFlags.Async
419                ? transformAsyncFunctionBody(node)
420                : visitFunctionBody(node.body, visitor, context),
421        );
422    }
423
424    function recordDeclarationName({ name }: ParameterDeclaration | VariableDeclaration | BindingElement, names: Set<__String>) {
425        if (isIdentifier(name)) {
426            names.add(name.escapedText);
427        }
428        else {
429            for (const element of name.elements) {
430                if (!isOmittedExpression(element)) {
431                    recordDeclarationName(element, names);
432                }
433            }
434        }
435    }
436
437    function isVariableDeclarationListWithCollidingName(node: ForInitializer): node is VariableDeclarationList {
438        return !!node
439            && isVariableDeclarationList(node)
440            && !(node.flags & NodeFlags.BlockScoped)
441            && node.declarations.some(collidesWithParameterName);
442    }
443
444    function visitVariableDeclarationListWithCollidingNames(node: VariableDeclarationList, hasReceiver: boolean) {
445        hoistVariableDeclarationList(node);
446
447        const variables = getInitializedVariables(node);
448        if (variables.length === 0) {
449            if (hasReceiver) {
450                return visitNode(factory.converters.convertToAssignmentElementTarget(node.declarations[0].name), visitor, isExpression);
451            }
452            return undefined;
453        }
454
455        return factory.inlineExpressions(map(variables, transformInitializedVariable));
456    }
457
458    function hoistVariableDeclarationList(node: VariableDeclarationList) {
459        forEach(node.declarations, hoistVariable);
460    }
461
462    function hoistVariable({ name }: VariableDeclaration | BindingElement) {
463        if (isIdentifier(name)) {
464            hoistVariableDeclaration(name);
465        }
466        else {
467            for (const element of name.elements) {
468                if (!isOmittedExpression(element)) {
469                    hoistVariable(element);
470                }
471            }
472        }
473    }
474
475    function transformInitializedVariable(node: VariableDeclaration) {
476        const converted = setSourceMapRange(
477            factory.createAssignment(
478                factory.converters.convertToAssignmentElementTarget(node.name),
479                node.initializer!
480            ),
481            node
482        );
483        return visitNode(converted, visitor, isExpression);
484    }
485
486    function collidesWithParameterName({ name }: VariableDeclaration | BindingElement): boolean {
487        if (isIdentifier(name)) {
488            return enclosingFunctionParameterNames.has(name.escapedText);
489        }
490        else {
491            for (const element of name.elements) {
492                if (!isOmittedExpression(element) && collidesWithParameterName(element)) {
493                    return true;
494                }
495            }
496        }
497        return false;
498    }
499
500    function transformMethodBody(node: MethodDeclaration | AccessorDeclaration | ConstructorDeclaration): FunctionBody | undefined {
501        Debug.assertIsDefined(node.body);
502
503        const savedCapturedSuperProperties = capturedSuperProperties;
504        const savedHasSuperElementAccess = hasSuperElementAccess;
505        capturedSuperProperties = new Set();
506        hasSuperElementAccess = false;
507
508        let updated = visitFunctionBody(node.body, visitor, context);
509
510        // Minor optimization, emit `_super` helper to capture `super` access in an arrow.
511        // This step isn't needed if we eventually transform this to ES5.
512        const originalMethod = getOriginalNode(node, isFunctionLikeDeclaration);
513        const emitSuperHelpers = languageVersion >= ScriptTarget.ES2015 &&
514            resolver.getNodeCheckFlags(node) & (NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync | NodeCheckFlags.MethodWithSuperPropertyAccessInAsync) &&
515            (getFunctionFlags(originalMethod) & FunctionFlags.AsyncGenerator) !== FunctionFlags.AsyncGenerator;
516
517        if (emitSuperHelpers) {
518            enableSubstitutionForAsyncMethodsWithSuper();
519            if (capturedSuperProperties.size) {
520                const variableStatement = createSuperAccessVariableStatement(factory, resolver, node, capturedSuperProperties);
521                substitutedSuperAccessors[getNodeId(variableStatement)] = true;
522
523                const statements = updated.statements.slice();
524                insertStatementsAfterStandardPrologue(statements, [variableStatement]);
525                updated = factory.updateBlock(updated, statements);
526            }
527
528            if (hasSuperElementAccess) {
529                // Emit helpers for super element access expressions (`super[x]`).
530                if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync) {
531                    addEmitHelper(updated, advancedAsyncSuperHelper);
532                }
533                else if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.MethodWithSuperPropertyAccessInAsync) {
534                    addEmitHelper(updated, asyncSuperHelper);
535                }
536            }
537        }
538
539        capturedSuperProperties = savedCapturedSuperProperties;
540        hasSuperElementAccess = savedHasSuperElementAccess;
541        return updated;
542    }
543
544    function transformAsyncFunctionBody(node: MethodDeclaration | AccessorDeclaration | FunctionDeclaration | FunctionExpression): FunctionBody;
545    function transformAsyncFunctionBody(node: ArrowFunction): ConciseBody;
546    function transformAsyncFunctionBody(node: FunctionLikeDeclaration): ConciseBody {
547        resumeLexicalEnvironment();
548
549        const original = getOriginalNode(node, isFunctionLike);
550        const nodeType = original.type;
551        const promiseConstructor = languageVersion < ScriptTarget.ES2015 ? getPromiseConstructor(nodeType) : undefined;
552        const isArrowFunction = node.kind === SyntaxKind.ArrowFunction;
553        const hasLexicalArguments = (resolver.getNodeCheckFlags(node) & NodeCheckFlags.CaptureArguments) !== 0;
554
555        // An async function is emit as an outer function that calls an inner
556        // generator function. To preserve lexical bindings, we pass the current
557        // `this` and `arguments` objects to `__awaiter`. The generator function
558        // passed to `__awaiter` is executed inside of the callback to the
559        // promise constructor.
560
561        const savedEnclosingFunctionParameterNames = enclosingFunctionParameterNames;
562        enclosingFunctionParameterNames = new Set();
563        for (const parameter of node.parameters) {
564            recordDeclarationName(parameter, enclosingFunctionParameterNames);
565        }
566
567        const savedCapturedSuperProperties = capturedSuperProperties;
568        const savedHasSuperElementAccess = hasSuperElementAccess;
569        if (!isArrowFunction) {
570            capturedSuperProperties = new Set();
571            hasSuperElementAccess = false;
572        }
573
574        let result: ConciseBody;
575        if (!isArrowFunction) {
576            const statements: Statement[] = [];
577            const statementOffset = factory.copyPrologue((node.body as Block).statements, statements, /*ensureUseStrict*/ false, visitor);
578            statements.push(
579                factory.createReturnStatement(
580                    emitHelpers().createAwaiterHelper(
581                        inHasLexicalThisContext(),
582                        hasLexicalArguments,
583                        promiseConstructor,
584                        transformAsyncFunctionBodyWorker(node.body as Block, statementOffset)
585                    )
586                )
587            );
588
589            insertStatementsAfterStandardPrologue(statements, endLexicalEnvironment());
590
591            // Minor optimization, emit `_super` helper to capture `super` access in an arrow.
592            // This step isn't needed if we eventually transform this to ES5.
593            const emitSuperHelpers = languageVersion >= ScriptTarget.ES2015 && resolver.getNodeCheckFlags(node) & (NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync | NodeCheckFlags.MethodWithSuperPropertyAccessInAsync);
594
595            if (emitSuperHelpers) {
596                enableSubstitutionForAsyncMethodsWithSuper();
597                if (capturedSuperProperties.size) {
598                    const variableStatement = createSuperAccessVariableStatement(factory, resolver, node, capturedSuperProperties);
599                    substitutedSuperAccessors[getNodeId(variableStatement)] = true;
600                    insertStatementsAfterStandardPrologue(statements, [variableStatement]);
601                }
602            }
603
604            const block = factory.createBlock(statements, /*multiLine*/ true);
605            setTextRange(block, node.body);
606
607            if (emitSuperHelpers && hasSuperElementAccess) {
608                // Emit helpers for super element access expressions (`super[x]`).
609                if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync) {
610                    addEmitHelper(block, advancedAsyncSuperHelper);
611                }
612                else if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.MethodWithSuperPropertyAccessInAsync) {
613                    addEmitHelper(block, asyncSuperHelper);
614                }
615            }
616
617            result = block;
618        }
619        else {
620            const expression = emitHelpers().createAwaiterHelper(
621                inHasLexicalThisContext(),
622                hasLexicalArguments,
623                promiseConstructor,
624                transformAsyncFunctionBodyWorker(node.body)
625            );
626
627            const declarations = endLexicalEnvironment();
628            if (some(declarations)) {
629                const block = factory.converters.convertToFunctionBlock(expression);
630                result = factory.updateBlock(block, setTextRange(factory.createNodeArray(concatenate(declarations, block.statements)), block.statements));
631            }
632            else {
633                result = expression;
634            }
635        }
636
637        enclosingFunctionParameterNames = savedEnclosingFunctionParameterNames;
638        if (!isArrowFunction) {
639            capturedSuperProperties = savedCapturedSuperProperties;
640            hasSuperElementAccess = savedHasSuperElementAccess;
641        }
642        return result;
643    }
644
645    function transformAsyncFunctionBodyWorker(body: ConciseBody, start?: number) {
646        if (isBlock(body)) {
647            return factory.updateBlock(body, visitNodes(body.statements, asyncBodyVisitor, isStatement, start));
648        }
649        else {
650            return factory.converters.convertToFunctionBlock(visitNode(body, asyncBodyVisitor, isConciseBody));
651        }
652    }
653
654    function getPromiseConstructor(type: TypeNode | undefined) {
655        const typeName = type && getEntityNameFromTypeNode(type);
656        if (typeName && isEntityName(typeName)) {
657            const serializationKind = resolver.getTypeReferenceSerializationKind(typeName);
658            if (serializationKind === TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue
659                || serializationKind === TypeReferenceSerializationKind.Unknown) {
660                return typeName;
661            }
662        }
663
664        return undefined;
665    }
666
667    function enableSubstitutionForAsyncMethodsWithSuper() {
668        if ((enabledSubstitutions & ES2017SubstitutionFlags.AsyncMethodsWithSuper) === 0) {
669            enabledSubstitutions |= ES2017SubstitutionFlags.AsyncMethodsWithSuper;
670
671            // We need to enable substitutions for call, property access, and element access
672            // if we need to rewrite super calls.
673            context.enableSubstitution(SyntaxKind.CallExpression);
674            context.enableSubstitution(SyntaxKind.PropertyAccessExpression);
675            context.enableSubstitution(SyntaxKind.ElementAccessExpression);
676
677            // We need to be notified when entering and exiting declarations that bind super.
678            context.enableEmitNotification(SyntaxKind.ClassDeclaration);
679            context.enableEmitNotification(SyntaxKind.MethodDeclaration);
680            context.enableEmitNotification(SyntaxKind.GetAccessor);
681            context.enableEmitNotification(SyntaxKind.SetAccessor);
682            context.enableEmitNotification(SyntaxKind.Constructor);
683            // We need to be notified when entering the generated accessor arrow functions.
684            context.enableEmitNotification(SyntaxKind.VariableStatement);
685        }
686    }
687
688    /**
689     * Hook for node emit.
690     *
691     * @param hint A hint as to the intended usage of the node.
692     * @param node The node to emit.
693     * @param emit A callback used to emit the node in the printer.
694     */
695    function onEmitNode(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void {
696        // If we need to support substitutions for `super` in an async method,
697        // we should track it here.
698        if (enabledSubstitutions & ES2017SubstitutionFlags.AsyncMethodsWithSuper && isSuperContainer(node)) {
699            const superContainerFlags = resolver.getNodeCheckFlags(node) & (NodeCheckFlags.MethodWithSuperPropertyAccessInAsync | NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync);
700            if (superContainerFlags !== enclosingSuperContainerFlags) {
701                const savedEnclosingSuperContainerFlags = enclosingSuperContainerFlags;
702                enclosingSuperContainerFlags = superContainerFlags;
703                previousOnEmitNode(hint, node, emitCallback);
704                enclosingSuperContainerFlags = savedEnclosingSuperContainerFlags;
705                return;
706            }
707        }
708        // Disable substitution in the generated super accessor itself.
709        else if (enabledSubstitutions && substitutedSuperAccessors[getNodeId(node)]) {
710            const savedEnclosingSuperContainerFlags = enclosingSuperContainerFlags;
711            enclosingSuperContainerFlags = 0;
712            previousOnEmitNode(hint, node, emitCallback);
713            enclosingSuperContainerFlags = savedEnclosingSuperContainerFlags;
714            return;
715        }
716        previousOnEmitNode(hint, node, emitCallback);
717    }
718
719    /**
720     * Hooks node substitutions.
721     *
722     * @param hint A hint as to the intended usage of the node.
723     * @param node The node to substitute.
724     */
725    function onSubstituteNode(hint: EmitHint, node: Node) {
726        node = previousOnSubstituteNode(hint, node);
727        if (hint === EmitHint.Expression && enclosingSuperContainerFlags) {
728            return substituteExpression(node as Expression);
729        }
730
731        return node;
732    }
733
734    function substituteExpression(node: Expression) {
735        switch (node.kind) {
736            case SyntaxKind.PropertyAccessExpression:
737                return substitutePropertyAccessExpression(node as PropertyAccessExpression);
738            case SyntaxKind.ElementAccessExpression:
739                return substituteElementAccessExpression(node as ElementAccessExpression);
740            case SyntaxKind.CallExpression:
741                return substituteCallExpression(node as CallExpression);
742        }
743        return node;
744    }
745
746    function substitutePropertyAccessExpression(node: PropertyAccessExpression) {
747        if (node.expression.kind === SyntaxKind.SuperKeyword) {
748            return setTextRange(
749                factory.createPropertyAccessExpression(
750                    factory.createUniqueName("_super", GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel),
751                    node.name),
752                node
753            );
754        }
755        return node;
756    }
757
758    function substituteElementAccessExpression(node: ElementAccessExpression) {
759        if (node.expression.kind === SyntaxKind.SuperKeyword) {
760            return createSuperElementAccessInAsyncMethod(
761                node.argumentExpression,
762                node
763            );
764        }
765        return node;
766    }
767
768    function substituteCallExpression(node: CallExpression): Expression {
769        const expression = node.expression;
770        if (isSuperProperty(expression)) {
771            const argumentExpression = isPropertyAccessExpression(expression)
772                ? substitutePropertyAccessExpression(expression)
773                : substituteElementAccessExpression(expression);
774            return factory.createCallExpression(
775                factory.createPropertyAccessExpression(argumentExpression, "call"),
776                /*typeArguments*/ undefined,
777                [
778                    factory.createThis(),
779                    ...node.arguments
780                ]
781            );
782        }
783        return node;
784    }
785
786    function isSuperContainer(node: Node): node is SuperContainer {
787        const kind = node.kind;
788        return kind === SyntaxKind.ClassDeclaration
789            || kind === SyntaxKind.Constructor
790            || kind === SyntaxKind.MethodDeclaration
791            || kind === SyntaxKind.GetAccessor
792            || kind === SyntaxKind.SetAccessor;
793    }
794
795    function createSuperElementAccessInAsyncMethod(argumentExpression: Expression, location: TextRange): LeftHandSideExpression {
796        if (enclosingSuperContainerFlags & NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync) {
797            return setTextRange(
798                factory.createPropertyAccessExpression(
799                    factory.createCallExpression(
800                        factory.createUniqueName("_superIndex", GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel),
801                        /*typeArguments*/ undefined,
802                        [argumentExpression]
803                    ),
804                    "value"
805                ),
806                location
807            );
808        }
809        else {
810            return setTextRange(
811                factory.createCallExpression(
812                    factory.createUniqueName("_superIndex", GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel),
813                    /*typeArguments*/ undefined,
814                    [argumentExpression]
815                ),
816                location
817            );
818        }
819    }
820}
821
822/**
823 * Creates a variable named `_super` with accessor properties for the given property names.
824 *
825 * @internal
826 */
827export function createSuperAccessVariableStatement(factory: NodeFactory, resolver: EmitResolver, node: FunctionLikeDeclaration, names: Set<__String>) {
828    // Create a variable declaration with a getter/setter (if binding) definition for each name:
829    //   const _super = Object.create(null, { x: { get: () => super.x, set: (v) => super.x = v }, ... });
830    const hasBinding = (resolver.getNodeCheckFlags(node) & NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync) !== 0;
831    const accessors: PropertyAssignment[] = [];
832    names.forEach((_, key) => {
833        const name = unescapeLeadingUnderscores(key);
834        const getterAndSetter: PropertyAssignment[] = [];
835        getterAndSetter.push(factory.createPropertyAssignment(
836            "get",
837            factory.createArrowFunction(
838                /* modifiers */ undefined,
839                /* typeParameters */ undefined,
840                /* parameters */ [],
841                /* type */ undefined,
842                /* equalsGreaterThanToken */ undefined,
843                setEmitFlags(
844                    factory.createPropertyAccessExpression(
845                        setEmitFlags(
846                            factory.createSuper(),
847                            EmitFlags.NoSubstitution
848                        ),
849                        name
850                    ),
851                    EmitFlags.NoSubstitution
852                )
853            )
854        ));
855        if (hasBinding) {
856            getterAndSetter.push(
857                factory.createPropertyAssignment(
858                    "set",
859                    factory.createArrowFunction(
860                        /* modifiers */ undefined,
861                        /* typeParameters */ undefined,
862                        /* parameters */ [
863                            factory.createParameterDeclaration(
864                                /* modifiers */ undefined,
865                                /* dotDotDotToken */ undefined,
866                                "v",
867                                /* questionToken */ undefined,
868                                /* type */ undefined,
869                                /* initializer */ undefined
870                            )
871                        ],
872                        /* type */ undefined,
873                        /* equalsGreaterThanToken */ undefined,
874                        factory.createAssignment(
875                            setEmitFlags(
876                                factory.createPropertyAccessExpression(
877                                    setEmitFlags(
878                                        factory.createSuper(),
879                                        EmitFlags.NoSubstitution
880                                    ),
881                                    name
882                                ),
883                                EmitFlags.NoSubstitution
884                            ),
885                            factory.createIdentifier("v")
886                        )
887                    )
888                )
889            );
890        }
891        accessors.push(
892            factory.createPropertyAssignment(
893                name,
894                factory.createObjectLiteralExpression(getterAndSetter),
895            )
896        );
897    });
898    return factory.createVariableStatement(
899        /* modifiers */ undefined,
900        factory.createVariableDeclarationList(
901            [
902                factory.createVariableDeclaration(
903                    factory.createUniqueName("_super", GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel),
904                    /*exclamationToken*/ undefined,
905                    /* type */ undefined,
906                    factory.createCallExpression(
907                        factory.createPropertyAccessExpression(
908                            factory.createIdentifier("Object"),
909                            "create"
910                        ),
911                        /* typeArguments */ undefined,
912                        [
913                            factory.createNull(),
914                            factory.createObjectLiteralExpression(accessors, /* multiline */ true)
915                        ]
916                    )
917                )
918            ],
919            NodeFlags.Const));
920}
921