371 lines
13 KiB
Go
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,
|
|
)
|
|
}
|