• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*@internal*/
2namespace ts {
3    export function transformECMAScriptModule(context: TransformationContext) {
4        const {
5            factory,
6            getEmitHelperFactory: emitHelpers,
7        } = context;
8        const compilerOptions = context.getCompilerOptions();
9        const previousOnEmitNode = context.onEmitNode;
10        const previousOnSubstituteNode = context.onSubstituteNode;
11        context.onEmitNode = onEmitNode;
12        context.onSubstituteNode = onSubstituteNode;
13        context.enableEmitNotification(SyntaxKind.SourceFile);
14        context.enableSubstitution(SyntaxKind.Identifier);
15
16        let helperNameSubstitutions: ESMap<string, Identifier> | undefined;
17        return chainBundle(context, transformSourceFile);
18
19        function transformSourceFile(node: SourceFile) {
20            if (node.isDeclarationFile) {
21                return node;
22            }
23
24            if (isExternalModule(node) || compilerOptions.isolatedModules) {
25                const result = updateExternalModule(node);
26                if (!isExternalModule(node) || some(result.statements, isExternalModuleIndicator)) {
27                    return result;
28                }
29                return factory.updateSourceFile(
30                    result,
31                    setTextRange(factory.createNodeArray([...result.statements, createEmptyExports(factory)]), result.statements),
32                );
33            }
34
35            return node;
36        }
37
38        function updateExternalModule(node: SourceFile) {
39            const externalHelpersImportDeclaration = createExternalHelpersImportDeclarationIfNeeded(factory, emitHelpers(), node, compilerOptions);
40            if (externalHelpersImportDeclaration) {
41                const statements: Statement[] = [];
42                const statementOffset = factory.copyPrologue(node.statements, statements);
43                append(statements, externalHelpersImportDeclaration);
44
45                addRange(statements, visitNodes(node.statements, visitor, isStatement, statementOffset));
46                return factory.updateSourceFile(
47                    node,
48                    setTextRange(factory.createNodeArray(statements), node.statements));
49            }
50            else {
51                return visitEachChild(node, visitor, context);
52            }
53        }
54
55        function visitor(node: Node): VisitResult<Node> {
56            switch (node.kind) {
57                case SyntaxKind.ImportEqualsDeclaration:
58                    // Elide `import=` as it is not legal with --module ES6
59                    return undefined;
60                case SyntaxKind.ExportAssignment:
61                    return visitExportAssignment(<ExportAssignment>node);
62                case SyntaxKind.ExportDeclaration:
63                    const exportDecl = (node as ExportDeclaration);
64                    return visitExportDeclaration(exportDecl);
65            }
66
67            return node;
68        }
69
70        function visitExportAssignment(node: ExportAssignment): VisitResult<ExportAssignment> {
71            // Elide `export=` as it is not legal with --module ES6
72            return node.isExportEquals ? undefined : node;
73        }
74
75        function visitExportDeclaration(node: ExportDeclaration) {
76            // `export * as ns` only needs to be transformed in ES2015
77            if (compilerOptions.module !== undefined && compilerOptions.module > ModuleKind.ES2015) {
78                return node;
79            }
80
81            // Either ill-formed or don't need to be tranformed.
82            if (!node.exportClause || !isNamespaceExport(node.exportClause) || !node.moduleSpecifier) {
83                return node;
84            }
85
86            const oldIdentifier = node.exportClause.name;
87            const synthName = factory.getGeneratedNameForNode(oldIdentifier);
88            const importDecl = factory.createImportDeclaration(
89                /*decorators*/ undefined,
90                /*modifiers*/ undefined,
91                factory.createImportClause(
92                    /*isTypeOnly*/ false,
93                    /*name*/ undefined,
94                    factory.createNamespaceImport(
95                        synthName
96                    )
97                ),
98                node.moduleSpecifier,
99            );
100            setOriginalNode(importDecl, node.exportClause);
101
102            const exportDecl = isExportNamespaceAsDefaultDeclaration(node) ? factory.createExportDefault(synthName) : factory.createExportDeclaration(
103                /*decorators*/ undefined,
104                /*modifiers*/ undefined,
105                /*isTypeOnly*/ false,
106                factory.createNamedExports([factory.createExportSpecifier(synthName, oldIdentifier)]),
107            );
108            setOriginalNode(exportDecl, node);
109
110            return [importDecl, exportDecl];
111        }
112
113        //
114        // Emit Notification
115        //
116
117        /**
118         * Hook for node emit.
119         *
120         * @param hint A hint as to the intended usage of the node.
121         * @param node The node to emit.
122         * @param emit A callback used to emit the node in the printer.
123         */
124        function onEmitNode(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void {
125            if (isSourceFile(node)) {
126                if ((isExternalModule(node) || compilerOptions.isolatedModules) && compilerOptions.importHelpers) {
127                    helperNameSubstitutions = new Map<string, Identifier>();
128                }
129                previousOnEmitNode(hint, node, emitCallback);
130                helperNameSubstitutions = undefined;
131            }
132            else {
133                previousOnEmitNode(hint, node, emitCallback);
134            }
135        }
136
137        //
138        // Substitutions
139        //
140
141        /**
142         * Hooks node substitutions.
143         *
144         * @param hint A hint as to the intended usage of the node.
145         * @param node The node to substitute.
146         */
147        function onSubstituteNode(hint: EmitHint, node: Node) {
148            node = previousOnSubstituteNode(hint, node);
149            if (helperNameSubstitutions && isIdentifier(node) && getEmitFlags(node) & EmitFlags.HelperName) {
150                return substituteHelperName(node);
151            }
152
153            return node;
154        }
155
156        function substituteHelperName(node: Identifier): Expression {
157            const name = idText(node);
158            let substitution = helperNameSubstitutions!.get(name);
159            if (!substitution) {
160                helperNameSubstitutions!.set(name, substitution = factory.createUniqueName(name, GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel));
161            }
162            return substitution;
163        }
164    }
165}
166