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