2025-10-15 10:12:44 +03:00

371 lines
13 KiB
Go

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,
)
}