• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*@internal*/
2namespace ts {
3    export function transformSystemModule(context: TransformationContext) {
4        interface DependencyGroup {
5            name: StringLiteral;
6            externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[];
7        }
8
9        const {
10            factory,
11            startLexicalEnvironment,
12            endLexicalEnvironment,
13            hoistVariableDeclaration
14        } = context;
15
16        const compilerOptions = context.getCompilerOptions();
17        const resolver = context.getEmitResolver();
18        const host = context.getEmitHost();
19        const previousOnSubstituteNode = context.onSubstituteNode;
20        const previousOnEmitNode = context.onEmitNode;
21        context.onSubstituteNode = onSubstituteNode;
22        context.onEmitNode = onEmitNode;
23        context.enableSubstitution(SyntaxKind.Identifier); // Substitutes expression identifiers for imported symbols.
24        context.enableSubstitution(SyntaxKind.ShorthandPropertyAssignment); // Substitutes expression identifiers for imported symbols
25        context.enableSubstitution(SyntaxKind.BinaryExpression); // Substitutes assignments to exported symbols.
26        context.enableSubstitution(SyntaxKind.MetaProperty); // Substitutes 'import.meta'
27        context.enableEmitNotification(SyntaxKind.SourceFile); // Restore state when substituting nodes in a file.
28
29        const moduleInfoMap: ExternalModuleInfo[] = []; // The ExternalModuleInfo for each file.
30        const deferredExports: (Statement[] | undefined)[] = []; // Exports to defer until an EndOfDeclarationMarker is found.
31        const exportFunctionsMap: Identifier[] = []; // The export function associated with a source file.
32        const noSubstitutionMap: boolean[][] = []; // Set of nodes for which substitution rules should be ignored for each file.
33        const contextObjectMap: Identifier[] = []; // The context object associated with a source file.
34
35        let currentSourceFile: SourceFile; // The current file.
36        let moduleInfo: ExternalModuleInfo; // ExternalModuleInfo for the current file.
37        let exportFunction: Identifier; // The export function for the current file.
38        let contextObject: Identifier; // The context object for the current file.
39        let hoistedStatements: Statement[] | undefined;
40        let enclosingBlockScopedContainer: Node;
41        let noSubstitution: boolean[] | undefined; // Set of nodes for which substitution rules should be ignored.
42
43        return chainBundle(context, transformSourceFile);
44
45        /**
46         * Transforms the module aspects of a SourceFile.
47         *
48         * @param node The SourceFile node.
49         */
50        function transformSourceFile(node: SourceFile) {
51            if (node.isDeclarationFile || !(isEffectiveExternalModule(node, compilerOptions) || node.transformFlags & TransformFlags.ContainsDynamicImport)) {
52                return node;
53            }
54
55            const id = getOriginalNodeId(node);
56            currentSourceFile = node;
57            enclosingBlockScopedContainer = node;
58
59            // System modules have the following shape:
60            //
61            //     System.register(['dep-1', ... 'dep-n'], function(exports) {/* module body function */})
62            //
63            // The parameter 'exports' here is a callback '<T>(name: string, value: T) => T' that
64            // is used to publish exported values. 'exports' returns its 'value' argument so in
65            // most cases expressions that mutate exported values can be rewritten as:
66            //
67            //     expr -> exports('name', expr)
68            //
69            // The only exception in this rule is postfix unary operators,
70            // see comment to 'substitutePostfixUnaryExpression' for more details
71
72            // Collect information about the external module and dependency groups.
73            moduleInfo = moduleInfoMap[id] = collectExternalModuleInfo(context, node, resolver, compilerOptions);
74
75            // Make sure that the name of the 'exports' function does not conflict with
76            // existing identifiers.
77            exportFunction = factory.createUniqueName("exports");
78            exportFunctionsMap[id] = exportFunction;
79            contextObject = contextObjectMap[id] = factory.createUniqueName("context");
80
81            // Add the body of the module.
82            const dependencyGroups = collectDependencyGroups(moduleInfo.externalImports);
83            const moduleBodyBlock = createSystemModuleBody(node, dependencyGroups);
84            const moduleBodyFunction = factory.createFunctionExpression(
85                /*modifiers*/ undefined,
86                /*asteriskToken*/ undefined,
87                /*name*/ undefined,
88                /*typeParameters*/ undefined,
89                [
90                    factory.createParameterDeclaration(/*modifiers*/ undefined, /*dotDotDotToken*/ undefined, exportFunction),
91                    factory.createParameterDeclaration(/*modifiers*/ undefined, /*dotDotDotToken*/ undefined, contextObject)
92                ],
93                /*type*/ undefined,
94                moduleBodyBlock
95            );
96
97            // Write the call to `System.register`
98            // Clear the emit-helpers flag for later passes since we'll have already used it in the module body
99            // So the helper will be emit at the correct position instead of at the top of the source-file
100            const moduleName = tryGetModuleNameFromFile(factory, node, host, compilerOptions);
101            const dependencies = factory.createArrayLiteralExpression(map(dependencyGroups, dependencyGroup => dependencyGroup.name));
102            const updated = setEmitFlags(
103                factory.updateSourceFile(
104                    node,
105                    setTextRange(
106                        factory.createNodeArray([
107                            factory.createExpressionStatement(
108                                factory.createCallExpression(
109                                    factory.createPropertyAccessExpression(factory.createIdentifier("System"), "register"),
110                                    /*typeArguments*/ undefined,
111                                    moduleName
112                                        ? [moduleName, dependencies, moduleBodyFunction]
113                                        : [dependencies, moduleBodyFunction]
114                                )
115                            )
116                        ]),
117                        node.statements
118                    )
119                ), EmitFlags.NoTrailingComments);
120
121            if (!outFile(compilerOptions)) {
122                moveEmitHelpers(updated, moduleBodyBlock, helper => !helper.scoped);
123            }
124
125            if (noSubstitution) {
126                noSubstitutionMap[id] = noSubstitution;
127                noSubstitution = undefined;
128            }
129
130            currentSourceFile = undefined!;
131            moduleInfo = undefined!;
132            exportFunction = undefined!;
133            contextObject = undefined!;
134            hoistedStatements = undefined;
135            enclosingBlockScopedContainer = undefined!;
136            return updated;
137        }
138
139        /**
140         * Collects the dependency groups for this files imports.
141         *
142         * @param externalImports The imports for the file.
143         */
144        function collectDependencyGroups(externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[]) {
145            const groupIndices = new Map<string, number>();
146            const dependencyGroups: DependencyGroup[] = [];
147            for (const externalImport of externalImports) {
148                const externalModuleName = getExternalModuleNameLiteral(factory, externalImport, currentSourceFile, host, resolver, compilerOptions);
149                if (externalModuleName) {
150                    const text = externalModuleName.text;
151                    const groupIndex = groupIndices.get(text);
152                    if (groupIndex !== undefined) {
153                        // deduplicate/group entries in dependency list by the dependency name
154                        dependencyGroups[groupIndex].externalImports.push(externalImport);
155                    }
156                    else {
157                        groupIndices.set(text, dependencyGroups.length);
158                        dependencyGroups.push({
159                            name: externalModuleName,
160                            externalImports: [externalImport]
161                        });
162                    }
163                }
164            }
165
166            return dependencyGroups;
167        }
168
169        /**
170         * Adds the statements for the module body function for the source file.
171         *
172         * @param node The source file for the module.
173         * @param dependencyGroups The grouped dependencies of the module.
174         */
175        function createSystemModuleBody(node: SourceFile, dependencyGroups: DependencyGroup[]) {
176            // Shape of the body in system modules:
177            //
178            //  function (exports) {
179            //      <list of local aliases for imports>
180            //      <hoisted variable declarations>
181            //      <hoisted function declarations>
182            //      return {
183            //          setters: [
184            //              <list of setter function for imports>
185            //          ],
186            //          execute: function() {
187            //              <module statements>
188            //          }
189            //      }
190            //      <temp declarations>
191            //  }
192            //
193            // i.e:
194            //
195            //   import {x} from 'file1'
196            //   var y = 1;
197            //   export function foo() { return y + x(); }
198            //   console.log(y);
199            //
200            // Will be transformed to:
201            //
202            //  function(exports) {
203            //      function foo() { return y + file_1.x(); }
204            //      exports("foo", foo);
205            //      var file_1, y;
206            //      return {
207            //          setters: [
208            //              function(v) { file_1 = v }
209            //          ],
210            //          execute(): function() {
211            //              y = 1;
212            //              console.log(y);
213            //          }
214            //      };
215            //  }
216
217            const statements: Statement[] = [];
218
219            // We start a new lexical environment in this function body, but *not* in the
220            // body of the execute function. This allows us to emit temporary declarations
221            // only in the outer module body and not in the inner one.
222            startLexicalEnvironment();
223
224            // Add any prologue directives.
225            const ensureUseStrict = getStrictOptionValue(compilerOptions, "alwaysStrict") || (!compilerOptions.noImplicitUseStrict && isExternalModule(currentSourceFile));
226            const statementOffset = factory.copyPrologue(node.statements, statements, ensureUseStrict, topLevelVisitor);
227
228            // var __moduleName = context_1 && context_1.id;
229            statements.push(
230                factory.createVariableStatement(
231                    /*modifiers*/ undefined,
232                    factory.createVariableDeclarationList([
233                        factory.createVariableDeclaration(
234                            "__moduleName",
235                            /*exclamationToken*/ undefined,
236                            /*type*/ undefined,
237                            factory.createLogicalAnd(
238                                contextObject,
239                                factory.createPropertyAccessExpression(contextObject, "id")
240                            )
241                        )
242                    ])
243                )
244            );
245
246            // Visit the synthetic external helpers import declaration if present
247            visitNode(moduleInfo.externalHelpersImportDeclaration, topLevelVisitor, isStatement);
248
249            // Visit the statements of the source file, emitting any transformations into
250            // the `executeStatements` array. We do this *before* we fill the `setters` array
251            // as we both emit transformations as well as aggregate some data used when creating
252            // setters. This allows us to reduce the number of times we need to loop through the
253            // statements of the source file.
254            const executeStatements = visitNodes(node.statements, topLevelVisitor, isStatement, statementOffset);
255
256            // Emit early exports for function declarations.
257            addRange(statements, hoistedStatements);
258
259            // We emit hoisted variables early to align roughly with our previous emit output.
260            // Two key differences in this approach are:
261            // - Temporary variables will appear at the top rather than at the bottom of the file
262            insertStatementsAfterStandardPrologue(statements, endLexicalEnvironment());
263
264            const exportStarFunction = addExportStarIfNeeded(statements)!; // TODO: GH#18217
265            const modifiers = node.transformFlags & TransformFlags.ContainsAwait ?
266                factory.createModifiersFromModifierFlags(ModifierFlags.Async) :
267                undefined;
268            const moduleObject = factory.createObjectLiteralExpression([
269                factory.createPropertyAssignment("setters",
270                    createSettersArray(exportStarFunction, dependencyGroups)
271                ),
272                factory.createPropertyAssignment("execute",
273                    factory.createFunctionExpression(
274                        modifiers,
275                        /*asteriskToken*/ undefined,
276                        /*name*/ undefined,
277                        /*typeParameters*/ undefined,
278                        /*parameters*/ [],
279                        /*type*/ undefined,
280                        factory.createBlock(executeStatements, /*multiLine*/ true)
281                    )
282                )
283            ], /*multiLine*/ true);
284
285            statements.push(factory.createReturnStatement(moduleObject));
286            return factory.createBlock(statements, /*multiLine*/ true);
287        }
288
289        /**
290         * Adds an exportStar function to a statement list if it is needed for the file.
291         *
292         * @param statements A statement list.
293         */
294        function addExportStarIfNeeded(statements: Statement[]) {
295            if (!moduleInfo.hasExportStarsToExportValues) {
296                return;
297            }
298
299            // when resolving exports local exported entries/indirect exported entries in the module
300            // should always win over entries with similar names that were added via star exports
301            // to support this we store names of local/indirect exported entries in a set.
302            // this set is used to filter names brought by star expors.
303
304            // local names set should only be added if we have anything exported
305            if (!moduleInfo.exportedNames && moduleInfo.exportSpecifiers.size === 0) {
306                // no exported declarations (export var ...) or export specifiers (export {x})
307                // check if we have any non star export declarations.
308                let hasExportDeclarationWithExportClause = false;
309                for (const externalImport of moduleInfo.externalImports) {
310                    if (externalImport.kind === SyntaxKind.ExportDeclaration && externalImport.exportClause) {
311                        hasExportDeclarationWithExportClause = true;
312                        break;
313                    }
314                }
315
316                if (!hasExportDeclarationWithExportClause) {
317                    // we still need to emit exportStar helper
318                    const exportStarFunction = createExportStarFunction(/*localNames*/ undefined);
319                    statements.push(exportStarFunction);
320                    return exportStarFunction.name;
321                }
322            }
323
324            const exportedNames: ObjectLiteralElementLike[] = [];
325            if (moduleInfo.exportedNames) {
326                for (const exportedLocalName of moduleInfo.exportedNames) {
327                    if (exportedLocalName.escapedText === "default") {
328                        continue;
329                    }
330
331                    // write name of exported declaration, i.e 'export var x...'
332                    exportedNames.push(
333                        factory.createPropertyAssignment(
334                            factory.createStringLiteralFromNode(exportedLocalName),
335                            factory.createTrue()
336                        )
337                    );
338                }
339            }
340
341            const exportedNamesStorageRef = factory.createUniqueName("exportedNames");
342            statements.push(
343                factory.createVariableStatement(
344                    /*modifiers*/ undefined,
345                    factory.createVariableDeclarationList([
346                        factory.createVariableDeclaration(
347                            exportedNamesStorageRef,
348                            /*exclamationToken*/ undefined,
349                            /*type*/ undefined,
350                            factory.createObjectLiteralExpression(exportedNames, /*multiline*/ true)
351                        )
352                    ])
353                )
354            );
355
356            const exportStarFunction = createExportStarFunction(exportedNamesStorageRef);
357            statements.push(exportStarFunction);
358            return exportStarFunction.name;
359        }
360
361        /**
362         * Creates an exportStar function for the file, with an optional set of excluded local
363         * names.
364         *
365         * @param localNames An optional reference to an object containing a set of excluded local
366         * names.
367         */
368        function createExportStarFunction(localNames: Identifier | undefined) {
369            const exportStarFunction = factory.createUniqueName("exportStar");
370            const m = factory.createIdentifier("m");
371            const n = factory.createIdentifier("n");
372            const exports = factory.createIdentifier("exports");
373            let condition: Expression = factory.createStrictInequality(n, factory.createStringLiteral("default"));
374            if (localNames) {
375                condition = factory.createLogicalAnd(
376                    condition,
377                    factory.createLogicalNot(
378                        factory.createCallExpression(
379                            factory.createPropertyAccessExpression(localNames, "hasOwnProperty"),
380                            /*typeArguments*/ undefined,
381                            [n]
382                        )
383                    )
384                );
385            }
386
387            return factory.createFunctionDeclaration(
388                /*modifiers*/ undefined,
389                /*asteriskToken*/ undefined,
390                exportStarFunction,
391                /*typeParameters*/ undefined,
392                [factory.createParameterDeclaration(/*modifiers*/ undefined, /*dotDotDotToken*/ undefined, m)],
393                /*type*/ undefined,
394                factory.createBlock([
395                    factory.createVariableStatement(
396                        /*modifiers*/ undefined,
397                        factory.createVariableDeclarationList([
398                            factory.createVariableDeclaration(
399                                exports,
400                                /*exclamationToken*/ undefined,
401                                /*type*/ undefined,
402                                factory.createObjectLiteralExpression([])
403                            )
404                        ])
405                    ),
406                    factory.createForInStatement(
407                        factory.createVariableDeclarationList([
408                            factory.createVariableDeclaration(n)
409                        ]),
410                        m,
411                        factory.createBlock([
412                            setEmitFlags(
413                                factory.createIfStatement(
414                                    condition,
415                                    factory.createExpressionStatement(
416                                        factory.createAssignment(
417                                            factory.createElementAccessExpression(exports, n),
418                                            factory.createElementAccessExpression(m, n)
419                                        )
420                                    )
421                                ),
422                                EmitFlags.SingleLine
423                            )
424                        ])
425                    ),
426                    factory.createExpressionStatement(
427                        factory.createCallExpression(
428                            exportFunction,
429                            /*typeArguments*/ undefined,
430                            [exports]
431                        )
432                    )
433                ], /*multiline*/ true)
434            );
435        }
436
437        /**
438         * Creates an array setter callbacks for each dependency group.
439         *
440         * @param exportStarFunction A reference to an exportStarFunction for the file.
441         * @param dependencyGroups An array of grouped dependencies.
442         */
443        function createSettersArray(exportStarFunction: Identifier, dependencyGroups: DependencyGroup[]) {
444            const setters: Expression[] = [];
445            for (const group of dependencyGroups) {
446                // derive a unique name for parameter from the first named entry in the group
447                const localName = forEach(group.externalImports, i => getLocalNameForExternalImport(factory, i, currentSourceFile));
448                const parameterName = localName ? factory.getGeneratedNameForNode(localName) : factory.createUniqueName("");
449                const statements: Statement[] = [];
450                for (const entry of group.externalImports) {
451                    const importVariableName = getLocalNameForExternalImport(factory, entry, currentSourceFile)!; // TODO: GH#18217
452                    switch (entry.kind) {
453                        case SyntaxKind.ImportDeclaration:
454                            if (!entry.importClause) {
455                                // 'import "..."' case
456                                // module is imported only for side-effects, no emit required
457                                break;
458                            }
459                            // falls through
460
461                        case SyntaxKind.ImportEqualsDeclaration:
462                            Debug.assert(importVariableName !== undefined);
463                            // save import into the local
464                            statements.push(
465                                factory.createExpressionStatement(
466                                    factory.createAssignment(importVariableName, parameterName)
467                                )
468                            );
469                            if (hasSyntacticModifier(entry, ModifierFlags.Export)) {
470                                statements.push(
471                                    factory.createExpressionStatement(
472                                        factory.createCallExpression(
473                                            exportFunction,
474                                            /*typeArguments*/ undefined,
475                                            [
476                                                factory.createStringLiteral(idText(importVariableName)),
477                                                parameterName,
478                                            ]
479                                        )
480                                    )
481                                );
482                            }
483                            break;
484
485                        case SyntaxKind.ExportDeclaration:
486                            Debug.assert(importVariableName !== undefined);
487                            if (entry.exportClause) {
488                                if (isNamedExports(entry.exportClause)) {
489                                    //  export {a, b as c} from 'foo'
490                                    //
491                                    // emit as:
492                                    //
493                                    //  exports_({
494                                    //     "a": _["a"],
495                                    //     "c": _["b"]
496                                    //  });
497                                    const properties: PropertyAssignment[] = [];
498                                    for (const e of entry.exportClause.elements) {
499                                        properties.push(
500                                            factory.createPropertyAssignment(
501                                                factory.createStringLiteral(idText(e.name)),
502                                                factory.createElementAccessExpression(
503                                                    parameterName,
504                                                    factory.createStringLiteral(idText(e.propertyName || e.name))
505                                                )
506                                            )
507                                        );
508                                    }
509
510                                    statements.push(
511                                        factory.createExpressionStatement(
512                                            factory.createCallExpression(
513                                                exportFunction,
514                                                /*typeArguments*/ undefined,
515                                                [factory.createObjectLiteralExpression(properties, /*multiline*/ true)]
516                                            )
517                                        )
518                                    );
519                                }
520                                else {
521                                    statements.push(
522                                        factory.createExpressionStatement(
523                                            factory.createCallExpression(
524                                                exportFunction,
525                                                /*typeArguments*/ undefined,
526                                                [
527                                                    factory.createStringLiteral(idText(entry.exportClause.name)),
528                                                    parameterName
529                                                ]
530                                            )
531                                        )
532                                    );
533                                }
534                            }
535                            else {
536                                //  export * from 'foo'
537                                //
538                                // emit as:
539                                //
540                                //  exportStar(foo_1_1);
541                                statements.push(
542                                    factory.createExpressionStatement(
543                                        factory.createCallExpression(
544                                            exportStarFunction,
545                                            /*typeArguments*/ undefined,
546                                            [parameterName]
547                                        )
548                                    )
549                                );
550                            }
551                            break;
552                    }
553                }
554
555                setters.push(
556                    factory.createFunctionExpression(
557                        /*modifiers*/ undefined,
558                        /*asteriskToken*/ undefined,
559                        /*name*/ undefined,
560                        /*typeParameters*/ undefined,
561                        [factory.createParameterDeclaration(/*modifiers*/ undefined, /*dotDotDotToken*/ undefined, parameterName)],
562                        /*type*/ undefined,
563                        factory.createBlock(statements, /*multiLine*/ true)
564                    )
565                );
566            }
567
568            return factory.createArrayLiteralExpression(setters, /*multiLine*/ true);
569        }
570
571        //
572        // Top-level Source Element Visitors
573        //
574
575        /**
576         * Visit source elements at the top-level of a module.
577         *
578         * @param node The node to visit.
579         */
580        function topLevelVisitor(node: Node): VisitResult<Node> {
581            switch (node.kind) {
582                case SyntaxKind.ImportDeclaration:
583                    return visitImportDeclaration(node as ImportDeclaration);
584
585                case SyntaxKind.ImportEqualsDeclaration:
586                    return visitImportEqualsDeclaration(node as ImportEqualsDeclaration);
587
588                case SyntaxKind.ExportDeclaration:
589                    return visitExportDeclaration(node as ExportDeclaration);
590
591                case SyntaxKind.ExportAssignment:
592                    return visitExportAssignment(node as ExportAssignment);
593
594                default:
595                    return topLevelNestedVisitor(node);
596            }
597        }
598
599        /**
600         * Visits an ImportDeclaration node.
601         *
602         * @param node The node to visit.
603         */
604        function visitImportDeclaration(node: ImportDeclaration): VisitResult<Statement> {
605            let statements: Statement[] | undefined;
606            if (node.importClause) {
607                hoistVariableDeclaration(getLocalNameForExternalImport(factory, node, currentSourceFile)!); // TODO: GH#18217
608            }
609
610            if (hasAssociatedEndOfDeclarationMarker(node)) {
611                // Defer exports until we encounter an EndOfDeclarationMarker node
612                const id = getOriginalNodeId(node);
613                deferredExports[id] = appendExportsOfImportDeclaration(deferredExports[id], node);
614            }
615            else {
616                statements = appendExportsOfImportDeclaration(statements, node);
617            }
618
619            return singleOrMany(statements);
620        }
621
622        function visitExportDeclaration(node: ExportDeclaration): VisitResult<Statement> {
623            Debug.assertIsDefined(node);
624            return undefined;
625        }
626
627        /**
628         * Visits an ImportEqualsDeclaration node.
629         *
630         * @param node The node to visit.
631         */
632        function visitImportEqualsDeclaration(node: ImportEqualsDeclaration): VisitResult<Statement> {
633            Debug.assert(isExternalModuleImportEqualsDeclaration(node), "import= for internal module references should be handled in an earlier transformer.");
634
635            let statements: Statement[] | undefined;
636            hoistVariableDeclaration(getLocalNameForExternalImport(factory, node, currentSourceFile)!); // TODO: GH#18217
637
638            if (hasAssociatedEndOfDeclarationMarker(node)) {
639                // Defer exports until we encounter an EndOfDeclarationMarker node
640                const id = getOriginalNodeId(node);
641                deferredExports[id] = appendExportsOfImportEqualsDeclaration(deferredExports[id], node);
642            }
643            else {
644                statements = appendExportsOfImportEqualsDeclaration(statements, node);
645            }
646
647            return singleOrMany(statements);
648        }
649
650        /**
651         * Visits an ExportAssignment node.
652         *
653         * @param node The node to visit.
654         */
655        function visitExportAssignment(node: ExportAssignment): VisitResult<Statement> {
656            if (node.isExportEquals) {
657                // Elide `export=` as it is illegal in a SystemJS module.
658                return undefined;
659            }
660
661            const expression = visitNode(node.expression, visitor, isExpression);
662            const original = node.original;
663            if (original && hasAssociatedEndOfDeclarationMarker(original)) {
664                // Defer exports until we encounter an EndOfDeclarationMarker node
665                const id = getOriginalNodeId(node);
666                deferredExports[id] = appendExportStatement(deferredExports[id], factory.createIdentifier("default"), expression, /*allowComments*/ true);
667            }
668            else {
669                return createExportStatement(factory.createIdentifier("default"), expression, /*allowComments*/ true);
670            }
671        }
672
673        /**
674         * Visits a FunctionDeclaration, hoisting it to the outer module body function.
675         *
676         * @param node The node to visit.
677         */
678        function visitFunctionDeclaration(node: FunctionDeclaration): VisitResult<Statement> {
679            if (hasSyntacticModifier(node, ModifierFlags.Export)) {
680                hoistedStatements = append(hoistedStatements,
681                    factory.updateFunctionDeclaration(
682                        node,
683                        visitNodes(node.modifiers, modifierVisitor, isModifierLike),
684                        node.asteriskToken,
685                        factory.getDeclarationName(node, /*allowComments*/ true, /*allowSourceMaps*/ true),
686                        /*typeParameters*/ undefined,
687                        visitNodes(node.parameters, visitor, isParameterDeclaration),
688                        /*type*/ undefined,
689                        visitNode(node.body, visitor, isBlock)));
690            }
691            else {
692                hoistedStatements = append(hoistedStatements, visitEachChild(node, visitor, context));
693            }
694
695            if (hasAssociatedEndOfDeclarationMarker(node)) {
696                // Defer exports until we encounter an EndOfDeclarationMarker node
697                const id = getOriginalNodeId(node);
698                deferredExports[id] = appendExportsOfHoistedDeclaration(deferredExports[id], node);
699            }
700            else {
701                hoistedStatements = appendExportsOfHoistedDeclaration(hoistedStatements, node);
702            }
703
704            return undefined;
705        }
706
707        /**
708         * Visits a ClassDeclaration, hoisting its name to the outer module body function.
709         *
710         * @param node The node to visit.
711         */
712        function visitClassDeclaration(node: ClassDeclaration): VisitResult<Statement> {
713            let statements: Statement[] | undefined;
714
715            // Hoist the name of the class declaration to the outer module body function.
716            const name = factory.getLocalName(node);
717            hoistVariableDeclaration(name);
718
719            // Rewrite the class declaration into an assignment of a class expression.
720            statements = append(statements,
721                setTextRange(
722                    factory.createExpressionStatement(
723                        factory.createAssignment(
724                            name,
725                            setTextRange(
726                                factory.createClassExpression(
727                                    visitNodes(node.modifiers, modifierVisitor, isModifierLike),
728                                    node.name,
729                                    /*typeParameters*/ undefined,
730                                    visitNodes(node.heritageClauses, visitor, isHeritageClause),
731                                    visitNodes(node.members, visitor, isClassElement)
732                                ),
733                                node
734                            )
735                        )
736                    ),
737                    node
738                )
739            );
740
741            if (hasAssociatedEndOfDeclarationMarker(node)) {
742                // Defer exports until we encounter an EndOfDeclarationMarker node
743                const id = getOriginalNodeId(node);
744                deferredExports[id] = appendExportsOfHoistedDeclaration(deferredExports[id], node);
745            }
746            else {
747                statements = appendExportsOfHoistedDeclaration(statements, node);
748            }
749
750            return singleOrMany(statements);
751        }
752
753        /**
754         * Visits a variable statement, hoisting declared names to the top-level module body.
755         * Each declaration is rewritten into an assignment expression.
756         *
757         * @param node The node to visit.
758         */
759        function visitVariableStatement(node: VariableStatement): VisitResult<Statement> {
760            if (!shouldHoistVariableDeclarationList(node.declarationList)) {
761                return visitNode(node, visitor, isStatement);
762            }
763
764            let expressions: Expression[] | undefined;
765            const isExportedDeclaration = hasSyntacticModifier(node, ModifierFlags.Export);
766            const isMarkedDeclaration = hasAssociatedEndOfDeclarationMarker(node);
767            for (const variable of node.declarationList.declarations) {
768                if (variable.initializer) {
769                    expressions = append(expressions, transformInitializedVariable(variable, isExportedDeclaration && !isMarkedDeclaration));
770                }
771                else {
772                    hoistBindingElement(variable);
773                }
774            }
775
776            let statements: Statement[] | undefined;
777            if (expressions) {
778                statements = append(statements, setTextRange(factory.createExpressionStatement(factory.inlineExpressions(expressions)), node));
779            }
780
781            if (isMarkedDeclaration) {
782                // Defer exports until we encounter an EndOfDeclarationMarker node
783                const id = getOriginalNodeId(node);
784                deferredExports[id] = appendExportsOfVariableStatement(deferredExports[id], node, isExportedDeclaration);
785            }
786            else {
787                statements = appendExportsOfVariableStatement(statements, node, /*exportSelf*/ false);
788            }
789
790            return singleOrMany(statements);
791        }
792
793        /**
794         * Hoists the declared names of a VariableDeclaration or BindingElement.
795         *
796         * @param node The declaration to hoist.
797         */
798        function hoistBindingElement(node: VariableDeclaration | BindingElement): void {
799            if (isBindingPattern(node.name)) {
800                for (const element of node.name.elements) {
801                    if (!isOmittedExpression(element)) {
802                        hoistBindingElement(element);
803                    }
804                }
805            }
806            else {
807                hoistVariableDeclaration(factory.cloneNode(node.name));
808            }
809        }
810
811        /**
812         * Determines whether a VariableDeclarationList should be hoisted.
813         *
814         * @param node The node to test.
815         */
816        function shouldHoistVariableDeclarationList(node: VariableDeclarationList) {
817            // hoist only non-block scoped declarations or block scoped declarations parented by source file
818            return (getEmitFlags(node) & EmitFlags.NoHoisting) === 0
819                && (enclosingBlockScopedContainer.kind === SyntaxKind.SourceFile
820                    || (getOriginalNode(node).flags & NodeFlags.BlockScoped) === 0);
821        }
822
823        /**
824         * Transform an initialized variable declaration into an expression.
825         *
826         * @param node The node to transform.
827         * @param isExportedDeclaration A value indicating whether the variable is exported.
828         */
829        function transformInitializedVariable(node: VariableDeclaration, isExportedDeclaration: boolean): Expression {
830            const createAssignment = isExportedDeclaration ? createExportedVariableAssignment : createNonExportedVariableAssignment;
831            return isBindingPattern(node.name)
832                ? flattenDestructuringAssignment(
833                    node,
834                    visitor,
835                    context,
836                    FlattenLevel.All,
837                    /*needsValue*/ false,
838                    createAssignment
839                )
840                : node.initializer ? createAssignment(node.name, visitNode(node.initializer, visitor, isExpression)) : node.name;
841        }
842
843        /**
844         * Creates an assignment expression for an exported variable declaration.
845         *
846         * @param name The name of the variable.
847         * @param value The value of the variable's initializer.
848         * @param location The source map location for the assignment.
849         */
850        function createExportedVariableAssignment(name: Identifier, value: Expression, location?: TextRange) {
851            return createVariableAssignment(name, value, location, /*isExportedDeclaration*/ true);
852        }
853
854        /**
855         * Creates an assignment expression for a non-exported variable declaration.
856         *
857         * @param name The name of the variable.
858         * @param value The value of the variable's initializer.
859         * @param location The source map location for the assignment.
860         */
861        function createNonExportedVariableAssignment(name: Identifier, value: Expression, location?: TextRange) {
862            return createVariableAssignment(name, value, location, /*isExportedDeclaration*/ false);
863        }
864
865        /**
866         * Creates an assignment expression for a variable declaration.
867         *
868         * @param name The name of the variable.
869         * @param value The value of the variable's initializer.
870         * @param location The source map location for the assignment.
871         * @param isExportedDeclaration A value indicating whether the variable is exported.
872         */
873        function createVariableAssignment(name: Identifier, value: Expression, location: TextRange | undefined, isExportedDeclaration: boolean) {
874            hoistVariableDeclaration(factory.cloneNode(name));
875            return isExportedDeclaration
876                ? createExportExpression(name, preventSubstitution(setTextRange(factory.createAssignment(name, value), location)))
877                : preventSubstitution(setTextRange(factory.createAssignment(name, value), location));
878        }
879
880        /**
881         * Visits a MergeDeclarationMarker used as a placeholder for the beginning of a merged
882         * and transformed declaration.
883         *
884         * @param node The node to visit.
885         */
886        function visitMergeDeclarationMarker(node: MergeDeclarationMarker): VisitResult<Statement> {
887            // For an EnumDeclaration or ModuleDeclaration that merges with a preceeding
888            // declaration we do not emit a leading variable declaration. To preserve the
889            // begin/end semantics of the declararation and to properly handle exports
890            // we wrapped the leading variable declaration in a `MergeDeclarationMarker`.
891            //
892            // To balance the declaration, we defer the exports of the elided variable
893            // statement until we visit this declaration's `EndOfDeclarationMarker`.
894            if (hasAssociatedEndOfDeclarationMarker(node) && node.original!.kind === SyntaxKind.VariableStatement) {
895                const id = getOriginalNodeId(node);
896                const isExportedDeclaration = hasSyntacticModifier(node.original!, ModifierFlags.Export);
897                deferredExports[id] = appendExportsOfVariableStatement(deferredExports[id], node.original as VariableStatement, isExportedDeclaration);
898            }
899
900            return node;
901        }
902
903        /**
904         * Determines whether a node has an associated EndOfDeclarationMarker.
905         *
906         * @param node The node to test.
907         */
908        function hasAssociatedEndOfDeclarationMarker(node: Node) {
909            return (getEmitFlags(node) & EmitFlags.HasEndOfDeclarationMarker) !== 0;
910        }
911
912        /**
913         * Visits a DeclarationMarker used as a placeholder for the end of a transformed
914         * declaration.
915         *
916         * @param node The node to visit.
917         */
918        function visitEndOfDeclarationMarker(node: EndOfDeclarationMarker): VisitResult<Statement> {
919            // For some transformations we emit an `EndOfDeclarationMarker` to mark the actual
920            // end of the transformed declaration. We use this marker to emit any deferred exports
921            // of the declaration.
922            const id = getOriginalNodeId(node);
923            const statements = deferredExports[id];
924            if (statements) {
925                delete deferredExports[id];
926                return append(statements, node);
927            }
928            else {
929                const original = getOriginalNode(node);
930                if (isModuleOrEnumDeclaration(original)) {
931                    return append(appendExportsOfDeclaration(statements, original), node);
932                }
933            }
934
935            return node;
936        }
937
938        /**
939         * Appends the exports of an ImportDeclaration to a statement list, returning the
940         * statement list.
941         *
942         * @param statements A statement list to which the down-level export statements are to be
943         * appended. If `statements` is `undefined`, a new array is allocated if statements are
944         * appended.
945         * @param decl The declaration whose exports are to be recorded.
946         */
947        function appendExportsOfImportDeclaration(statements: Statement[] | undefined, decl: ImportDeclaration) {
948            if (moduleInfo.exportEquals) {
949                return statements;
950            }
951
952            const importClause = decl.importClause;
953            if (!importClause) {
954                return statements;
955            }
956
957            if (importClause.name) {
958                statements = appendExportsOfDeclaration(statements, importClause);
959            }
960
961            const namedBindings = importClause.namedBindings;
962            if (namedBindings) {
963                switch (namedBindings.kind) {
964                    case SyntaxKind.NamespaceImport:
965                        statements = appendExportsOfDeclaration(statements, namedBindings);
966                        break;
967
968                    case SyntaxKind.NamedImports:
969                        for (const importBinding of namedBindings.elements) {
970                            statements = appendExportsOfDeclaration(statements, importBinding);
971                        }
972
973                        break;
974                }
975            }
976
977            return statements;
978        }
979
980        /**
981         * Appends the export of an ImportEqualsDeclaration to a statement list, returning the
982         * statement list.
983         *
984         * @param statements A statement list to which the down-level export statements are to be
985         * appended. If `statements` is `undefined`, a new array is allocated if statements are
986         * appended.
987         * @param decl The declaration whose exports are to be recorded.
988         */
989        function appendExportsOfImportEqualsDeclaration(statements: Statement[] | undefined, decl: ImportEqualsDeclaration): Statement[] | undefined {
990            if (moduleInfo.exportEquals) {
991                return statements;
992            }
993
994            return appendExportsOfDeclaration(statements, decl);
995        }
996
997        /**
998         * Appends the exports of a VariableStatement to a statement list, returning the statement
999         * list.
1000         *
1001         * @param statements A statement list to which the down-level export statements are to be
1002         * appended. If `statements` is `undefined`, a new array is allocated if statements are
1003         * appended.
1004         * @param node The VariableStatement whose exports are to be recorded.
1005         * @param exportSelf A value indicating whether to also export each VariableDeclaration of
1006         * `nodes` declaration list.
1007         */
1008        function appendExportsOfVariableStatement(statements: Statement[] | undefined, node: VariableStatement, exportSelf: boolean): Statement[] | undefined {
1009            if (moduleInfo.exportEquals) {
1010                return statements;
1011            }
1012
1013            for (const decl of node.declarationList.declarations) {
1014                if (decl.initializer || exportSelf) {
1015                    statements = appendExportsOfBindingElement(statements, decl, exportSelf);
1016                }
1017            }
1018
1019            return statements;
1020        }
1021
1022        /**
1023         * Appends the exports of a VariableDeclaration or BindingElement to a statement list,
1024         * returning the statement list.
1025         *
1026         * @param statements A statement list to which the down-level export statements are to be
1027         * appended. If `statements` is `undefined`, a new array is allocated if statements are
1028         * appended.
1029         * @param decl The declaration whose exports are to be recorded.
1030         * @param exportSelf A value indicating whether to also export the declaration itself.
1031         */
1032        function appendExportsOfBindingElement(statements: Statement[] | undefined, decl: VariableDeclaration | BindingElement, exportSelf: boolean): Statement[] | undefined {
1033            if (moduleInfo.exportEquals) {
1034                return statements;
1035            }
1036
1037            if (isBindingPattern(decl.name)) {
1038                for (const element of decl.name.elements) {
1039                    if (!isOmittedExpression(element)) {
1040                        statements = appendExportsOfBindingElement(statements, element, exportSelf);
1041                    }
1042                }
1043            }
1044            else if (!isGeneratedIdentifier(decl.name)) {
1045                let excludeName: string | undefined;
1046                if (exportSelf) {
1047                    statements = appendExportStatement(statements, decl.name, factory.getLocalName(decl));
1048                    excludeName = idText(decl.name);
1049                }
1050
1051                statements = appendExportsOfDeclaration(statements, decl, excludeName);
1052            }
1053
1054            return statements;
1055        }
1056
1057        /**
1058         * Appends the exports of a ClassDeclaration or FunctionDeclaration to a statement list,
1059         * returning the statement list.
1060         *
1061         * @param statements A statement list to which the down-level export statements are to be
1062         * appended. If `statements` is `undefined`, a new array is allocated if statements are
1063         * appended.
1064         * @param decl The declaration whose exports are to be recorded.
1065         */
1066        function appendExportsOfHoistedDeclaration(statements: Statement[] | undefined, decl: ClassDeclaration | FunctionDeclaration): Statement[] | undefined {
1067            if (moduleInfo.exportEquals) {
1068                return statements;
1069            }
1070
1071            let excludeName: string | undefined;
1072            if (hasSyntacticModifier(decl, ModifierFlags.Export)) {
1073                const exportName = hasSyntacticModifier(decl, ModifierFlags.Default) ? factory.createStringLiteral("default") : decl.name!;
1074                statements = appendExportStatement(statements, exportName, factory.getLocalName(decl));
1075                excludeName = getTextOfIdentifierOrLiteral(exportName);
1076            }
1077
1078            if (decl.name) {
1079                statements = appendExportsOfDeclaration(statements, decl, excludeName);
1080            }
1081
1082            return statements;
1083        }
1084
1085        /**
1086         * Appends the exports of a declaration to a statement list, returning the statement list.
1087         *
1088         * @param statements A statement list to which the down-level export statements are to be
1089         * appended. If `statements` is `undefined`, a new array is allocated if statements are
1090         * appended.
1091         * @param decl The declaration to export.
1092         * @param excludeName An optional name to exclude from exports.
1093         */
1094        function appendExportsOfDeclaration(statements: Statement[] | undefined, decl: Declaration, excludeName?: string): Statement[] | undefined {
1095            if (moduleInfo.exportEquals) {
1096                return statements;
1097            }
1098
1099            const name = factory.getDeclarationName(decl);
1100            const exportSpecifiers = moduleInfo.exportSpecifiers.get(idText(name));
1101            if (exportSpecifiers) {
1102                for (const exportSpecifier of exportSpecifiers) {
1103                    if (exportSpecifier.name.escapedText !== excludeName) {
1104                        statements = appendExportStatement(statements, exportSpecifier.name, name);
1105                    }
1106                }
1107            }
1108            return statements;
1109        }
1110
1111        /**
1112         * Appends the down-level representation of an export to a statement list, returning the
1113         * statement list.
1114         *
1115         * @param statements A statement list to which the down-level export statements are to be
1116         * appended. If `statements` is `undefined`, a new array is allocated if statements are
1117         * appended.
1118         * @param exportName The name of the export.
1119         * @param expression The expression to export.
1120         * @param allowComments Whether to allow comments on the export.
1121         */
1122        function appendExportStatement(statements: Statement[] | undefined, exportName: Identifier | StringLiteral, expression: Expression, allowComments?: boolean): Statement[] | undefined {
1123            statements = append(statements, createExportStatement(exportName, expression, allowComments));
1124            return statements;
1125        }
1126
1127        /**
1128         * Creates a call to the current file's export function to export a value.
1129         *
1130         * @param name The bound name of the export.
1131         * @param value The exported value.
1132         * @param allowComments An optional value indicating whether to emit comments for the statement.
1133         */
1134        function createExportStatement(name: Identifier | StringLiteral, value: Expression, allowComments?: boolean) {
1135            const statement = factory.createExpressionStatement(createExportExpression(name, value));
1136            startOnNewLine(statement);
1137            if (!allowComments) {
1138                setEmitFlags(statement, EmitFlags.NoComments);
1139            }
1140
1141            return statement;
1142        }
1143
1144        /**
1145         * Creates a call to the current file's export function to export a value.
1146         *
1147         * @param name The bound name of the export.
1148         * @param value The exported value.
1149         */
1150        function createExportExpression(name: Identifier | StringLiteral, value: Expression) {
1151            const exportName = isIdentifier(name) ? factory.createStringLiteralFromNode(name) : name;
1152            setEmitFlags(value, getEmitFlags(value) | EmitFlags.NoComments);
1153            return setCommentRange(factory.createCallExpression(exportFunction, /*typeArguments*/ undefined, [exportName, value]), value);
1154        }
1155
1156        //
1157        // Top-Level or Nested Source Element Visitors
1158        //
1159
1160        /**
1161         * Visit nested elements at the top-level of a module.
1162         *
1163         * @param node The node to visit.
1164         */
1165        function topLevelNestedVisitor(node: Node): VisitResult<Node> {
1166            switch (node.kind) {
1167                case SyntaxKind.VariableStatement:
1168                    return visitVariableStatement(node as VariableStatement);
1169
1170                case SyntaxKind.FunctionDeclaration:
1171                    return visitFunctionDeclaration(node as FunctionDeclaration);
1172
1173                case SyntaxKind.ClassDeclaration:
1174                    return visitClassDeclaration(node as ClassDeclaration);
1175
1176                case SyntaxKind.ForStatement:
1177                    return visitForStatement(node as ForStatement, /*isTopLevel*/ true);
1178
1179                case SyntaxKind.ForInStatement:
1180                    return visitForInStatement(node as ForInStatement);
1181
1182                case SyntaxKind.ForOfStatement:
1183                    return visitForOfStatement(node as ForOfStatement);
1184
1185                case SyntaxKind.DoStatement:
1186                    return visitDoStatement(node as DoStatement);
1187
1188                case SyntaxKind.WhileStatement:
1189                    return visitWhileStatement(node as WhileStatement);
1190
1191                case SyntaxKind.LabeledStatement:
1192                    return visitLabeledStatement(node as LabeledStatement);
1193
1194                case SyntaxKind.WithStatement:
1195                    return visitWithStatement(node as WithStatement);
1196
1197                case SyntaxKind.SwitchStatement:
1198                    return visitSwitchStatement(node as SwitchStatement);
1199
1200                case SyntaxKind.CaseBlock:
1201                    return visitCaseBlock(node as CaseBlock);
1202
1203                case SyntaxKind.CaseClause:
1204                    return visitCaseClause(node as CaseClause);
1205
1206                case SyntaxKind.DefaultClause:
1207                    return visitDefaultClause(node as DefaultClause);
1208
1209                case SyntaxKind.TryStatement:
1210                    return visitTryStatement(node as TryStatement);
1211
1212                case SyntaxKind.CatchClause:
1213                    return visitCatchClause(node as CatchClause);
1214
1215                case SyntaxKind.Block:
1216                    return visitBlock(node as Block);
1217
1218                case SyntaxKind.MergeDeclarationMarker:
1219                    return visitMergeDeclarationMarker(node as MergeDeclarationMarker);
1220
1221                case SyntaxKind.EndOfDeclarationMarker:
1222                    return visitEndOfDeclarationMarker(node as EndOfDeclarationMarker);
1223
1224                default:
1225                    return visitor(node);
1226            }
1227        }
1228
1229        /**
1230         * Visits the body of a ForStatement to hoist declarations.
1231         *
1232         * @param node The node to visit.
1233         */
1234        function visitForStatement(node: ForStatement, isTopLevel: boolean): VisitResult<Statement> {
1235            const savedEnclosingBlockScopedContainer = enclosingBlockScopedContainer;
1236            enclosingBlockScopedContainer = node;
1237
1238            node = factory.updateForStatement(
1239                node,
1240                visitNode(node.initializer, isTopLevel ? visitForInitializer : discardedValueVisitor, isForInitializer),
1241                visitNode(node.condition, visitor, isExpression),
1242                visitNode(node.incrementor, discardedValueVisitor, isExpression),
1243                visitIterationBody(node.statement, isTopLevel ? topLevelNestedVisitor : visitor, context)
1244            );
1245
1246            enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer;
1247            return node;
1248        }
1249
1250        /**
1251         * Visits the body of a ForInStatement to hoist declarations.
1252         *
1253         * @param node The node to visit.
1254         */
1255        function visitForInStatement(node: ForInStatement): VisitResult<Statement> {
1256            const savedEnclosingBlockScopedContainer = enclosingBlockScopedContainer;
1257            enclosingBlockScopedContainer = node;
1258
1259            node = factory.updateForInStatement(
1260                node,
1261                visitForInitializer(node.initializer),
1262                visitNode(node.expression, visitor, isExpression),
1263                visitIterationBody(node.statement, topLevelNestedVisitor, context)
1264            );
1265
1266            enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer;
1267            return node;
1268        }
1269
1270        /**
1271         * Visits the body of a ForOfStatement to hoist declarations.
1272         *
1273         * @param node The node to visit.
1274         */
1275        function visitForOfStatement(node: ForOfStatement): VisitResult<Statement> {
1276            const savedEnclosingBlockScopedContainer = enclosingBlockScopedContainer;
1277            enclosingBlockScopedContainer = node;
1278
1279            node = factory.updateForOfStatement(
1280                node,
1281                node.awaitModifier,
1282                visitForInitializer(node.initializer),
1283                visitNode(node.expression, visitor, isExpression),
1284                visitIterationBody(node.statement, topLevelNestedVisitor, context)
1285            );
1286
1287            enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer;
1288            return node;
1289        }
1290
1291        /**
1292         * Determines whether to hoist the initializer of a ForStatement, ForInStatement, or
1293         * ForOfStatement.
1294         *
1295         * @param node The node to test.
1296         */
1297        function shouldHoistForInitializer(node: ForInitializer): node is VariableDeclarationList {
1298            return isVariableDeclarationList(node)
1299                && shouldHoistVariableDeclarationList(node);
1300        }
1301
1302        /**
1303         * Visits the initializer of a ForStatement, ForInStatement, or ForOfStatement
1304         *
1305         * @param node The node to visit.
1306         */
1307        function visitForInitializer(node: ForInitializer): ForInitializer {
1308            if (shouldHoistForInitializer(node)) {
1309                let expressions: Expression[] | undefined;
1310                for (const variable of node.declarations) {
1311                    expressions = append(expressions, transformInitializedVariable(variable, /*isExportedDeclaration*/ false));
1312                    if (!variable.initializer) {
1313                        hoistBindingElement(variable);
1314                    }
1315                }
1316
1317                return expressions ? factory.inlineExpressions(expressions) : factory.createOmittedExpression();
1318            }
1319            else {
1320                return visitNode(node, discardedValueVisitor, isExpression);
1321            }
1322        }
1323
1324        /**
1325         * Visits the body of a DoStatement to hoist declarations.
1326         *
1327         * @param node The node to visit.
1328         */
1329        function visitDoStatement(node: DoStatement): VisitResult<Statement> {
1330            return factory.updateDoStatement(
1331                node,
1332                visitIterationBody(node.statement, topLevelNestedVisitor, context),
1333                visitNode(node.expression, visitor, isExpression)
1334            );
1335        }
1336
1337        /**
1338         * Visits the body of a WhileStatement to hoist declarations.
1339         *
1340         * @param node The node to visit.
1341         */
1342        function visitWhileStatement(node: WhileStatement): VisitResult<Statement> {
1343            return factory.updateWhileStatement(
1344                node,
1345                visitNode(node.expression, visitor, isExpression),
1346                visitIterationBody(node.statement, topLevelNestedVisitor, context)
1347            );
1348        }
1349
1350        /**
1351         * Visits the body of a LabeledStatement to hoist declarations.
1352         *
1353         * @param node The node to visit.
1354         */
1355        function visitLabeledStatement(node: LabeledStatement): VisitResult<Statement> {
1356            return factory.updateLabeledStatement(
1357                node,
1358                node.label,
1359                visitNode(node.statement, topLevelNestedVisitor, isStatement, factory.liftToBlock)
1360            );
1361        }
1362
1363        /**
1364         * Visits the body of a WithStatement to hoist declarations.
1365         *
1366         * @param node The node to visit.
1367         */
1368        function visitWithStatement(node: WithStatement): VisitResult<Statement> {
1369            return factory.updateWithStatement(
1370                node,
1371                visitNode(node.expression, visitor, isExpression),
1372                visitNode(node.statement, topLevelNestedVisitor, isStatement, factory.liftToBlock)
1373            );
1374        }
1375
1376        /**
1377         * Visits the body of a SwitchStatement to hoist declarations.
1378         *
1379         * @param node The node to visit.
1380         */
1381        function visitSwitchStatement(node: SwitchStatement): VisitResult<Statement> {
1382            return factory.updateSwitchStatement(
1383                node,
1384                visitNode(node.expression, visitor, isExpression),
1385                visitNode(node.caseBlock, topLevelNestedVisitor, isCaseBlock)
1386            );
1387        }
1388
1389        /**
1390         * Visits the body of a CaseBlock to hoist declarations.
1391         *
1392         * @param node The node to visit.
1393         */
1394        function visitCaseBlock(node: CaseBlock): CaseBlock {
1395            const savedEnclosingBlockScopedContainer = enclosingBlockScopedContainer;
1396            enclosingBlockScopedContainer = node;
1397
1398            node = factory.updateCaseBlock(
1399                node,
1400                visitNodes(node.clauses, topLevelNestedVisitor, isCaseOrDefaultClause)
1401            );
1402
1403            enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer;
1404            return node;
1405        }
1406
1407        /**
1408         * Visits the body of a CaseClause to hoist declarations.
1409         *
1410         * @param node The node to visit.
1411         */
1412        function visitCaseClause(node: CaseClause): VisitResult<CaseOrDefaultClause> {
1413            return factory.updateCaseClause(
1414                node,
1415                visitNode(node.expression, visitor, isExpression),
1416                visitNodes(node.statements, topLevelNestedVisitor, isStatement)
1417            );
1418        }
1419
1420        /**
1421         * Visits the body of a DefaultClause to hoist declarations.
1422         *
1423         * @param node The node to visit.
1424         */
1425        function visitDefaultClause(node: DefaultClause): VisitResult<CaseOrDefaultClause> {
1426            return visitEachChild(node, topLevelNestedVisitor, context);
1427        }
1428
1429        /**
1430         * Visits the body of a TryStatement to hoist declarations.
1431         *
1432         * @param node The node to visit.
1433         */
1434        function visitTryStatement(node: TryStatement): VisitResult<Statement> {
1435            return visitEachChild(node, topLevelNestedVisitor, context);
1436        }
1437
1438        /**
1439         * Visits the body of a CatchClause to hoist declarations.
1440         *
1441         * @param node The node to visit.
1442         */
1443        function visitCatchClause(node: CatchClause): CatchClause {
1444            const savedEnclosingBlockScopedContainer = enclosingBlockScopedContainer;
1445            enclosingBlockScopedContainer = node;
1446
1447            node = factory.updateCatchClause(
1448                node,
1449                node.variableDeclaration,
1450                visitNode(node.block, topLevelNestedVisitor, isBlock)
1451            );
1452
1453            enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer;
1454            return node;
1455        }
1456
1457        /**
1458         * Visits the body of a Block to hoist declarations.
1459         *
1460         * @param node The node to visit.
1461         */
1462        function visitBlock(node: Block): Block {
1463            const savedEnclosingBlockScopedContainer = enclosingBlockScopedContainer;
1464            enclosingBlockScopedContainer = node;
1465
1466            node = visitEachChild(node, topLevelNestedVisitor, context);
1467
1468            enclosingBlockScopedContainer = savedEnclosingBlockScopedContainer;
1469            return node;
1470        }
1471
1472        //
1473        // Destructuring Assignment Visitors
1474        //
1475
1476        /**
1477         * Visit nodes to flatten destructuring assignments to exported symbols.
1478         *
1479         * @param node The node to visit.
1480         */
1481        function visitorWorker(node: Node, valueIsDiscarded: boolean): VisitResult<Node> {
1482            if (!(node.transformFlags & (TransformFlags.ContainsDestructuringAssignment | TransformFlags.ContainsDynamicImport | TransformFlags.ContainsUpdateExpressionForIdentifier))) {
1483                return node;
1484            }
1485            switch (node.kind) {
1486                case SyntaxKind.ForStatement:
1487                    return visitForStatement(node as ForStatement, /*isTopLevel*/ false);
1488                case SyntaxKind.ExpressionStatement:
1489                    return visitExpressionStatement(node as ExpressionStatement);
1490                case SyntaxKind.ParenthesizedExpression:
1491                    return visitParenthesizedExpression(node as ParenthesizedExpression, valueIsDiscarded);
1492                case SyntaxKind.PartiallyEmittedExpression:
1493                    return visitPartiallyEmittedExpression(node as PartiallyEmittedExpression, valueIsDiscarded);
1494                case SyntaxKind.BinaryExpression:
1495                    if (isDestructuringAssignment(node)) {
1496                        return visitDestructuringAssignment(node, valueIsDiscarded);
1497                    }
1498                    break;
1499                case SyntaxKind.CallExpression:
1500                    if (isImportCall(node)) {
1501                        return visitImportCallExpression(node);
1502                    }
1503                    break;
1504                case SyntaxKind.PrefixUnaryExpression:
1505                case SyntaxKind.PostfixUnaryExpression:
1506                    return visitPrefixOrPostfixUnaryExpression(node as PrefixUnaryExpression | PostfixUnaryExpression, valueIsDiscarded);
1507            }
1508            return visitEachChild(node, visitor, context);
1509        }
1510
1511        /**
1512         * Visit nodes to flatten destructuring assignments to exported symbols.
1513         *
1514         * @param node The node to visit.
1515         */
1516        function visitor(node: Node): VisitResult<Node> {
1517            return visitorWorker(node, /*valueIsDiscarded*/ false);
1518        }
1519
1520        function discardedValueVisitor(node: Node): VisitResult<Node> {
1521            return visitorWorker(node, /*valueIsDiscarded*/ true);
1522        }
1523
1524        function visitExpressionStatement(node: ExpressionStatement) {
1525            return factory.updateExpressionStatement(node, visitNode(node.expression, discardedValueVisitor, isExpression));
1526        }
1527
1528        function visitParenthesizedExpression(node: ParenthesizedExpression, valueIsDiscarded: boolean) {
1529            return factory.updateParenthesizedExpression(node, visitNode(node.expression, valueIsDiscarded ? discardedValueVisitor : visitor, isExpression));
1530        }
1531
1532        function visitPartiallyEmittedExpression(node: PartiallyEmittedExpression, valueIsDiscarded: boolean) {
1533            return factory.updatePartiallyEmittedExpression(node, visitNode(node.expression, valueIsDiscarded ? discardedValueVisitor : visitor, isExpression));
1534        }
1535
1536        function visitImportCallExpression(node: ImportCall): Expression {
1537            // import("./blah")
1538            // emit as
1539            // System.register([], function (_export, _context) {
1540            //     return {
1541            //         setters: [],
1542            //         execute: () => {
1543            //             _context.import('./blah');
1544            //         }
1545            //     };
1546            // });
1547            const externalModuleName = getExternalModuleNameLiteral(factory, node, currentSourceFile, host, resolver, compilerOptions);
1548            const firstArgument = visitNode(firstOrUndefined(node.arguments), visitor);
1549            // Only use the external module name if it differs from the first argument. This allows us to preserve the quote style of the argument on output.
1550            const argument = externalModuleName && (!firstArgument || !isStringLiteral(firstArgument) || firstArgument.text !== externalModuleName.text) ? externalModuleName : firstArgument;
1551            return factory.createCallExpression(
1552                factory.createPropertyAccessExpression(
1553                    contextObject,
1554                    factory.createIdentifier("import")
1555                ),
1556                /*typeArguments*/ undefined,
1557                argument ? [argument] : []
1558            );
1559        }
1560
1561        /**
1562         * Visits a DestructuringAssignment to flatten destructuring to exported symbols.
1563         *
1564         * @param node The node to visit.
1565         */
1566        function visitDestructuringAssignment(node: DestructuringAssignment, valueIsDiscarded: boolean): VisitResult<Expression> {
1567            if (hasExportedReferenceInDestructuringTarget(node.left)) {
1568                return flattenDestructuringAssignment(
1569                    node,
1570                    visitor,
1571                    context,
1572                    FlattenLevel.All,
1573                    !valueIsDiscarded
1574                );
1575            }
1576
1577            return visitEachChild(node, visitor, context);
1578        }
1579
1580        /**
1581         * Determines whether the target of a destructuring assignment refers to an exported symbol.
1582         *
1583         * @param node The destructuring target.
1584         */
1585        function hasExportedReferenceInDestructuringTarget(node: Expression | ObjectLiteralElementLike): boolean {
1586            if (isAssignmentExpression(node, /*excludeCompoundAssignment*/ true)) {
1587                return hasExportedReferenceInDestructuringTarget(node.left);
1588            }
1589            else if (isSpreadElement(node)) {
1590                return hasExportedReferenceInDestructuringTarget(node.expression);
1591            }
1592            else if (isObjectLiteralExpression(node)) {
1593                return some(node.properties, hasExportedReferenceInDestructuringTarget);
1594            }
1595            else if (isArrayLiteralExpression(node)) {
1596                return some(node.elements, hasExportedReferenceInDestructuringTarget);
1597            }
1598            else if (isShorthandPropertyAssignment(node)) {
1599                return hasExportedReferenceInDestructuringTarget(node.name);
1600            }
1601            else if (isPropertyAssignment(node)) {
1602                return hasExportedReferenceInDestructuringTarget(node.initializer);
1603            }
1604            else if (isIdentifier(node)) {
1605                const container = resolver.getReferencedExportContainer(node);
1606                return container !== undefined && container.kind === SyntaxKind.SourceFile;
1607            }
1608            else {
1609                return false;
1610            }
1611        }
1612
1613        function visitPrefixOrPostfixUnaryExpression(node: PrefixUnaryExpression | PostfixUnaryExpression, valueIsDiscarded: boolean) {
1614            // When we see a prefix or postfix increment expression whose operand is an exported
1615            // symbol, we should ensure all exports of that symbol are updated with the correct
1616            // value.
1617            //
1618            // - We do not transform generated identifiers for any reason.
1619            // - We do not transform identifiers tagged with the LocalName flag.
1620            // - We do not transform identifiers that were originally the name of an enum or
1621            //   namespace due to how they are transformed in TypeScript.
1622            // - We only transform identifiers that are exported at the top level.
1623            if ((node.operator === SyntaxKind.PlusPlusToken || node.operator === SyntaxKind.MinusMinusToken)
1624                && isIdentifier(node.operand)
1625                && !isGeneratedIdentifier(node.operand)
1626                && !isLocalName(node.operand)
1627                && !isDeclarationNameOfEnumOrNamespace(node.operand)) {
1628                const exportedNames = getExports(node.operand);
1629                if (exportedNames) {
1630                    let temp: Identifier | undefined;
1631                    let expression: Expression = visitNode(node.operand, visitor, isExpression);
1632                    if (isPrefixUnaryExpression(node)) {
1633                        expression = factory.updatePrefixUnaryExpression(node, expression);
1634                    }
1635                    else {
1636                        expression = factory.updatePostfixUnaryExpression(node, expression);
1637                        if (!valueIsDiscarded) {
1638                            temp = factory.createTempVariable(hoistVariableDeclaration);
1639                            expression = factory.createAssignment(temp, expression);
1640                            setTextRange(expression, node);
1641                        }
1642                        expression = factory.createComma(expression, factory.cloneNode(node.operand));
1643                        setTextRange(expression, node);
1644                    }
1645
1646                    for (const exportName of exportedNames) {
1647                        expression = createExportExpression(exportName, preventSubstitution(expression));
1648                    }
1649
1650                    if (temp) {
1651                        expression = factory.createComma(expression, temp);
1652                        setTextRange(expression, node);
1653                    }
1654
1655                    return expression;
1656                }
1657            }
1658            return visitEachChild(node, visitor, context);
1659        }
1660
1661        //
1662        // Modifier Visitors
1663        //
1664
1665        /**
1666         * Visit nodes to elide module-specific modifiers.
1667         *
1668         * @param node The node to visit.
1669         */
1670        function modifierVisitor(node: Node): VisitResult<Node> {
1671            switch (node.kind) {
1672                case SyntaxKind.ExportKeyword:
1673                case SyntaxKind.DefaultKeyword:
1674                    return undefined;
1675            }
1676            return node;
1677        }
1678
1679        //
1680        // Emit Notification
1681        //
1682
1683        /**
1684         * Hook for node emit notifications.
1685         *
1686         * @param hint A hint as to the intended usage of the node.
1687         * @param node The node to emit.
1688         * @param emitCallback A callback used to emit the node in the printer.
1689         */
1690        function onEmitNode(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void {
1691            if (node.kind === SyntaxKind.SourceFile) {
1692                const id = getOriginalNodeId(node);
1693                currentSourceFile = node as SourceFile;
1694                moduleInfo = moduleInfoMap[id];
1695                exportFunction = exportFunctionsMap[id];
1696                noSubstitution = noSubstitutionMap[id];
1697                contextObject = contextObjectMap[id];
1698
1699                if (noSubstitution) {
1700                    delete noSubstitutionMap[id];
1701                }
1702
1703                previousOnEmitNode(hint, node, emitCallback);
1704
1705                currentSourceFile = undefined!;
1706                moduleInfo = undefined!;
1707                exportFunction = undefined!;
1708                contextObject = undefined!;
1709                noSubstitution = undefined;
1710            }
1711            else {
1712                previousOnEmitNode(hint, node, emitCallback);
1713            }
1714        }
1715
1716        //
1717        // Substitutions
1718        //
1719
1720        /**
1721         * Hooks node substitutions.
1722         *
1723         * @param hint A hint as to the intended usage of the node.
1724         * @param node The node to substitute.
1725         */
1726        function onSubstituteNode(hint: EmitHint, node: Node) {
1727            node = previousOnSubstituteNode(hint, node);
1728            if (isSubstitutionPrevented(node)) {
1729                return node;
1730            }
1731
1732            if (hint === EmitHint.Expression) {
1733                return substituteExpression(node as Expression);
1734            }
1735            else if (hint === EmitHint.Unspecified) {
1736                return substituteUnspecified(node);
1737            }
1738
1739            return node;
1740        }
1741
1742        /**
1743         * Substitute the node, if necessary.
1744         *
1745         * @param node The node to substitute.
1746         */
1747        function substituteUnspecified(node: Node) {
1748            switch (node.kind) {
1749                case SyntaxKind.ShorthandPropertyAssignment:
1750                    return substituteShorthandPropertyAssignment(node as ShorthandPropertyAssignment);
1751            }
1752            return node;
1753        }
1754
1755        /**
1756         * Substitution for a ShorthandPropertyAssignment whose name that may contain an imported or exported symbol.
1757         *
1758         * @param node The node to substitute.
1759         */
1760        function substituteShorthandPropertyAssignment(node: ShorthandPropertyAssignment) {
1761            const name = node.name;
1762            if (!isGeneratedIdentifier(name) && !isLocalName(name)) {
1763                const importDeclaration = resolver.getReferencedImportDeclaration(name);
1764                if (importDeclaration) {
1765                    if (isImportClause(importDeclaration)) {
1766                        return setTextRange(
1767                            factory.createPropertyAssignment(
1768                                factory.cloneNode(name),
1769                                factory.createPropertyAccessExpression(
1770                                    factory.getGeneratedNameForNode(importDeclaration.parent),
1771                                    factory.createIdentifier("default")
1772                                )
1773                            ),
1774                            /*location*/ node
1775                        );
1776                    }
1777                    else if (isImportSpecifier(importDeclaration)) {
1778                        return setTextRange(
1779                            factory.createPropertyAssignment(
1780                                factory.cloneNode(name),
1781                                factory.createPropertyAccessExpression(
1782                                    factory.getGeneratedNameForNode(importDeclaration.parent?.parent?.parent || importDeclaration),
1783                                    factory.cloneNode(importDeclaration.propertyName || importDeclaration.name)
1784                                ),
1785                            ),
1786                            /*location*/ node
1787                        );
1788                    }
1789                }
1790            }
1791            return node;
1792        }
1793
1794        /**
1795         * Substitute the expression, if necessary.
1796         *
1797         * @param node The node to substitute.
1798         */
1799        function substituteExpression(node: Expression) {
1800            switch (node.kind) {
1801                case SyntaxKind.Identifier:
1802                    return substituteExpressionIdentifier(node as Identifier);
1803                case SyntaxKind.BinaryExpression:
1804                    return substituteBinaryExpression(node as BinaryExpression);
1805                case SyntaxKind.MetaProperty:
1806                    return substituteMetaProperty(node as MetaProperty);
1807            }
1808
1809            return node;
1810        }
1811
1812        /**
1813         * Substitution for an Identifier expression that may contain an imported or exported symbol.
1814         *
1815         * @param node The node to substitute.
1816         */
1817        function substituteExpressionIdentifier(node: Identifier): Expression {
1818            if (getEmitFlags(node) & EmitFlags.HelperName) {
1819                const externalHelpersModuleName = getExternalHelpersModuleName(currentSourceFile);
1820                if (externalHelpersModuleName) {
1821                    return factory.createPropertyAccessExpression(externalHelpersModuleName, node);
1822                }
1823
1824                return node;
1825            }
1826
1827            // When we see an identifier in an expression position that
1828            // points to an imported symbol, we should substitute a qualified
1829            // reference to the imported symbol if one is needed.
1830            //
1831            // - We do not substitute generated identifiers for any reason.
1832            // - We do not substitute identifiers tagged with the LocalName flag.
1833            if (!isGeneratedIdentifier(node) && !isLocalName(node)) {
1834                const importDeclaration = resolver.getReferencedImportDeclaration(node);
1835                if (importDeclaration) {
1836                    if (isImportClause(importDeclaration)) {
1837                        return setTextRange(
1838                            factory.createPropertyAccessExpression(
1839                                factory.getGeneratedNameForNode(importDeclaration.parent),
1840                                factory.createIdentifier("default")
1841                            ),
1842                            /*location*/ node
1843                        );
1844                    }
1845                    else if (isImportSpecifier(importDeclaration)) {
1846                        return setTextRange(
1847                            factory.createPropertyAccessExpression(
1848                                factory.getGeneratedNameForNode(importDeclaration.parent?.parent?.parent || importDeclaration),
1849                                factory.cloneNode(importDeclaration.propertyName || importDeclaration.name)
1850                            ),
1851                            /*location*/ node
1852                        );
1853                    }
1854                }
1855            }
1856
1857            return node;
1858        }
1859
1860        /**
1861         * Substitution for a BinaryExpression that may contain an imported or exported symbol.
1862         *
1863         * @param node The node to substitute.
1864         */
1865        function substituteBinaryExpression(node: BinaryExpression): Expression {
1866            // When we see an assignment expression whose left-hand side is an exported symbol,
1867            // we should ensure all exports of that symbol are updated with the correct value.
1868            //
1869            // - We do not substitute generated identifiers for any reason.
1870            // - We do not substitute identifiers tagged with the LocalName flag.
1871            // - We do not substitute identifiers that were originally the name of an enum or
1872            //   namespace due to how they are transformed in TypeScript.
1873            // - We only substitute identifiers that are exported at the top level.
1874            if (isAssignmentOperator(node.operatorToken.kind)
1875                && isIdentifier(node.left)
1876                && !isGeneratedIdentifier(node.left)
1877                && !isLocalName(node.left)
1878                && !isDeclarationNameOfEnumOrNamespace(node.left)) {
1879                const exportedNames = getExports(node.left);
1880                if (exportedNames) {
1881                    // For each additional export of the declaration, apply an export assignment.
1882                    let expression: Expression = node;
1883                    for (const exportName of exportedNames) {
1884                        expression = createExportExpression(exportName, preventSubstitution(expression));
1885                    }
1886
1887                    return expression;
1888                }
1889            }
1890
1891            return node;
1892        }
1893
1894        function substituteMetaProperty(node: MetaProperty) {
1895            if (isImportMeta(node)) {
1896                return factory.createPropertyAccessExpression(contextObject, factory.createIdentifier("meta"));
1897            }
1898            return node;
1899        }
1900
1901        /**
1902         * Gets the exports of a name.
1903         *
1904         * @param name The name.
1905         */
1906        function getExports(name: Identifier) {
1907            let exportedNames: Identifier[] | undefined;
1908            if (!isGeneratedIdentifier(name)) {
1909                const valueDeclaration = resolver.getReferencedImportDeclaration(name)
1910                    || resolver.getReferencedValueDeclaration(name);
1911
1912                if (valueDeclaration) {
1913                    const exportContainer = resolver.getReferencedExportContainer(name, /*prefixLocals*/ false);
1914                    if (exportContainer && exportContainer.kind === SyntaxKind.SourceFile) {
1915                        exportedNames = append(exportedNames, factory.getDeclarationName(valueDeclaration));
1916                    }
1917
1918                    exportedNames = addRange(exportedNames, moduleInfo && moduleInfo.exportedBindings[getOriginalNodeId(valueDeclaration)]);
1919                }
1920            }
1921
1922            return exportedNames;
1923        }
1924
1925        /**
1926         * Prevent substitution of a node for this transformer.
1927         *
1928         * @param node The node which should not be substituted.
1929         */
1930        function preventSubstitution<T extends Node>(node: T): T {
1931            if (noSubstitution === undefined) noSubstitution = [];
1932            noSubstitution[getNodeId(node)] = true;
1933            return node;
1934        }
1935
1936        /**
1937         * Determines whether a node should not be substituted.
1938         *
1939         * @param node The node to test.
1940         */
1941        function isSubstitutionPrevented(node: Node) {
1942            return noSubstitution && node.id && noSubstitution[node.id];
1943        }
1944    }
1945}
1946