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

391 lines
15 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/collections"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/printer"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/stringutil"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/transformers"
)
type externalModuleInfo struct {
externalImports []*ast.Declaration // ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration. imports and reexports of other external modules
exportSpecifiers collections.MultiMap[string, *ast.ExportSpecifier] // Maps local names to their associated export specifiers (excludes reexports)
exportedBindings collections.MultiMap[*ast.Declaration, *ast.ModuleExportName] // Maps local declarations to their associated export aliases
exportedNames []*ast.ModuleExportName // all exported names in the module, both local and re-exported, excluding the names of locally exported function declarations
exportedFunctions collections.OrderedSet[*ast.FunctionDeclarationNode] // all of the top-level exported function declarations
exportEquals *ast.ExportAssignment // an export=/module.exports= declaration if one was present
hasExportStarsToExportValues bool // whether this module contains export*
}
type externalModuleInfoCollector struct {
sourceFile *ast.SourceFile
compilerOptions *core.CompilerOptions
emitContext *printer.EmitContext
resolver binder.ReferenceResolver
uniqueExports collections.Set[string]
hasExportDefault bool
output *externalModuleInfo
}
func collectExternalModuleInfo(sourceFile *ast.SourceFile, compilerOptions *core.CompilerOptions, emitContext *printer.EmitContext, resolver binder.ReferenceResolver) *externalModuleInfo {
c := externalModuleInfoCollector{
sourceFile: sourceFile,
compilerOptions: compilerOptions,
emitContext: emitContext,
resolver: resolver,
output: &externalModuleInfo{},
}
return c.collect()
}
func (c *externalModuleInfoCollector) collect() *externalModuleInfo {
hasImportStar := false
hasImportDefault := false
for _, node := range c.sourceFile.Statements.Nodes {
switch node.Kind {
case ast.KindImportDeclaration:
// import "mod"
// import x from "mod"
// import * as x from "mod"
// import { x, y } from "mod"
n := node.AsImportDeclaration()
c.addExternalImport(node)
if !hasImportStar && getImportNeedsImportStarHelper(n) {
hasImportStar = true
}
if !hasImportDefault && getImportNeedsImportDefaultHelper(n) {
hasImportDefault = true
}
case ast.KindImportEqualsDeclaration:
n := node.AsImportEqualsDeclaration()
if ast.IsExternalModuleReference(n.ModuleReference) {
// import x = require("mod")
c.addExternalImport(node)
}
case ast.KindExportDeclaration:
n := node.AsExportDeclaration()
if n.ModuleSpecifier != nil {
// export * from "mod"
// export * as ns from "mod"
// export { x, y } from "mod"
c.addExternalImport(node)
if n.ExportClause == nil {
// export * from "mod"
c.output.hasExportStarsToExportValues = true
} else if ast.IsNamedExports(n.ExportClause) {
// export { x, y } from "mod"
c.addExportedNamesForExportDeclaration(n)
if !hasImportDefault {
hasImportDefault = containsDefaultReference(n.ExportClause)
}
} else {
// export * as ns from "mod"
name := n.ExportClause.AsNamespaceExport().Name()
nameText := name.Text()
if c.addUniqueExport(nameText) {
c.addExportedBinding(node, name)
c.addExportedName(name)
}
// we use the same helpers for `export * as ns` as we do for `import * as ns`
hasImportStar = true
}
} else {
// export { x, y }
c.addExportedNamesForExportDeclaration(node.AsExportDeclaration())
}
case ast.KindExportAssignment:
n := node.AsExportAssignment()
if n.IsExportEquals && c.output.exportEquals == nil {
// export = x
c.output.exportEquals = n
}
case ast.KindVariableStatement:
n := node.AsVariableStatement()
if ast.HasSyntacticModifier(node, ast.ModifierFlagsExport) {
for _, decl := range n.DeclarationList.AsVariableDeclarationList().Declarations.Nodes {
c.collectExportedVariableInfo(decl)
}
}
case ast.KindFunctionDeclaration:
n := node.AsFunctionDeclaration()
if ast.HasSyntacticModifier(node, ast.ModifierFlagsExport) {
c.addExportedFunctionDeclaration(n, nil /*name*/, ast.HasSyntacticModifier(node, ast.ModifierFlagsDefault))
}
case ast.KindClassDeclaration:
n := node.AsClassDeclaration()
if ast.HasSyntacticModifier(node, ast.ModifierFlagsExport) {
if ast.HasSyntacticModifier(node, ast.ModifierFlagsDefault) {
// export default class { }
if !c.hasExportDefault {
name := n.Name()
if name == nil {
name = c.emitContext.Factory.NewGeneratedNameForNode(node)
}
c.addExportedBinding(node, name)
c.hasExportDefault = true
}
} else {
// export class x { }
name := n.Name()
if name != nil {
if c.addUniqueExport(name.Text()) {
c.addExportedBinding(node, name)
c.addExportedName(name)
}
}
}
}
}
}
return c.output
}
func (c *externalModuleInfoCollector) addUniqueExport(name string) bool {
if !c.uniqueExports.Has(name) {
c.uniqueExports.Add(name)
return true
}
return false
}
func (c *externalModuleInfoCollector) addExportedBinding(decl *ast.Declaration, name *ast.ModuleExportName) {
c.output.exportedBindings.Add(c.emitContext.MostOriginal(decl), name)
}
func (c *externalModuleInfoCollector) addExternalImport(node *ast.Node /*ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration*/) {
c.output.externalImports = append(c.output.externalImports, node)
}
func (c *externalModuleInfoCollector) addExportedName(name *ast.ModuleExportName) {
c.output.exportedNames = append(c.output.exportedNames, name)
}
func (c *externalModuleInfoCollector) addExportedNamesForExportDeclaration(node *ast.ExportDeclaration) {
for _, specifier := range node.ExportClause.AsNamedExports().Elements.Nodes {
specifierNameText := specifier.Name().Text()
if c.addUniqueExport(specifierNameText) {
name := specifier.PropertyNameOrName()
if name.Kind != ast.KindStringLiteral {
if node.ModuleSpecifier == nil {
c.output.exportSpecifiers.Add(name.Text(), specifier.AsExportSpecifier())
}
decl := c.resolver.GetReferencedImportDeclaration(c.emitContext.MostOriginal(name))
if decl == nil {
decl = c.resolver.GetReferencedValueDeclaration(c.emitContext.MostOriginal(name))
}
if decl != nil {
if decl.Kind == ast.KindFunctionDeclaration {
c.uniqueExports.Delete(specifierNameText)
c.addExportedFunctionDeclaration(decl.AsFunctionDeclaration(), specifier.Name(), ast.ModuleExportNameIsDefault(specifier.Name()))
continue
}
c.addExportedBinding(decl, specifier.Name())
}
}
c.addExportedName(specifier.Name())
}
}
}
func (c *externalModuleInfoCollector) addExportedFunctionDeclaration(node *ast.FunctionDeclaration, name *ast.ModuleExportName, isDefault bool) {
c.output.exportedFunctions.Add(c.emitContext.MostOriginal(node.AsNode()))
if isDefault {
// export default function() { }
// function x() { } + export { x as default };
if !c.hasExportDefault {
if name == nil {
name = c.emitContext.Factory.NewGeneratedNameForNode(node.AsNode())
}
c.addExportedBinding(node.AsNode(), name)
c.hasExportDefault = true
}
} else {
// export function x() { }
// function x() { } + export { x }
if name == nil {
name = node.Name()
}
nameText := name.Text()
if c.addUniqueExport(nameText) {
c.addExportedBinding(node.AsNode(), name)
}
}
}
func (c *externalModuleInfoCollector) collectExportedVariableInfo(decl *ast.Node /*VariableDeclaration | BindingElement*/) {
if ast.IsBindingPattern(decl.Name()) {
for _, element := range decl.Name().AsBindingPattern().Elements.Nodes {
e := element.AsBindingElement()
if e.Name() != nil {
c.collectExportedVariableInfo(element)
}
}
} else if !c.emitContext.HasAutoGenerateInfo(decl.Name()) {
text := decl.Name().Text()
if c.addUniqueExport(text) {
c.addExportedName(decl.Name())
if transformers.IsLocalName(c.emitContext, decl.Name()) {
c.addExportedBinding(decl, decl.Name())
}
}
}
}
const externalHelpersModuleNameText = "tslib"
func createExternalHelpersImportDeclarationIfNeeded(emitContext *printer.EmitContext, sourceFile *ast.SourceFile, compilerOptions *core.CompilerOptions, fileModuleKind core.ModuleKind, hasExportStarsToExportValues bool, hasImportStar bool, hasImportDefault bool) *ast.Node /*ImportDeclaration | ImportEqualsDeclaration*/ {
if compilerOptions.ImportHelpers.IsTrue() && ast.IsEffectiveExternalModule(sourceFile, compilerOptions) {
moduleKind := compilerOptions.GetEmitModuleKind()
helpers := getImportedHelpers(emitContext, sourceFile)
if fileModuleKind == core.ModuleKindCommonJS || fileModuleKind == core.ModuleKindNone && moduleKind == core.ModuleKindCommonJS {
// When we emit to a non-ES module, generate a synthetic `import tslib = require("tslib")` to be further transformed.
externalHelpersModuleName := getOrCreateExternalHelpersModuleNameIfNeeded(emitContext, sourceFile, compilerOptions, helpers, hasExportStarsToExportValues, hasImportStar || hasImportDefault, fileModuleKind)
if externalHelpersModuleName != nil {
externalHelpersImportDeclaration := emitContext.Factory.NewImportEqualsDeclaration(
nil, /*modifiers*/
false, /*isTypeOnly*/
externalHelpersModuleName,
emitContext.Factory.NewExternalModuleReference(emitContext.Factory.NewStringLiteral(externalHelpersModuleNameText)),
)
emitContext.AddEmitFlags(externalHelpersImportDeclaration, printer.EFNeverApplyImportHelper|printer.EFCustomPrologue)
return externalHelpersImportDeclaration
}
} else {
// When we emit as an ES module, generate an `import` declaration that uses named imports for helpers.
// If we cannot determine the implied module kind under `module: preserve` we assume ESM.
var helperNames []string
for _, helper := range helpers {
importName := helper.ImportName
if len(importName) > 0 {
helperNames = core.AppendIfUnique(helperNames, importName)
}
}
if len(helperNames) > 0 {
slices.SortFunc(helperNames, stringutil.CompareStringsCaseSensitive)
// Alias the imports if the names are used somewhere in the file.
// NOTE: We don't need to care about global import collisions as this is a module.
importSpecifiers := core.Map(helperNames, func(name string) *ast.ImportSpecifierNode {
if printer.IsFileLevelUniqueName(sourceFile, name, nil /*hasGlobalName*/) {
return emitContext.Factory.NewImportSpecifier(false /*isTypeOnly*/, nil /*propertyName*/, emitContext.Factory.NewIdentifier(name))
} else {
return emitContext.Factory.NewImportSpecifier(false /*isTypeOnly*/, emitContext.Factory.NewIdentifier(name), emitContext.Factory.NewUnscopedHelperName(name))
}
})
namedBindings := emitContext.Factory.NewNamedImports(emitContext.Factory.NewNodeList(importSpecifiers))
parseNode := emitContext.MostOriginal(sourceFile.AsNode())
emitContext.AddEmitFlags(parseNode, printer.EFExternalHelpers)
externalHelpersImportDeclaration := emitContext.Factory.NewImportDeclaration(
nil, /*modifiers*/
emitContext.Factory.NewImportClause(ast.KindUnknown /*phaseModifier*/, nil /*name*/, namedBindings),
emitContext.Factory.NewStringLiteral(externalHelpersModuleNameText),
nil, /*attributes*/
)
emitContext.AddEmitFlags(externalHelpersImportDeclaration, printer.EFNeverApplyImportHelper|printer.EFCustomPrologue)
return externalHelpersImportDeclaration
}
}
}
return nil
}
func getImportedHelpers(emitContext *printer.EmitContext, sourceFile *ast.SourceFile) []*printer.EmitHelper {
var helpers []*printer.EmitHelper
for _, helper := range emitContext.GetEmitHelpers(sourceFile.AsNode()) {
if !helper.Scoped {
helpers = append(helpers, helper)
}
}
return helpers
}
func getOrCreateExternalHelpersModuleNameIfNeeded(emitContext *printer.EmitContext, node *ast.SourceFile, compilerOptions *core.CompilerOptions, helpers []*printer.EmitHelper, hasExportStarsToExportValues bool, hasImportStarOrImportDefault bool, fileModuleKind core.ModuleKind) *ast.IdentifierNode {
externalHelpersModuleName := emitContext.GetExternalHelpersModuleName(node)
if externalHelpersModuleName != nil {
return externalHelpersModuleName
}
create := len(helpers) > 0 ||
(hasExportStarsToExportValues || compilerOptions.GetESModuleInterop() && hasImportStarOrImportDefault) &&
fileModuleKind < core.ModuleKindSystem
if create {
externalHelpersModuleName = emitContext.Factory.NewUniqueName(externalHelpersModuleNameText)
emitContext.SetExternalHelpersModuleName(node, externalHelpersModuleName)
}
return externalHelpersModuleName
}
func isNamedDefaultReference(e *ast.Node /*ImportSpecifier | ExportSpecifier*/) bool {
return ast.ModuleExportNameIsDefault(e.PropertyNameOrName())
}
func containsDefaultReference(node *ast.Node /*NamedImportBindings | NamedExportBindings*/) bool {
if node == nil {
return false
}
var elements *ast.NodeList
switch {
case ast.IsNamedImports(node):
elements = node.AsNamedImports().Elements
case ast.IsNamedExports(node):
elements = node.AsNamedExports().Elements
default:
return false
}
return core.Some(elements.Nodes, isNamedDefaultReference)
}
func getExportNeedsImportStarHelper(node *ast.ExportDeclaration) bool {
return ast.GetNamespaceDeclarationNode(node.AsNode()) != nil
}
func getImportNeedsImportStarHelper(node *ast.ImportDeclaration) bool {
if ast.GetNamespaceDeclarationNode(node.AsNode()) != nil {
return true
}
if node.ImportClause == nil {
return false
}
bindings := node.ImportClause.AsImportClause().NamedBindings
if bindings == nil {
return false
}
if !ast.IsNamedImports(bindings) {
return false
}
namedImports := bindings.AsNamedImports()
defaultRefCount := 0
for _, binding := range namedImports.Elements.Nodes {
if isNamedDefaultReference(binding) {
defaultRefCount++
}
}
// Import star is required if there's default named refs mixed with non-default refs, or if theres non-default refs and it has a default import
return (defaultRefCount > 0 && defaultRefCount != len(namedImports.Elements.Nodes)) || ((len(namedImports.Elements.Nodes)-defaultRefCount) != 0 && ast.IsDefaultImport(node.AsNode()))
}
func getImportNeedsImportDefaultHelper(node *ast.ImportDeclaration) bool {
// Import default is needed if there's a default import or a default ref and no other refs (meaning an import star helper wasn't requested)
return !getImportNeedsImportStarHelper(node) && (ast.IsDefaultImport(node.AsNode()) || (node.ImportClause != nil &&
ast.IsNamedImports(node.ImportClause.AsImportClause().NamedBindings) &&
containsDefaultReference(node.ImportClause.AsImportClause().NamedBindings)))
}