391 lines
15 KiB
Go
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)))
|
|
}
|