234 lines
9.2 KiB
Go
234 lines
9.2 KiB
Go
package modulespecifiers
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/debug"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
|
|
)
|
|
|
|
// Program errors validate that `noEmit` or `emitDeclarationOnly` is also set,
|
|
// so this function doesn't check them to avoid propagating errors.
|
|
func shouldAllowImportingTsExtension(compilerOptions *core.CompilerOptions, fromFileName string) bool {
|
|
return compilerOptions.GetAllowImportingTsExtensions() || len(fromFileName) > 0 && tspath.IsDeclarationFileName(fromFileName)
|
|
}
|
|
|
|
func usesExtensionsOnImports(file SourceFileForSpecifierGeneration) bool {
|
|
for _, ref := range file.Imports() {
|
|
text := ref.Text()
|
|
if tspath.PathIsRelative(text) && !tspath.FileExtensionIsOneOf(text, tspath.ExtensionsNotSupportingExtensionlessResolution) {
|
|
return tspath.HasTSFileExtension(text) || tspath.HasJSFileExtension(text)
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func inferPreference(
|
|
resolutionMode core.ResolutionMode,
|
|
sourceFile SourceFileForSpecifierGeneration,
|
|
moduleResolutionIsNodeNext bool,
|
|
) ModuleSpecifierEnding {
|
|
usesJsExtensions := false
|
|
var specifiers []*ast.LiteralLikeNode
|
|
if sourceFile != nil && len(sourceFile.Imports()) > 0 {
|
|
specifiers = sourceFile.Imports()
|
|
} else if sourceFile != nil && sourceFile.IsJS() {
|
|
// !!! TODO: JS support
|
|
// specifiers = core.Map(getRequiresAtTopOfFile(sourceFile), func(d *ast.Node) *ast.Node { return d.arguments[0] })
|
|
}
|
|
|
|
for _, specifier := range specifiers {
|
|
path := specifier.Text()
|
|
if tspath.PathIsRelative(path) {
|
|
// !!! TODO: proper resolutionMode support
|
|
if moduleResolutionIsNodeNext && resolutionMode == core.ResolutionModeCommonJS /* && getModeForUsageLocation(sourceFile!, specifier, compilerOptions) === ModuleKind.ESNext */ {
|
|
// We're trying to decide a preference for a CommonJS module specifier, but looking at an ESM import.
|
|
continue
|
|
}
|
|
if tspath.FileExtensionIsOneOf(path, tspath.ExtensionsNotSupportingExtensionlessResolution) {
|
|
// These extensions are not optional, so do not indicate a preference.
|
|
continue
|
|
}
|
|
if tspath.HasTSFileExtension(path) {
|
|
return ModuleSpecifierEndingTsExtension
|
|
}
|
|
if tspath.HasJSFileExtension(path) {
|
|
usesJsExtensions = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if usesJsExtensions {
|
|
return ModuleSpecifierEndingJsExtension
|
|
}
|
|
return ModuleSpecifierEndingMinimal
|
|
}
|
|
|
|
func getModuleSpecifierEndingPreference(
|
|
pref ImportModuleSpecifierEndingPreference,
|
|
resolutionMode core.ResolutionMode,
|
|
compilerOptions *core.CompilerOptions,
|
|
sourceFile SourceFileForSpecifierGeneration,
|
|
) ModuleSpecifierEnding {
|
|
moduleResolution := compilerOptions.GetModuleResolutionKind()
|
|
moduleResolutionIsNodeNext := core.ModuleResolutionKindNode16 <= moduleResolution && moduleResolution <= core.ModuleResolutionKindNodeNext
|
|
|
|
if pref == ImportModuleSpecifierEndingPreferenceJs || resolutionMode == core.ResolutionModeESM && moduleResolutionIsNodeNext {
|
|
// Extensions are explicitly requested or required. Now choose between .js and .ts.
|
|
if !shouldAllowImportingTsExtension(compilerOptions, "") {
|
|
return ModuleSpecifierEndingJsExtension
|
|
}
|
|
// `allowImportingTsExtensions` is a strong signal, so use .ts unless the file
|
|
// already uses .js extensions and no .ts extensions.
|
|
if inferPreference(resolutionMode, sourceFile, moduleResolutionIsNodeNext) != ModuleSpecifierEndingJsExtension {
|
|
return ModuleSpecifierEndingTsExtension
|
|
}
|
|
return ModuleSpecifierEndingJsExtension
|
|
}
|
|
|
|
if pref == ImportModuleSpecifierEndingPreferenceMinimal {
|
|
return ModuleSpecifierEndingMinimal
|
|
}
|
|
|
|
if pref == ImportModuleSpecifierEndingPreferenceIndex {
|
|
return ModuleSpecifierEndingIndex
|
|
}
|
|
|
|
// No preference was specified.
|
|
// Look at imports and/or requires to guess whether .js, .ts, or extensionless imports are preferred.
|
|
// N.B. that `Index` detection is not supported since it would require file system probing to do
|
|
// accurately, and more importantly, literally nobody wants `Index` and its existence is a mystery.
|
|
if !shouldAllowImportingTsExtension(compilerOptions, "") {
|
|
// If .ts imports are not valid, we only need to see one .js import to go with that.
|
|
if sourceFile != nil && usesExtensionsOnImports(sourceFile) {
|
|
return ModuleSpecifierEndingJsExtension
|
|
}
|
|
return ModuleSpecifierEndingMinimal
|
|
}
|
|
|
|
return inferPreference(resolutionMode, sourceFile, moduleResolutionIsNodeNext)
|
|
}
|
|
|
|
func getPreferredEnding(
|
|
prefs UserPreferences,
|
|
host ModuleSpecifierGenerationHost,
|
|
compilerOptions *core.CompilerOptions,
|
|
importingSourceFile SourceFileForSpecifierGeneration,
|
|
oldImportSpecifier string,
|
|
resolutionMode core.ResolutionMode,
|
|
) ModuleSpecifierEnding {
|
|
if len(oldImportSpecifier) > 0 {
|
|
if tspath.HasJSFileExtension(oldImportSpecifier) {
|
|
return ModuleSpecifierEndingJsExtension
|
|
}
|
|
if strings.HasSuffix(oldImportSpecifier, "/index") {
|
|
return ModuleSpecifierEndingIndex
|
|
}
|
|
}
|
|
if resolutionMode == core.ResolutionModeNone {
|
|
resolutionMode = host.GetDefaultResolutionModeForFile(importingSourceFile)
|
|
}
|
|
return getModuleSpecifierEndingPreference(
|
|
prefs.ImportModuleSpecifierEnding,
|
|
resolutionMode,
|
|
compilerOptions,
|
|
importingSourceFile,
|
|
)
|
|
}
|
|
|
|
type ModuleSpecifierPreferences struct {
|
|
relativePreference RelativePreferenceKind
|
|
getAllowedEndingsInPreferredOrder func(syntaxImpliedNodeFormat core.ResolutionMode) []ModuleSpecifierEnding
|
|
excludeRegexes []string
|
|
}
|
|
|
|
func getModuleSpecifierPreferences(
|
|
prefs UserPreferences,
|
|
host ModuleSpecifierGenerationHost,
|
|
compilerOptions *core.CompilerOptions,
|
|
importingSourceFile SourceFileForSpecifierGeneration,
|
|
oldImportSpecifier string,
|
|
) ModuleSpecifierPreferences {
|
|
excludes := prefs.AutoImportSpecifierExcludeRegexes
|
|
relativePreference := RelativePreferenceShortest
|
|
if len(oldImportSpecifier) > 0 {
|
|
if tspath.IsExternalModuleNameRelative(oldImportSpecifier) {
|
|
relativePreference = RelativePreferenceRelative
|
|
} else {
|
|
relativePreference = RelativePreferenceNonRelative
|
|
}
|
|
} else {
|
|
switch prefs.ImportModuleSpecifierPreference {
|
|
case ImportModuleSpecifierPreferenceRelative:
|
|
relativePreference = RelativePreferenceRelative
|
|
case ImportModuleSpecifierPreferenceNonRelative:
|
|
relativePreference = RelativePreferenceNonRelative
|
|
case ImportModuleSpecifierPreferenceProjectRelative:
|
|
relativePreference = RelativePreferenceExternalNonRelative
|
|
// all others are shortest
|
|
}
|
|
}
|
|
filePreferredEnding := getPreferredEnding(
|
|
prefs,
|
|
host,
|
|
compilerOptions,
|
|
importingSourceFile,
|
|
oldImportSpecifier,
|
|
core.ResolutionModeNone,
|
|
)
|
|
|
|
getAllowedEndingsInPreferredOrder := func(syntaxImpliedNodeFormat core.ResolutionMode) []ModuleSpecifierEnding {
|
|
preferredEnding := filePreferredEnding
|
|
resolutionMode := host.GetDefaultResolutionModeForFile(importingSourceFile)
|
|
if resolutionMode != syntaxImpliedNodeFormat {
|
|
preferredEnding = getPreferredEnding(
|
|
prefs,
|
|
host,
|
|
compilerOptions,
|
|
importingSourceFile,
|
|
oldImportSpecifier,
|
|
syntaxImpliedNodeFormat,
|
|
)
|
|
}
|
|
moduleResolution := compilerOptions.GetModuleResolutionKind()
|
|
moduleResolutionIsNodeNext := core.ModuleResolutionKindNode16 <= moduleResolution && moduleResolution <= core.ModuleResolutionKindNodeNext
|
|
allowImportingTsExtension := shouldAllowImportingTsExtension(compilerOptions, importingSourceFile.FileName())
|
|
if syntaxImpliedNodeFormat == core.ResolutionModeESM && moduleResolutionIsNodeNext {
|
|
if allowImportingTsExtension {
|
|
return []ModuleSpecifierEnding{ModuleSpecifierEndingTsExtension, ModuleSpecifierEndingJsExtension}
|
|
}
|
|
return []ModuleSpecifierEnding{ModuleSpecifierEndingJsExtension}
|
|
}
|
|
switch preferredEnding {
|
|
case ModuleSpecifierEndingJsExtension:
|
|
if allowImportingTsExtension {
|
|
return []ModuleSpecifierEnding{ModuleSpecifierEndingJsExtension, ModuleSpecifierEndingTsExtension, ModuleSpecifierEndingMinimal, ModuleSpecifierEndingIndex}
|
|
}
|
|
return []ModuleSpecifierEnding{ModuleSpecifierEndingJsExtension, ModuleSpecifierEndingMinimal, ModuleSpecifierEndingIndex}
|
|
case ModuleSpecifierEndingTsExtension:
|
|
return []ModuleSpecifierEnding{ModuleSpecifierEndingTsExtension, ModuleSpecifierEndingMinimal, ModuleSpecifierEndingJsExtension, ModuleSpecifierEndingIndex}
|
|
case ModuleSpecifierEndingIndex:
|
|
if allowImportingTsExtension {
|
|
return []ModuleSpecifierEnding{ModuleSpecifierEndingIndex, ModuleSpecifierEndingMinimal, ModuleSpecifierEndingTsExtension, ModuleSpecifierEndingJsExtension}
|
|
}
|
|
return []ModuleSpecifierEnding{ModuleSpecifierEndingIndex, ModuleSpecifierEndingMinimal, ModuleSpecifierEndingJsExtension}
|
|
case ModuleSpecifierEndingMinimal:
|
|
if allowImportingTsExtension {
|
|
return []ModuleSpecifierEnding{ModuleSpecifierEndingMinimal, ModuleSpecifierEndingIndex, ModuleSpecifierEndingTsExtension, ModuleSpecifierEndingJsExtension}
|
|
}
|
|
return []ModuleSpecifierEnding{ModuleSpecifierEndingMinimal, ModuleSpecifierEndingIndex, ModuleSpecifierEndingJsExtension}
|
|
default:
|
|
debug.AssertNever(preferredEnding)
|
|
}
|
|
return []ModuleSpecifierEnding{ModuleSpecifierEndingMinimal}
|
|
}
|
|
|
|
return ModuleSpecifierPreferences{
|
|
excludeRegexes: excludes,
|
|
relativePreference: relativePreference,
|
|
getAllowedEndingsInPreferredOrder: getAllowedEndingsInPreferredOrder,
|
|
}
|
|
}
|