vendor tsgo
This commit is contained in:
13
tools/tsgo/internal/modulespecifiers/compare.go
Normal file
13
tools/tsgo/internal/modulespecifiers/compare.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package modulespecifiers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func CountPathComponents(path string) int {
|
||||
initial := 0
|
||||
if strings.HasPrefix(path, "./") {
|
||||
initial = 2
|
||||
}
|
||||
return strings.Count(path[initial:], "/")
|
||||
}
|
||||
250
tools/tsgo/internal/modulespecifiers/preferences.go
Normal file
250
tools/tsgo/internal/modulespecifiers/preferences.go
Normal file
@@ -0,0 +1,250 @@
|
||||
package modulespecifiers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/debug"
|
||||
"github.com/microsoft/typescript-go/internal/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 GetAllowedEndingsInPreferredOrder(
|
||||
prefs UserPreferences,
|
||||
host ModuleSpecifierGenerationHost,
|
||||
compilerOptions *core.CompilerOptions,
|
||||
importingSourceFile SourceFileForSpecifierGeneration,
|
||||
oldImportSpecifier string,
|
||||
syntaxImpliedNodeFormat core.ResolutionMode,
|
||||
) []ModuleSpecifierEnding {
|
||||
preferredEnding := getPreferredEnding(
|
||||
prefs,
|
||||
host,
|
||||
compilerOptions,
|
||||
importingSourceFile,
|
||||
oldImportSpecifier,
|
||||
core.ResolutionModeNone,
|
||||
)
|
||||
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}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
getAllowedEndingsInPreferredOrder := func(syntaxImpliedNodeFormat core.ResolutionMode) []ModuleSpecifierEnding {
|
||||
return GetAllowedEndingsInPreferredOrder(
|
||||
prefs,
|
||||
host,
|
||||
compilerOptions,
|
||||
importingSourceFile,
|
||||
oldImportSpecifier,
|
||||
syntaxImpliedNodeFormat,
|
||||
)
|
||||
}
|
||||
|
||||
return ModuleSpecifierPreferences{
|
||||
excludeRegexes: excludes,
|
||||
relativePreference: relativePreference,
|
||||
getAllowedEndingsInPreferredOrder: getAllowedEndingsInPreferredOrder,
|
||||
}
|
||||
}
|
||||
1397
tools/tsgo/internal/modulespecifiers/specifiers.go
Normal file
1397
tools/tsgo/internal/modulespecifiers/specifiers.go
Normal file
File diff suppressed because it is too large
Load Diff
342
tools/tsgo/internal/modulespecifiers/specifiers_test.go
Normal file
342
tools/tsgo/internal/modulespecifiers/specifiers_test.go
Normal file
@@ -0,0 +1,342 @@
|
||||
package modulespecifiers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/module"
|
||||
"github.com/microsoft/typescript-go/internal/packagejson"
|
||||
"github.com/microsoft/typescript-go/internal/symlinks"
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
)
|
||||
|
||||
// Mock host for testing
|
||||
type mockModuleSpecifierGenerationHost struct {
|
||||
currentDir string
|
||||
useCaseSensitiveFileNames bool
|
||||
symlinkCache *symlinks.KnownSymlinks
|
||||
}
|
||||
|
||||
func (h *mockModuleSpecifierGenerationHost) GetCurrentDirectory() string {
|
||||
return h.currentDir
|
||||
}
|
||||
|
||||
func (h *mockModuleSpecifierGenerationHost) UseCaseSensitiveFileNames() bool {
|
||||
return h.useCaseSensitiveFileNames
|
||||
}
|
||||
|
||||
func (h *mockModuleSpecifierGenerationHost) GetSymlinkCache() *symlinks.KnownSymlinks {
|
||||
return h.symlinkCache
|
||||
}
|
||||
|
||||
func (h *mockModuleSpecifierGenerationHost) ResolveModuleName(moduleName string, containingFile string, resolutionMode core.ResolutionMode) *module.ResolvedModule {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *mockModuleSpecifierGenerationHost) GetGlobalTypingsCacheLocation() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (h *mockModuleSpecifierGenerationHost) CommonSourceDirectory() string {
|
||||
return h.currentDir
|
||||
}
|
||||
|
||||
func (h *mockModuleSpecifierGenerationHost) GetProjectReferenceFromSource(path tspath.Path) *tsoptions.SourceOutputAndProjectReference {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *mockModuleSpecifierGenerationHost) GetRedirectTargets(path tspath.Path) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *mockModuleSpecifierGenerationHost) GetSourceOfProjectReferenceIfOutputIncluded(file ast.HasFileName) string {
|
||||
return file.FileName()
|
||||
}
|
||||
|
||||
func (h *mockModuleSpecifierGenerationHost) FileExists(path string) bool {
|
||||
return true // Mock implementation
|
||||
}
|
||||
|
||||
func (h *mockModuleSpecifierGenerationHost) GetNearestAncestorDirectoryWithPackageJson(dirname string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (h *mockModuleSpecifierGenerationHost) GetPackageJsonInfo(pkgJsonPath string) *packagejson.InfoCacheEntry {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *mockModuleSpecifierGenerationHost) GetDefaultResolutionModeForFile(file ast.HasFileName) core.ResolutionMode {
|
||||
return core.ResolutionModeNone
|
||||
}
|
||||
|
||||
func (h *mockModuleSpecifierGenerationHost) GetResolvedModuleFromModuleSpecifier(file ast.HasFileName, moduleSpecifier *ast.StringLiteralLike) *module.ResolvedModule {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *mockModuleSpecifierGenerationHost) GetModeForUsageLocation(file ast.HasFileName, moduleSpecifier *ast.StringLiteralLike) core.ResolutionMode {
|
||||
return core.ResolutionModeNone
|
||||
}
|
||||
|
||||
func TestGetEachFileNameOfModule(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
importingFile string
|
||||
importedFile string
|
||||
preferSymlinks bool
|
||||
expectedCount int
|
||||
expectedPaths []string
|
||||
}{
|
||||
{
|
||||
name: "basic file path",
|
||||
importingFile: "/project/src/main.ts",
|
||||
importedFile: "/project/lib/utils.ts",
|
||||
preferSymlinks: false,
|
||||
expectedCount: 1,
|
||||
expectedPaths: []string{"/project/lib/utils.ts"},
|
||||
},
|
||||
{
|
||||
name: "symlink preference false",
|
||||
importingFile: "/project/src/main.ts",
|
||||
importedFile: "/project/lib/utils.ts",
|
||||
preferSymlinks: false,
|
||||
expectedCount: 1,
|
||||
},
|
||||
{
|
||||
name: "symlink preference true",
|
||||
importingFile: "/project/src/main.ts",
|
||||
importedFile: "/project/lib/utils.ts",
|
||||
preferSymlinks: true,
|
||||
expectedCount: 1,
|
||||
},
|
||||
{
|
||||
name: "ignored path with no alternatives",
|
||||
importingFile: "/project/src/main.ts",
|
||||
importedFile: "/project/node_modules/.pnpm/file.ts",
|
||||
preferSymlinks: false,
|
||||
expectedCount: 1, // Should return 1 because there's no better option (all paths are ignored)
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
host := &mockModuleSpecifierGenerationHost{
|
||||
currentDir: "/project",
|
||||
useCaseSensitiveFileNames: true,
|
||||
symlinkCache: symlinks.NewKnownSymlink("/project", true),
|
||||
}
|
||||
|
||||
result := GetEachFileNameOfModule(tt.importingFile, tt.importedFile, host, tt.preferSymlinks)
|
||||
|
||||
if len(result) != tt.expectedCount {
|
||||
t.Errorf("Expected %d paths, got %d", tt.expectedCount, len(result))
|
||||
}
|
||||
|
||||
if tt.expectedPaths != nil {
|
||||
for i, expectedPath := range tt.expectedPaths {
|
||||
if i >= len(result) {
|
||||
t.Errorf("Expected path %d: %s, but result has only %d paths", i, expectedPath, len(result))
|
||||
continue
|
||||
}
|
||||
if result[i].FileName != expectedPath {
|
||||
t.Errorf("Expected path %d to be %s, got %s", i, expectedPath, result[i].FileName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, path := range result {
|
||||
if path.FileName == "" {
|
||||
t.Errorf("Path %d has empty FileName", i)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEachFileNameOfModuleWithSymlinks(t *testing.T) {
|
||||
t.Parallel()
|
||||
host := &mockModuleSpecifierGenerationHost{
|
||||
currentDir: "/project",
|
||||
useCaseSensitiveFileNames: true,
|
||||
symlinkCache: symlinks.NewKnownSymlink("/project", true),
|
||||
}
|
||||
|
||||
symlinkPath := tspath.ToPath("/project/symlink", "/project", true).EnsureTrailingDirectorySeparator()
|
||||
realDirectory := &symlinks.KnownDirectoryLink{
|
||||
Real: "/real/path/",
|
||||
RealPath: tspath.ToPath("/real/path", "/project", true).EnsureTrailingDirectorySeparator(),
|
||||
}
|
||||
host.symlinkCache.SetDirectory("/project/symlink", symlinkPath, realDirectory)
|
||||
|
||||
result := GetEachFileNameOfModule("/project/src/main.ts", "/real/path/file.ts", host, true)
|
||||
|
||||
// Should find the symlink path
|
||||
found := false
|
||||
for _, path := range result {
|
||||
if path.FileName == "/project/symlink/file.ts" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Error("Expected to find symlink path /project/symlink/file.ts")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsNodeModules(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "contains node_modules",
|
||||
path: "/project/node_modules/lodash/index.js",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "does not contain node_modules",
|
||||
path: "/project/src/utils.ts",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "node_modules in middle",
|
||||
path: "/project/packages/node_modules/pkg/file.js",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "empty path",
|
||||
path: "",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
result := ContainsNodeModules(tt.path)
|
||||
if result != tt.expected {
|
||||
t.Errorf("ContainsNodeModules(%q) = %v, expected %v", tt.path, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsIgnoredPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "ignored path",
|
||||
path: "/project/node_modules/.pnpm/file.ts",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "not ignored path",
|
||||
path: "/project/src/file.ts",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
result := containsIgnoredPath(tt.path)
|
||||
if result != tt.expected {
|
||||
t.Errorf("containsIgnoredPath(%q) = %v, expected %v", tt.path, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTryGetRealFileNameForNonJSDeclarationFileName(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
fileName string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "json declaration file",
|
||||
fileName: "/project/foo.d.json.ts",
|
||||
expected: "/project/foo.json",
|
||||
},
|
||||
{
|
||||
name: "multi-dot source extension declaration file",
|
||||
fileName: "/project/foo.module.d.css.ts",
|
||||
expected: "/project/foo.module.css",
|
||||
},
|
||||
{
|
||||
name: "plain dts file ignored",
|
||||
fileName: "/project/foo.d.ts",
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := TryGetRealFileNameForNonJSDeclarationFileName(tt.fileName); got != tt.expected {
|
||||
t.Errorf("TryGetRealFileNameForNonJSDeclarationFileName(%q) = %q, expected %q", tt.fileName, got, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTryGetModuleNameFromExportsOrImports(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("with exports pattern", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
targetFilePath string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "match",
|
||||
targetFilePath: "/pkg/src/things/thing1/index.ts",
|
||||
expected: "./src/things/thing1",
|
||||
},
|
||||
{
|
||||
name: "mismatch with matching leading and trailing strings",
|
||||
targetFilePath: "/pkg/src/things/index.ts",
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
result := tryGetModuleNameFromExportsOrImports(
|
||||
&core.CompilerOptions{},
|
||||
&mockModuleSpecifierGenerationHost{},
|
||||
tt.targetFilePath,
|
||||
"/pkg",
|
||||
"./src/things/*",
|
||||
packagejson.ExportsOrImports{
|
||||
JSONValue: packagejson.JSONValue{
|
||||
Type: packagejson.JSONValueTypeString,
|
||||
Value: "./src/things/*/index.js",
|
||||
},
|
||||
},
|
||||
[]string{},
|
||||
MatchingModePattern,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
if result != tt.expected {
|
||||
t.Errorf("tryGetModuleNameFromExportsOrImports(targetFilePath = %q) = %v, expected %v", tt.targetFilePath, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
118
tools/tsgo/internal/modulespecifiers/types.go
Normal file
118
tools/tsgo/internal/modulespecifiers/types.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package modulespecifiers
|
||||
|
||||
import (
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/module"
|
||||
"github.com/microsoft/typescript-go/internal/packagejson"
|
||||
"github.com/microsoft/typescript-go/internal/symlinks"
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
)
|
||||
|
||||
type SourceFileForSpecifierGeneration interface {
|
||||
Path() tspath.Path
|
||||
FileName() string
|
||||
Imports() []*ast.StringLiteralLike
|
||||
IsJS() bool
|
||||
}
|
||||
|
||||
type CheckerShape interface {
|
||||
GetSymbolAtLocation(node *ast.Node) *ast.Symbol
|
||||
GetAliasedSymbol(symbol *ast.Symbol) *ast.Symbol
|
||||
}
|
||||
|
||||
type ResultKind uint8
|
||||
|
||||
const (
|
||||
ResultKindNone ResultKind = iota
|
||||
ResultKindNodeModules
|
||||
ResultKindPaths
|
||||
ResultKindRedirect
|
||||
ResultKindRelative
|
||||
ResultKindAmbient
|
||||
)
|
||||
|
||||
type ModulePath struct {
|
||||
FileName string
|
||||
IsInNodeModules bool
|
||||
IsRedirect bool
|
||||
}
|
||||
|
||||
type ModuleSpecifierGenerationHost interface {
|
||||
// GetModuleResolutionCache() any // !!! TODO: adapt new resolution cache model
|
||||
GetSymlinkCache() *symlinks.KnownSymlinks
|
||||
// GetFileIncludeReasons() any // !!! TODO: adapt new resolution cache model
|
||||
CommonSourceDirectory() string
|
||||
GetGlobalTypingsCacheLocation() string
|
||||
UseCaseSensitiveFileNames() bool
|
||||
GetCurrentDirectory() string
|
||||
|
||||
GetProjectReferenceFromSource(path tspath.Path) *tsoptions.SourceOutputAndProjectReference
|
||||
GetRedirectTargets(path tspath.Path) []string
|
||||
GetSourceOfProjectReferenceIfOutputIncluded(file ast.HasFileName) string
|
||||
|
||||
FileExists(path string) bool
|
||||
|
||||
GetNearestAncestorDirectoryWithPackageJson(dirname string) string
|
||||
GetPackageJsonInfo(pkgJsonPath string) *packagejson.InfoCacheEntry
|
||||
GetDefaultResolutionModeForFile(file ast.HasFileName) core.ResolutionMode
|
||||
GetResolvedModuleFromModuleSpecifier(file ast.HasFileName, moduleSpecifier *ast.StringLiteralLike) *module.ResolvedModule
|
||||
GetModeForUsageLocation(file ast.HasFileName, moduleSpecifier *ast.StringLiteralLike) core.ResolutionMode
|
||||
}
|
||||
|
||||
type ImportModuleSpecifierPreference string
|
||||
|
||||
const (
|
||||
ImportModuleSpecifierPreferenceNone ImportModuleSpecifierPreference = "" // !!!
|
||||
ImportModuleSpecifierPreferenceShortest ImportModuleSpecifierPreference = "shortest"
|
||||
ImportModuleSpecifierPreferenceProjectRelative ImportModuleSpecifierPreference = "project-relative"
|
||||
ImportModuleSpecifierPreferenceRelative ImportModuleSpecifierPreference = "relative"
|
||||
ImportModuleSpecifierPreferenceNonRelative ImportModuleSpecifierPreference = "non-relative"
|
||||
)
|
||||
|
||||
type ImportModuleSpecifierEndingPreference string
|
||||
|
||||
const (
|
||||
ImportModuleSpecifierEndingPreferenceNone ImportModuleSpecifierEndingPreference = "" // !!!
|
||||
ImportModuleSpecifierEndingPreferenceAuto ImportModuleSpecifierEndingPreference = "auto"
|
||||
ImportModuleSpecifierEndingPreferenceMinimal ImportModuleSpecifierEndingPreference = "minimal"
|
||||
ImportModuleSpecifierEndingPreferenceIndex ImportModuleSpecifierEndingPreference = "index"
|
||||
ImportModuleSpecifierEndingPreferenceJs ImportModuleSpecifierEndingPreference = "js"
|
||||
)
|
||||
|
||||
type UserPreferences struct {
|
||||
ImportModuleSpecifierPreference ImportModuleSpecifierPreference
|
||||
ImportModuleSpecifierEnding ImportModuleSpecifierEndingPreference
|
||||
AutoImportSpecifierExcludeRegexes []string
|
||||
}
|
||||
|
||||
type ModuleSpecifierOptions struct {
|
||||
OverrideImportMode core.ResolutionMode
|
||||
}
|
||||
|
||||
type RelativePreferenceKind uint8
|
||||
|
||||
const (
|
||||
RelativePreferenceRelative RelativePreferenceKind = iota
|
||||
RelativePreferenceNonRelative
|
||||
RelativePreferenceShortest
|
||||
RelativePreferenceExternalNonRelative
|
||||
)
|
||||
|
||||
type ModuleSpecifierEnding uint8
|
||||
|
||||
const (
|
||||
ModuleSpecifierEndingMinimal ModuleSpecifierEnding = iota
|
||||
ModuleSpecifierEndingIndex
|
||||
ModuleSpecifierEndingJsExtension
|
||||
ModuleSpecifierEndingTsExtension
|
||||
)
|
||||
|
||||
type MatchingMode uint8
|
||||
|
||||
const (
|
||||
MatchingModeExact MatchingMode = iota
|
||||
MatchingModeDirectory
|
||||
MatchingModePattern
|
||||
)
|
||||
502
tools/tsgo/internal/modulespecifiers/util.go
Normal file
502
tools/tsgo/internal/modulespecifiers/util.go
Normal file
@@ -0,0 +1,502 @@
|
||||
package modulespecifiers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/module"
|
||||
"github.com/microsoft/typescript-go/internal/packagejson"
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
)
|
||||
|
||||
type regexPatternCacheKey struct {
|
||||
pattern string
|
||||
caseInsensitive bool
|
||||
}
|
||||
|
||||
var (
|
||||
regexPatternCacheMu sync.RWMutex
|
||||
regexPatternCache = make(map[regexPatternCacheKey]*regexp.Regexp)
|
||||
)
|
||||
|
||||
func comparePathsByRedirect(a ModulePath, b ModulePath, useCaseSensitiveFileNames bool) int {
|
||||
// Redirects sort first, matching Strada's compareBooleans(b.isRedirect, a.isRedirect).
|
||||
if c := core.CompareBooleans(b.IsRedirect, a.IsRedirect); c != 0 {
|
||||
return c
|
||||
}
|
||||
if c := tspath.CompareNumberOfDirectorySeparators(a.FileName, b.FileName); c != 0 {
|
||||
return c
|
||||
}
|
||||
// Strada relies on Map insertion order to break remaining ties deterministically;
|
||||
// Go maps are unordered, so compare paths to keep the ordering stable.
|
||||
return tspath.ComparePaths(a.FileName, b.FileName, tspath.ComparePathsOptions{UseCaseSensitiveFileNames: useCaseSensitiveFileNames})
|
||||
}
|
||||
|
||||
func PathIsBareSpecifier(path string) bool {
|
||||
return !tspath.PathIsAbsolute(path) && !tspath.PathIsRelative(path)
|
||||
}
|
||||
|
||||
func IsExcludedByRegex(moduleSpecifier string, excludes []string) bool {
|
||||
for _, pattern := range excludes {
|
||||
re := stringToRegex(pattern)
|
||||
if re == nil {
|
||||
continue
|
||||
}
|
||||
if re.MatchString(moduleSpecifier) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func stringToRegex(pattern string) *regexp.Regexp {
|
||||
caseInsensitive := false
|
||||
|
||||
if len(pattern) > 2 && pattern[0] == '/' {
|
||||
lastSlash := strings.LastIndex(pattern, "/")
|
||||
if lastSlash > 0 {
|
||||
hasUnescapedMiddleSlash := false
|
||||
for i := 1; i < lastSlash; i++ {
|
||||
if pattern[i] == '/' && (i == 0 || pattern[i-1] != '\\') {
|
||||
hasUnescapedMiddleSlash = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !hasUnescapedMiddleSlash {
|
||||
flags := pattern[lastSlash+1:]
|
||||
pattern = pattern[1:lastSlash]
|
||||
|
||||
for _, flag := range flags {
|
||||
switch flag {
|
||||
case 'i':
|
||||
caseInsensitive = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
key := regexPatternCacheKey{pattern, caseInsensitive}
|
||||
|
||||
regexPatternCacheMu.RLock()
|
||||
re, ok := regexPatternCache[key]
|
||||
regexPatternCacheMu.RUnlock()
|
||||
if ok {
|
||||
return re
|
||||
}
|
||||
|
||||
regexPatternCacheMu.Lock()
|
||||
defer regexPatternCacheMu.Unlock()
|
||||
|
||||
re, ok = regexPatternCache[key]
|
||||
if ok {
|
||||
return re
|
||||
}
|
||||
|
||||
if len(regexPatternCache) > 1000 {
|
||||
clear(regexPatternCache)
|
||||
}
|
||||
|
||||
pattern = strings.Clone(pattern)
|
||||
key.pattern = pattern
|
||||
|
||||
compilePattern := pattern
|
||||
if caseInsensitive {
|
||||
compilePattern = "(?i:" + pattern + ")"
|
||||
}
|
||||
|
||||
compiled, err := regexp.Compile(compilePattern)
|
||||
if err != nil {
|
||||
regexPatternCache[key] = nil
|
||||
return nil
|
||||
}
|
||||
regexPatternCache[key] = compiled
|
||||
return compiled
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures a path is either absolute (prefixed with `/` or `c:`) or dot-relative (prefixed
|
||||
* with `./` or `../`) so as not to be confused with an unprefixed module name.
|
||||
*
|
||||
* ```ts
|
||||
* ensurePathIsNonModuleName("/path/to/file.ext") === "/path/to/file.ext"
|
||||
* ensurePathIsNonModuleName("./path/to/file.ext") === "./path/to/file.ext"
|
||||
* ensurePathIsNonModuleName("../path/to/file.ext") === "../path/to/file.ext"
|
||||
* ensurePathIsNonModuleName("path/to/file.ext") === "./path/to/file.ext"
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
func ensurePathIsNonModuleName(path string) string {
|
||||
if PathIsBareSpecifier(path) {
|
||||
return "./" + path
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func GetJSExtensionForDeclarationFileExtension(ext string) string {
|
||||
switch ext {
|
||||
case tspath.ExtensionDts:
|
||||
return tspath.ExtensionJs
|
||||
case tspath.ExtensionDmts:
|
||||
return tspath.ExtensionMjs
|
||||
case tspath.ExtensionDcts:
|
||||
return tspath.ExtensionCjs
|
||||
default:
|
||||
// .d.json.ts and the like
|
||||
return ext[len(".d") : len(ext)-len(tspath.ExtensionTs)]
|
||||
}
|
||||
}
|
||||
|
||||
// TryGetRealFileNameForNonJSDeclarationFileName remaps files like `foo.d.json.ts` or
|
||||
// `foo.module.d.css.ts` back to their real non-JS names.
|
||||
func TryGetRealFileNameForNonJSDeclarationFileName(fileName string) string {
|
||||
baseName := tspath.GetBaseFileName(fileName)
|
||||
// Ends with .ts, contains ".d.", and is NOT a standard .d.ts file
|
||||
if !strings.HasSuffix(fileName, tspath.ExtensionTs) ||
|
||||
!strings.Contains(baseName, ".d.") ||
|
||||
strings.HasSuffix(baseName, tspath.ExtensionDts) {
|
||||
return ""
|
||||
}
|
||||
noExtension := tspath.RemoveExtension(fileName, tspath.ExtensionTs)
|
||||
lastDotIndex := strings.LastIndex(noExtension, ".")
|
||||
ext := noExtension[lastDotIndex:]
|
||||
before, _, _ := strings.Cut(noExtension, ".d.")
|
||||
return before + ext
|
||||
}
|
||||
|
||||
func getJSExtensionForFile(fileName string, options *core.CompilerOptions) string {
|
||||
result := module.TryGetJSExtensionForFile(fileName, options)
|
||||
if len(result) == 0 {
|
||||
panic(fmt.Sprintf("Extension %s is unsupported:: FileName:: %s", extensionFromPath(fileName), fileName))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the extension from a path.
|
||||
* Path must have a valid extension.
|
||||
*/
|
||||
func extensionFromPath(path string) string {
|
||||
ext := tspath.TryGetExtensionFromPath(path)
|
||||
if len(ext) == 0 {
|
||||
panic(fmt.Sprintf("File %s has unknown extension.", path))
|
||||
}
|
||||
return ext
|
||||
}
|
||||
|
||||
func tryGetAnyFileFromPath(host ModuleSpecifierGenerationHost, path string) bool {
|
||||
// !!! TODO: shouldn't this use readdir instead of fileexists for perf?
|
||||
// We check all js, `node` and `json` extensions in addition to TS, since node module resolution would also choose those over the directory
|
||||
extGroups := tsoptions.GetSupportedExtensions(
|
||||
&core.CompilerOptions{
|
||||
AllowJs: core.TSTrue,
|
||||
},
|
||||
[]tsoptions.FileExtensionInfo{
|
||||
{
|
||||
Extension: "node",
|
||||
IsMixedContent: false,
|
||||
ScriptKind: core.ScriptKindExternal,
|
||||
},
|
||||
{
|
||||
Extension: "json",
|
||||
IsMixedContent: false,
|
||||
ScriptKind: core.ScriptKindJSON,
|
||||
},
|
||||
},
|
||||
)
|
||||
for _, exts := range extGroups {
|
||||
for _, e := range exts {
|
||||
fullPath := path + e
|
||||
if host.FileExists(tspath.GetNormalizedAbsolutePath(fullPath, host.GetCurrentDirectory())) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getPathsRelativeToRootDirs(path string, rootDirs []string, useCaseSensitiveFileNames bool) []string {
|
||||
var results []string
|
||||
for _, rootDir := range rootDirs {
|
||||
relativePath := getRelativePathIfInSameVolume(path, rootDir, useCaseSensitiveFileNames)
|
||||
if !isPathRelativeToParent(relativePath) {
|
||||
results = append(results, relativePath)
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func isPathRelativeToParent(path string) bool {
|
||||
return strings.HasPrefix(path, "..")
|
||||
}
|
||||
|
||||
func getRelativePathIfInSameVolume(path string, directoryPath string, useCaseSensitiveFileNames bool) string {
|
||||
relativePath := tspath.GetRelativePathToDirectoryOrUrl(directoryPath, path, false, tspath.ComparePathsOptions{
|
||||
UseCaseSensitiveFileNames: useCaseSensitiveFileNames,
|
||||
CurrentDirectory: directoryPath,
|
||||
})
|
||||
if tspath.IsRootedDiskPath(relativePath) {
|
||||
return ""
|
||||
}
|
||||
return relativePath
|
||||
}
|
||||
|
||||
func packageJsonPathsAreEqual(a string, b string, options tspath.ComparePathsOptions) bool {
|
||||
if a == b {
|
||||
return true
|
||||
}
|
||||
if len(a) == 0 || len(b) == 0 {
|
||||
return false
|
||||
}
|
||||
return tspath.ComparePaths(a, b, options) == 0
|
||||
}
|
||||
|
||||
func prefersTsExtension(allowedEndings []ModuleSpecifierEnding) bool {
|
||||
jsPriority := slices.Index(allowedEndings, ModuleSpecifierEndingJsExtension)
|
||||
tsPriority := slices.Index(allowedEndings, ModuleSpecifierEndingTsExtension)
|
||||
if tsPriority > -1 {
|
||||
return tsPriority < jsPriority
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func replaceFirstStar(s string, replacement string) string {
|
||||
return strings.Replace(s, "*", replacement, 1)
|
||||
}
|
||||
|
||||
type NodeModulePathParts struct {
|
||||
TopLevelNodeModulesIndex int
|
||||
TopLevelPackageNameIndex int
|
||||
PackageRootIndex int
|
||||
FileNameIndex int
|
||||
}
|
||||
|
||||
type nodeModulesPathParseState uint8
|
||||
|
||||
const (
|
||||
nodeModulesPathParseStateBeforeNodeModules nodeModulesPathParseState = iota
|
||||
nodeModulesPathParseStateNodeModules
|
||||
nodeModulesPathParseStateScope
|
||||
nodeModulesPathParseStatePackageContent
|
||||
)
|
||||
|
||||
func GetNodeModulePathParts(fullPath string) *NodeModulePathParts {
|
||||
// If fullPath can't be valid module file within node_modules, returns undefined.
|
||||
// Example of expected pattern: /base/path/node_modules/[@scope/otherpackage/@otherscope/node_modules/]package/[subdirectory/]file.js
|
||||
// Returns indices: ^ ^ ^ ^
|
||||
|
||||
topLevelNodeModulesIndex := 0
|
||||
topLevelPackageNameIndex := 0
|
||||
packageRootIndex := 0
|
||||
fileNameIndex := 0
|
||||
|
||||
partStart := 0
|
||||
partEnd := 0
|
||||
state := nodeModulesPathParseStateBeforeNodeModules
|
||||
|
||||
for partEnd >= 0 {
|
||||
partStart = partEnd
|
||||
partEnd = core.IndexAfter(fullPath, "/", partStart+1)
|
||||
switch state {
|
||||
case nodeModulesPathParseStateBeforeNodeModules:
|
||||
if strings.Index(fullPath[partStart:], "/node_modules/") == 0 {
|
||||
topLevelNodeModulesIndex = partStart
|
||||
topLevelPackageNameIndex = partEnd
|
||||
state = nodeModulesPathParseStateNodeModules
|
||||
}
|
||||
case nodeModulesPathParseStateNodeModules, nodeModulesPathParseStateScope:
|
||||
if state == nodeModulesPathParseStateNodeModules && fullPath[partStart+1] == '@' {
|
||||
state = nodeModulesPathParseStateScope
|
||||
} else {
|
||||
packageRootIndex = partEnd
|
||||
state = nodeModulesPathParseStatePackageContent
|
||||
}
|
||||
case nodeModulesPathParseStatePackageContent:
|
||||
if strings.Index(fullPath[partStart:], "/node_modules/") == 0 {
|
||||
state = nodeModulesPathParseStateNodeModules
|
||||
} else {
|
||||
state = nodeModulesPathParseStatePackageContent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileNameIndex = partStart
|
||||
|
||||
if state > nodeModulesPathParseStateNodeModules {
|
||||
return &NodeModulePathParts{
|
||||
TopLevelNodeModulesIndex: topLevelNodeModulesIndex,
|
||||
TopLevelPackageNameIndex: topLevelPackageNameIndex,
|
||||
PackageRootIndex: packageRootIndex,
|
||||
FileNameIndex: fileNameIndex,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetNodeModulesPackageName(
|
||||
compilerOptions *core.CompilerOptions,
|
||||
importingSourceFile *ast.SourceFile, // !!! | FutureSourceFile
|
||||
nodeModulesFileName string,
|
||||
host ModuleSpecifierGenerationHost,
|
||||
preferences UserPreferences,
|
||||
options ModuleSpecifierOptions,
|
||||
) string {
|
||||
info := getInfo(importingSourceFile.FileName(), host)
|
||||
modulePaths := getAllModulePaths(info, nodeModulesFileName, host, compilerOptions, preferences, options)
|
||||
for _, modulePath := range modulePaths {
|
||||
if result := tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, preferences, true /*packageNameOnly*/, options.OverrideImportMode); len(result) > 0 {
|
||||
return result
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func allKeysStartWithDot(obj *collections.OrderedMap[string, packagejson.ExportsOrImports]) bool {
|
||||
for k := range obj.Keys() {
|
||||
if !strings.HasPrefix(k, ".") {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func GetPackageNameFromDirectory(fileOrDirectoryPath string) string {
|
||||
idx := strings.LastIndex(fileOrDirectoryPath, "/node_modules/")
|
||||
if idx == -1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
basename := fileOrDirectoryPath[idx+len("/node_modules/"):]
|
||||
if basename[0] == '.' {
|
||||
return ""
|
||||
}
|
||||
|
||||
nextSlash := strings.Index(basename, "/")
|
||||
if nextSlash == -1 {
|
||||
return basename
|
||||
}
|
||||
|
||||
if basename[0] != '@' || nextSlash == len(basename)-1 {
|
||||
return basename[:nextSlash]
|
||||
}
|
||||
|
||||
secondSlash := strings.Index(basename[nextSlash+1:], "/")
|
||||
if secondSlash == -1 {
|
||||
return basename
|
||||
}
|
||||
|
||||
return basename[:nextSlash+1+secondSlash]
|
||||
}
|
||||
|
||||
// ProcessEntrypointEnding processes a pre-computed module specifier from a package.json exports
|
||||
// entrypoint according to the entrypoint's Ending type and the user's preferred endings.
|
||||
func ProcessEntrypointEnding(
|
||||
entrypoint *module.ResolvedEntrypoint,
|
||||
prefs UserPreferences,
|
||||
host ModuleSpecifierGenerationHost,
|
||||
options *core.CompilerOptions,
|
||||
importingSourceFile SourceFileForSpecifierGeneration,
|
||||
allowedEndings []ModuleSpecifierEnding,
|
||||
) string {
|
||||
specifier := entrypoint.ModuleSpecifier
|
||||
if entrypoint.Ending == module.EndingFixed {
|
||||
return specifier
|
||||
}
|
||||
|
||||
if len(allowedEndings) == 0 {
|
||||
allowedEndings = GetAllowedEndingsInPreferredOrder(
|
||||
prefs,
|
||||
host,
|
||||
options,
|
||||
importingSourceFile,
|
||||
"",
|
||||
host.GetDefaultResolutionModeForFile(importingSourceFile),
|
||||
)
|
||||
}
|
||||
|
||||
preferredEnding := allowedEndings[0]
|
||||
|
||||
// Handle declaration file extensions
|
||||
dtsExtension := tspath.GetDeclarationFileExtension(specifier)
|
||||
if dtsExtension != "" {
|
||||
switch preferredEnding {
|
||||
case ModuleSpecifierEndingTsExtension, ModuleSpecifierEndingJsExtension:
|
||||
// Map .d.ts -> .js, .d.mts -> .mjs, .d.cts -> .cjs
|
||||
jsExtension := GetJSExtensionForDeclarationFileExtension(dtsExtension)
|
||||
return tspath.ChangeAnyExtension(specifier, jsExtension, []string{dtsExtension}, false)
|
||||
case ModuleSpecifierEndingMinimal, ModuleSpecifierEndingIndex:
|
||||
if entrypoint.Ending == module.EndingChangeable {
|
||||
// .d.mts/.d.cts must keep an extension; rewrite to .mjs/.cjs instead of dropping
|
||||
if dtsExtension == tspath.ExtensionDts {
|
||||
specifier = tspath.RemoveExtension(specifier, dtsExtension)
|
||||
if preferredEnding == ModuleSpecifierEndingMinimal {
|
||||
specifier = strings.TrimSuffix(specifier, "/index")
|
||||
}
|
||||
return specifier
|
||||
}
|
||||
jsExtension := GetJSExtensionForDeclarationFileExtension(dtsExtension)
|
||||
return tspath.ChangeAnyExtension(specifier, jsExtension, []string{dtsExtension}, false)
|
||||
}
|
||||
// EndingExtensionChangeable - can only change extension, not remove it
|
||||
jsExtension := GetJSExtensionForDeclarationFileExtension(dtsExtension)
|
||||
return tspath.ChangeAnyExtension(specifier, jsExtension, []string{dtsExtension}, false)
|
||||
}
|
||||
return specifier
|
||||
}
|
||||
|
||||
// Handle .ts/.tsx/.mts/.cts extensions
|
||||
if tspath.FileExtensionIsOneOf(specifier, []string{tspath.ExtensionTs, tspath.ExtensionTsx, tspath.ExtensionMts, tspath.ExtensionCts}) {
|
||||
switch preferredEnding {
|
||||
case ModuleSpecifierEndingTsExtension:
|
||||
return specifier
|
||||
case ModuleSpecifierEndingJsExtension:
|
||||
if jsExtension := module.TryGetJSExtensionForFile(specifier, options); jsExtension != "" {
|
||||
return tspath.RemoveFileExtension(specifier) + jsExtension
|
||||
}
|
||||
return specifier
|
||||
case ModuleSpecifierEndingMinimal, ModuleSpecifierEndingIndex:
|
||||
if entrypoint.Ending == module.EndingChangeable {
|
||||
specifier = tspath.RemoveFileExtension(specifier)
|
||||
if preferredEnding == ModuleSpecifierEndingMinimal {
|
||||
specifier = strings.TrimSuffix(specifier, "/index")
|
||||
}
|
||||
return specifier
|
||||
}
|
||||
// EndingExtensionChangeable - can only change extension, not remove it
|
||||
if jsExtension := module.TryGetJSExtensionForFile(specifier, options); jsExtension != "" {
|
||||
return tspath.RemoveFileExtension(specifier) + jsExtension
|
||||
}
|
||||
return specifier
|
||||
}
|
||||
return specifier
|
||||
}
|
||||
|
||||
// Handle .js/.jsx/.mjs/.cjs extensions
|
||||
if tspath.FileExtensionIsOneOf(specifier, []string{tspath.ExtensionJs, tspath.ExtensionJsx, tspath.ExtensionMjs, tspath.ExtensionCjs}) {
|
||||
switch preferredEnding {
|
||||
case ModuleSpecifierEndingTsExtension, ModuleSpecifierEndingJsExtension:
|
||||
return specifier
|
||||
case ModuleSpecifierEndingMinimal, ModuleSpecifierEndingIndex:
|
||||
if entrypoint.Ending == module.EndingChangeable {
|
||||
specifier = tspath.RemoveFileExtension(specifier)
|
||||
if preferredEnding == ModuleSpecifierEndingMinimal {
|
||||
specifier = strings.TrimSuffix(specifier, "/index")
|
||||
}
|
||||
return specifier
|
||||
}
|
||||
// EndingExtensionChangeable - keep the extension
|
||||
return specifier
|
||||
}
|
||||
return specifier
|
||||
}
|
||||
|
||||
// For other extensions (like .json), return as-is
|
||||
return specifier
|
||||
}
|
||||
Reference in New Issue
Block a user