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