1import { 2 addRange, append, Bundle, chainBundle, createEmptyExports, createExternalHelpersImportDeclarationIfNeeded, Debug, EmitFlags, 3 EmitHint, ESMap, ExportAssignment, ExportDeclaration, Expression, GeneratedIdentifierFlags, getEmitFlags, 4 getEmitModuleKind, getEmitScriptTarget, getExternalModuleNameLiteral, hasSyntacticModifier, Identifier, idText, 5 ImportDeclaration, ImportEqualsDeclaration, insertStatementsAfterCustomPrologue, 6 isExportNamespaceAsDefaultDeclaration, isExternalModule, isExternalModuleImportEqualsDeclaration, 7 isExternalModuleIndicator, isIdentifier, isNamespaceExport, isOnlyAnnotationsAreExportedOrImported, isSourceFile, isStatement, Map, ModifierFlags, 8 ModuleKind, Node, NodeFlags, ScriptTarget, setOriginalNode, setTextRange, singleOrMany, some, SourceFile, Statement, 9 SyntaxKind, TransformationContext, VariableStatement, visitEachChild, visitNodes, VisitResult, 10} from "../../_namespaces/ts"; 11 12/** @internal */ 13export function transformECMAScriptModule(context: TransformationContext): (x: SourceFile | Bundle) => SourceFile | Bundle { 14 const { 15 factory, 16 getEmitHelperFactory: emitHelpers, 17 } = context; 18 const host = context.getEmitHost(); 19 const resolver = context.getEmitResolver(); 20 const compilerOptions = context.getCompilerOptions(); 21 const languageVersion = getEmitScriptTarget(compilerOptions); 22 const previousOnEmitNode = context.onEmitNode; 23 const previousOnSubstituteNode = context.onSubstituteNode; 24 context.onEmitNode = onEmitNode; 25 context.onSubstituteNode = onSubstituteNode; 26 context.enableEmitNotification(SyntaxKind.SourceFile); 27 context.enableSubstitution(SyntaxKind.Identifier); 28 29 let helperNameSubstitutions: ESMap<string, Identifier> | undefined; 30 let currentSourceFile: SourceFile | undefined; 31 let importRequireStatements: [ImportDeclaration, VariableStatement] | undefined; 32 return chainBundle(context, transformSourceFile); 33 34 function transformSourceFile(node: SourceFile) { 35 if (node.isDeclarationFile) { 36 return node; 37 } 38 39 if (isExternalModule(node) || compilerOptions.isolatedModules) { 40 currentSourceFile = node; 41 importRequireStatements = undefined; 42 let result = updateExternalModule(node); 43 currentSourceFile = undefined; 44 if (importRequireStatements) { 45 result = factory.updateSourceFile( 46 result, 47 setTextRange(factory.createNodeArray(insertStatementsAfterCustomPrologue(result.statements.slice(), importRequireStatements)), result.statements), 48 ); 49 } 50 if (!isExternalModule(node) || some(result.statements, isExternalModuleIndicator)) { 51 return result; 52 } 53 if (isOnlyAnnotationsAreExportedOrImported(result, resolver)) { 54 return result; 55 } 56 return factory.updateSourceFile( 57 result, 58 setTextRange(factory.createNodeArray([...result.statements, createEmptyExports(factory)]), result.statements), 59 ); 60 } 61 62 return node; 63 } 64 65 function updateExternalModule(node: SourceFile) { 66 const externalHelpersImportDeclaration = createExternalHelpersImportDeclarationIfNeeded(factory, emitHelpers(), node, compilerOptions); 67 if (externalHelpersImportDeclaration) { 68 const statements: Statement[] = []; 69 const statementOffset = factory.copyPrologue(node.statements, statements); 70 append(statements, externalHelpersImportDeclaration); 71 72 addRange(statements, visitNodes(node.statements, visitor, isStatement, statementOffset)); 73 return factory.updateSourceFile( 74 node, 75 setTextRange(factory.createNodeArray(statements), node.statements)); 76 } 77 else { 78 return visitEachChild(node, visitor, context); 79 } 80 } 81 82 function visitor(node: Node): VisitResult<Node> { 83 switch (node.kind) { 84 case SyntaxKind.ImportEqualsDeclaration: 85 // Though an error in es2020 modules, in node-flavor es2020 modules, we can helpfully transform this to a synthetic `require` call 86 // To give easy access to a synchronous `require` in node-flavor esm. We do the transform even in scenarios where we error, but `import.meta.url` 87 // is available, just because the output is reasonable for a node-like runtime. 88 return getEmitModuleKind(compilerOptions) >= ModuleKind.Node16 ? visitImportEqualsDeclaration(node as ImportEqualsDeclaration) : undefined; 89 case SyntaxKind.ExportAssignment: 90 return visitExportAssignment(node as ExportAssignment); 91 case SyntaxKind.ExportDeclaration: 92 const exportDecl = (node as ExportDeclaration); 93 return visitExportDeclaration(exportDecl); 94 } 95 96 return node; 97 } 98 99 /** 100 * Creates a `require()` call to import an external module. 101 * 102 * @param importNode The declaration to import. 103 */ 104 function createRequireCall(importNode: ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration) { 105 const moduleName = getExternalModuleNameLiteral(factory, importNode, Debug.checkDefined(currentSourceFile), host, resolver, compilerOptions); 106 const args: Expression[] = []; 107 if (moduleName) { 108 args.push(moduleName); 109 } 110 111 if (!importRequireStatements) { 112 const createRequireName = factory.createUniqueName("_createRequire", GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel); 113 const importStatement = factory.createImportDeclaration( 114 /*modifiers*/ undefined, 115 factory.createImportClause( 116 /*isTypeOnly*/ false, 117 /*name*/ undefined, 118 factory.createNamedImports([ 119 factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("createRequire"), createRequireName) 120 ]) 121 ), 122 factory.createStringLiteral("module") 123 ); 124 const requireHelperName = factory.createUniqueName("__require", GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel); 125 const requireStatement = factory.createVariableStatement( 126 /*modifiers*/ undefined, 127 factory.createVariableDeclarationList( 128 [ 129 factory.createVariableDeclaration( 130 requireHelperName, 131 /*exclamationToken*/ undefined, 132 /*type*/ undefined, 133 factory.createCallExpression(factory.cloneNode(createRequireName), /*typeArguments*/ undefined, [ 134 factory.createPropertyAccessExpression(factory.createMetaProperty(SyntaxKind.ImportKeyword, factory.createIdentifier("meta")), factory.createIdentifier("url")) 135 ]) 136 ) 137 ], 138 /*flags*/ languageVersion >= ScriptTarget.ES2015 ? NodeFlags.Const : NodeFlags.None 139 ) 140 ); 141 importRequireStatements = [importStatement, requireStatement]; 142 143 } 144 145 const name = importRequireStatements[1].declarationList.declarations[0].name; 146 Debug.assertNode(name, isIdentifier); 147 return factory.createCallExpression(factory.cloneNode(name), /*typeArguments*/ undefined, args); 148 } 149 150 /** 151 * Visits an ImportEqualsDeclaration node. 152 * 153 * @param node The node to visit. 154 */ 155 function visitImportEqualsDeclaration(node: ImportEqualsDeclaration): VisitResult<Statement> { 156 Debug.assert(isExternalModuleImportEqualsDeclaration(node), "import= for internal module references should be handled in an earlier transformer."); 157 158 let statements: Statement[] | undefined; 159 statements = append(statements, 160 setOriginalNode( 161 setTextRange( 162 factory.createVariableStatement( 163 /*modifiers*/ undefined, 164 factory.createVariableDeclarationList( 165 [ 166 factory.createVariableDeclaration( 167 factory.cloneNode(node.name), 168 /*exclamationToken*/ undefined, 169 /*type*/ undefined, 170 createRequireCall(node) 171 ) 172 ], 173 /*flags*/ languageVersion >= ScriptTarget.ES2015 ? NodeFlags.Const : NodeFlags.None 174 ) 175 ), 176 node), 177 node 178 ) 179 ); 180 181 statements = appendExportsOfImportEqualsDeclaration(statements, node); 182 183 return singleOrMany(statements); 184 } 185 186 function appendExportsOfImportEqualsDeclaration(statements: Statement[] | undefined, node: ImportEqualsDeclaration) { 187 if (hasSyntacticModifier(node, ModifierFlags.Export)) { 188 statements = append(statements, factory.createExportDeclaration( 189 /*modifiers*/ undefined, 190 node.isTypeOnly, 191 factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, idText(node.name))]) 192 )); 193 } 194 return statements; 195 } 196 197 function visitExportAssignment(node: ExportAssignment): VisitResult<ExportAssignment> { 198 // Elide `export=` as it is not legal with --module ES6 199 return node.isExportEquals ? undefined : node; 200 } 201 202 function visitExportDeclaration(node: ExportDeclaration) { 203 // `export * as ns` only needs to be transformed in ES2015 204 if (compilerOptions.module !== undefined && compilerOptions.module > ModuleKind.ES2015) { 205 return node; 206 } 207 208 // Either ill-formed or don't need to be tranformed. 209 if (!node.exportClause || !isNamespaceExport(node.exportClause) || !node.moduleSpecifier) { 210 return node; 211 } 212 213 const oldIdentifier = node.exportClause.name; 214 const synthName = factory.getGeneratedNameForNode(oldIdentifier); 215 const importDecl = factory.createImportDeclaration( 216 /*modifiers*/ undefined, 217 factory.createImportClause( 218 /*isTypeOnly*/ false, 219 /*name*/ undefined, 220 factory.createNamespaceImport( 221 synthName 222 ) 223 ), 224 node.moduleSpecifier, 225 node.assertClause 226 ); 227 setOriginalNode(importDecl, node.exportClause); 228 229 const exportDecl = isExportNamespaceAsDefaultDeclaration(node) ? factory.createExportDefault(synthName) : factory.createExportDeclaration( 230 /*modifiers*/ undefined, 231 /*isTypeOnly*/ false, 232 factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, synthName, oldIdentifier)]), 233 ); 234 setOriginalNode(exportDecl, node); 235 236 return [importDecl, exportDecl]; 237 } 238 239 // 240 // Emit Notification 241 // 242 243 /** 244 * Hook for node emit. 245 * 246 * @param hint A hint as to the intended usage of the node. 247 * @param node The node to emit. 248 * @param emit A callback used to emit the node in the printer. 249 */ 250 function onEmitNode(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void { 251 if (isSourceFile(node)) { 252 if ((isExternalModule(node) || compilerOptions.isolatedModules) && compilerOptions.importHelpers) { 253 helperNameSubstitutions = new Map<string, Identifier>(); 254 } 255 previousOnEmitNode(hint, node, emitCallback); 256 helperNameSubstitutions = undefined; 257 } 258 else { 259 previousOnEmitNode(hint, node, emitCallback); 260 } 261 } 262 263 // 264 // Substitutions 265 // 266 267 /** 268 * Hooks node substitutions. 269 * 270 * @param hint A hint as to the intended usage of the node. 271 * @param node The node to substitute. 272 */ 273 function onSubstituteNode(hint: EmitHint, node: Node) { 274 node = previousOnSubstituteNode(hint, node); 275 if (helperNameSubstitutions && isIdentifier(node) && getEmitFlags(node) & EmitFlags.HelperName) { 276 return substituteHelperName(node); 277 } 278 279 return node; 280 } 281 282 function substituteHelperName(node: Identifier): Expression { 283 const name = idText(node); 284 let substitution = helperNameSubstitutions!.get(name); 285 if (!substitution) { 286 helperNameSubstitutions!.set(name, substitution = factory.createUniqueName(name, GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel)); 287 } 288 return substitution; 289 } 290} 291