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