package moduletransforms import ( "slices" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/binder" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/core" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/printer" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/transformers" ) type ESModuleTransformer struct { transformers.Transformer compilerOptions *core.CompilerOptions resolver binder.ReferenceResolver getEmitModuleFormatOfFile func(file ast.HasFileName) core.ModuleKind currentSourceFile *ast.SourceFile importRequireStatements *importRequireStatements helperNameSubstitutions map[string]*ast.IdentifierNode } type importRequireStatements struct { statements []*ast.Statement requireHelperName *ast.IdentifierNode } func NewESModuleTransformer(opts *transformers.TransformOptions) *transformers.Transformer { compilerOptions := opts.CompilerOptions resolver := opts.Resolver if resolver == nil { resolver = binder.NewReferenceResolver(compilerOptions, binder.ReferenceResolverHooks{}) } tx := &ESModuleTransformer{compilerOptions: compilerOptions, resolver: resolver, getEmitModuleFormatOfFile: opts.GetEmitModuleFormatOfFile} return tx.NewTransformer(tx.visit, opts.Context) } // Visits source elements that are not top-level or top-level nested statements. func (tx *ESModuleTransformer) visit(node *ast.Node) *ast.Node { switch node.Kind { case ast.KindSourceFile: node = tx.visitSourceFile(node.AsSourceFile()) case ast.KindImportDeclaration: node = tx.visitImportDeclaration(node.AsImportDeclaration()) case ast.KindImportEqualsDeclaration: node = tx.visitImportEqualsDeclaration(node.AsImportEqualsDeclaration()) case ast.KindExportAssignment: node = tx.visitExportAssignment(node.AsExportAssignment()) case ast.KindExportDeclaration: node = tx.visitExportDeclaration(node.AsExportDeclaration()) case ast.KindCallExpression: node = tx.visitCallExpression(node.AsCallExpression()) default: node = tx.Visitor().VisitEachChild(node) } return node } func (tx *ESModuleTransformer) visitSourceFile(node *ast.SourceFile) *ast.Node { if node.IsDeclarationFile || !(ast.IsExternalModule(node) || tx.compilerOptions.GetIsolatedModules()) { return node.AsNode() } tx.currentSourceFile = node tx.importRequireStatements = nil result := tx.Visitor().VisitEachChild(node.AsNode()).AsSourceFile() tx.EmitContext().AddEmitHelper(result.AsNode(), tx.EmitContext().ReadEmitHelpers()...) externalHelpersImportDeclaration := createExternalHelpersImportDeclarationIfNeeded(tx.EmitContext(), result, tx.compilerOptions, tx.getEmitModuleFormatOfFile(node), false /*hasExportStarsToExportValues*/, false /*hasImportStar*/, false /*hasImportDefault*/) if externalHelpersImportDeclaration != nil || tx.importRequireStatements != nil { prologue, rest := tx.Factory().SplitStandardPrologue(result.Statements.Nodes) statements := slices.Clone(prologue) if externalHelpersImportDeclaration != nil { statements = append(statements, externalHelpersImportDeclaration) } if tx.importRequireStatements != nil { statements = append(statements, tx.importRequireStatements.statements...) } statements = append(statements, rest...) statementList := tx.Factory().NewNodeList(statements) statementList.Loc = result.Statements.Loc result = tx.Factory().UpdateSourceFile(result, statementList, node.EndOfFileToken).AsSourceFile() } if ast.IsExternalModule(result) && tx.compilerOptions.GetEmitModuleKind() != core.ModuleKindPreserve && !core.Some(result.Statements.Nodes, ast.IsExternalModuleIndicator) { statements := slices.Clone(result.Statements.Nodes) statements = append(statements, createEmptyImports(tx.Factory())) statementList := tx.Factory().NewNodeList(statements) statementList.Loc = result.Statements.Loc result = tx.Factory().UpdateSourceFile(result, statementList, node.EndOfFileToken).AsSourceFile() } tx.importRequireStatements = nil tx.currentSourceFile = nil return result.AsNode() } func (tx *ESModuleTransformer) visitImportDeclaration(node *ast.ImportDeclaration) *ast.Node { if !tx.compilerOptions.RewriteRelativeImportExtensions.IsTrue() { return node.AsNode() } updatedModuleSpecifier := rewriteModuleSpecifier(tx.EmitContext(), node.ModuleSpecifier, tx.compilerOptions) return tx.Factory().UpdateImportDeclaration( node, nil, /*modifiers*/ tx.Visitor().VisitNode(node.ImportClause), updatedModuleSpecifier, tx.Visitor().VisitNode(node.Attributes), ) } func (tx *ESModuleTransformer) visitImportEqualsDeclaration(node *ast.ImportEqualsDeclaration) *ast.Node { // Though an error in es2020 modules, in node-flavor es2020 modules, we can helpfully transform this to a synthetic `require` call // 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` // is available, just because the output is reasonable for a node-like runtime. if tx.compilerOptions.GetEmitModuleKind() < core.ModuleKindNode16 { return nil } if !ast.IsExternalModuleImportEqualsDeclaration(node.AsNode()) { panic("import= for internal module references should be handled in an earlier transformer.") } varStatement := tx.Factory().NewVariableStatement( nil, /*modifiers*/ tx.Factory().NewVariableDeclarationList( ast.NodeFlagsConst, tx.Factory().NewNodeList([]*ast.Node{ tx.Factory().NewVariableDeclaration( node.Name().Clone(tx.Factory()), nil, /*exclamationToken*/ nil, /*type*/ tx.createRequireCall(node.AsNode()), ), }), ), ) tx.EmitContext().SetOriginal(varStatement, node.AsNode()) tx.EmitContext().AssignCommentAndSourceMapRanges(varStatement, node.AsNode()) var statements []*ast.Statement statements = append(statements, varStatement) statements = tx.appendExportsOfImportEqualsDeclaration(statements, node) return transformers.SingleOrMany(statements, tx.Factory()) } func (tx *ESModuleTransformer) appendExportsOfImportEqualsDeclaration(statements []*ast.Statement, node *ast.ImportEqualsDeclaration) []*ast.Statement { if ast.HasSyntacticModifier(node.AsNode(), ast.ModifierFlagsExport) { statements = append(statements, tx.Factory().NewExportDeclaration( nil, /*modifiers*/ false, /*isTypeOnly*/ tx.Factory().NewNamedExports( tx.Factory().NewNodeList([]*ast.Node{ tx.Factory().NewExportSpecifier( false, /*isTypeOnly*/ nil, /*propertyName*/ node.Name().Clone(tx.Factory()), ), }), ), nil, /*moduleSpecifier*/ nil, /*attributes*/ )) } return statements } func (tx *ESModuleTransformer) visitExportAssignment(node *ast.ExportAssignment) *ast.Node { if !node.IsExportEquals { return tx.Visitor().VisitEachChild(node.AsNode()) } if tx.compilerOptions.GetEmitModuleKind() != core.ModuleKindPreserve { // Elide `export=` as it is not legal with --module ES6 return nil } statement := tx.Factory().NewExpressionStatement( tx.Factory().NewAssignmentExpression( tx.Factory().NewPropertyAccessExpression( tx.Factory().NewIdentifier("module"), nil, /*questionDotToken*/ tx.Factory().NewIdentifier("exports"), ast.NodeFlagsNone, ), tx.Visitor().VisitNode(node.Expression), ), ) tx.EmitContext().SetOriginal(statement, node.AsNode()) return statement } func (tx *ESModuleTransformer) visitExportDeclaration(node *ast.ExportDeclaration) *ast.Node { if node.ModuleSpecifier == nil { return node.AsNode() } updatedModuleSpecifier := rewriteModuleSpecifier(tx.EmitContext(), node.ModuleSpecifier, tx.compilerOptions) if tx.compilerOptions.Module > core.ModuleKindES2015 || node.ExportClause == nil || !ast.IsNamespaceExport(node.ExportClause) { // Either ill-formed or don't need to be transformed. return tx.Factory().UpdateExportDeclaration( node, nil, /*modifiers*/ false, /*isTypeOnly*/ node.ExportClause, updatedModuleSpecifier, tx.Visitor().VisitNode(node.Attributes), ) } oldIdentifier := node.ExportClause.Name() synthName := tx.Factory().NewGeneratedNameForNode(oldIdentifier) importDecl := tx.Factory().NewImportDeclaration( nil, /*modifiers*/ tx.Factory().NewImportClause( ast.KindUnknown, /*phaseModifier*/ nil, /*name*/ tx.Factory().NewNamespaceImport(synthName), ), updatedModuleSpecifier, tx.Visitor().VisitNode(node.Attributes), ) tx.EmitContext().SetOriginal(importDecl, node.ExportClause) var exportDecl *ast.Node if ast.IsExportNamespaceAsDefaultDeclaration(node.AsNode()) { exportDecl = tx.Factory().NewExportAssignment(nil /*modifiers*/, false /*isExportEquals*/, nil /*typeNode*/, synthName) } else { exportDecl = tx.Factory().NewExportDeclaration( nil, /*modifiers*/ false, /*isTypeOnly*/ tx.Factory().NewNamedExports( tx.Factory().NewNodeList([]*ast.Node{ tx.Factory().NewExportSpecifier(false /*isTypeOnly*/, synthName, oldIdentifier), }), ), nil, /*moduleSpecifier*/ nil, /*attributes*/ ) } tx.EmitContext().SetOriginal(exportDecl, node.AsNode()) return transformers.SingleOrMany([]*ast.Statement{importDecl, exportDecl}, tx.Factory()) } func (tx *ESModuleTransformer) visitCallExpression(node *ast.CallExpression) *ast.Node { if tx.compilerOptions.RewriteRelativeImportExtensions.IsTrue() { if ast.IsImportCall(node.AsNode()) && len(node.Arguments.Nodes) > 0 || ast.IsInJSFile(node.AsNode()) && ast.IsRequireCall(node.AsNode(), false /*requireStringLiteralLikeArgument*/) { return tx.visitImportOrRequireCall(node) } } return tx.Visitor().VisitEachChild(node.AsNode()) } func (tx *ESModuleTransformer) visitImportOrRequireCall(node *ast.CallExpression) *ast.Node { if len(node.Arguments.Nodes) == 0 { return tx.Visitor().VisitEachChild(node.AsNode()) } expression := tx.Visitor().VisitNode(node.Expression) var argument *ast.Expression if ast.IsStringLiteralLike(node.Arguments.Nodes[0]) { argument = rewriteModuleSpecifier(tx.EmitContext(), node.Arguments.Nodes[0], tx.compilerOptions) } else { argument = tx.Factory().NewRewriteRelativeImportExtensionsHelper(node.Arguments.Nodes[0], tx.compilerOptions.Jsx == core.JsxEmitPreserve) } var arguments []*ast.Expression arguments = append(arguments, argument) rest := core.FirstResult(tx.Visitor().VisitSlice(node.Arguments.Nodes[1:])) arguments = append(arguments, rest...) argumentList := tx.Factory().NewNodeList(arguments) argumentList.Loc = node.Arguments.Loc return tx.Factory().UpdateCallExpression( node, expression, node.QuestionDotToken, nil, /*typeArguments*/ argumentList, ) } func (tx *ESModuleTransformer) createRequireCall(node *ast.Node /*ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration*/) *ast.Expression { moduleName := getExternalModuleNameLiteral(tx.Factory(), node, tx.currentSourceFile, nil /*host*/, nil /*emitResolver*/, tx.compilerOptions) var args []*ast.Expression if moduleName != nil { args = append(args, rewriteModuleSpecifier(tx.EmitContext(), moduleName, tx.compilerOptions)) } if tx.compilerOptions.GetEmitModuleKind() == core.ModuleKindPreserve { return tx.Factory().NewCallExpression( tx.Factory().NewIdentifier("require"), nil, /*questionDotToken*/ nil, /*typeArguments*/ tx.Factory().NewNodeList(args), ast.NodeFlagsNone, ) } if tx.importRequireStatements == nil { createRequireName := tx.Factory().NewUniqueNameEx("_createRequire", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic | printer.GeneratedIdentifierFlagsFileLevel}) importStatement := tx.Factory().NewImportDeclaration( nil, /*modifiers*/ tx.Factory().NewImportClause( ast.KindUnknown, /*phaseModifier*/ nil, /*name*/ tx.Factory().NewNamedImports( tx.Factory().NewNodeList([]*ast.Node{ tx.Factory().NewImportSpecifier( false, /*isTypeOnly*/ tx.Factory().NewIdentifier("createRequire"), createRequireName, ), }), ), ), tx.Factory().NewStringLiteral("module"), nil, /*attributes*/ ) tx.EmitContext().AddEmitFlags(importStatement, printer.EFCustomPrologue) requireHelperName := tx.Factory().NewUniqueNameEx("__require", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic | printer.GeneratedIdentifierFlagsFileLevel}) requireStatement := tx.Factory().NewVariableStatement( nil, /*modifiers*/ tx.Factory().NewVariableDeclarationList( ast.NodeFlagsConst, tx.Factory().NewNodeList([]*ast.Node{ tx.Factory().NewVariableDeclaration( requireHelperName, nil, /*exclamationToken*/ nil, /*type*/ tx.Factory().NewCallExpression( createRequireName.Clone(tx.Factory()), nil, /*questionDotToken*/ nil, /*typeArguments*/ tx.Factory().NewNodeList([]*ast.Expression{ tx.Factory().NewPropertyAccessExpression( tx.Factory().NewMetaProperty(ast.KindImportKeyword, tx.Factory().NewIdentifier("meta")), nil, /*questionDotToken*/ tx.Factory().NewIdentifier("url"), ast.NodeFlagsNone, ), }), ast.NodeFlagsNone, ), ), }), ), ) tx.EmitContext().AddEmitFlags(requireStatement, printer.EFCustomPrologue) tx.importRequireStatements = &importRequireStatements{ statements: []*ast.Statement{importStatement, requireStatement}, requireHelperName: requireHelperName, } } return tx.Factory().NewCallExpression( tx.importRequireStatements.requireHelperName.Clone(tx.Factory()), nil, /*questionDotToken*/ nil, /*typeArguments*/ tx.Factory().NewNodeList(args), ast.NodeFlagsNone, ) }