From 4712bb4e56f8a32fd657d15fb814a226ea27cf4c Mon Sep 17 00:00:00 2001 From: Egor Aristov Date: Wed, 15 Oct 2025 19:21:16 +0300 Subject: [PATCH] remove unused packages --- .../internal/tsgo/modulespecifiers/compare.go | 13 - .../tsgo/modulespecifiers/preferences.go | 233 --- .../tsgo/modulespecifiers/specifiers.go | 1286 ----------------- .../internal/tsgo/modulespecifiers/types.go | 122 -- kitcom/internal/tsgo/modulespecifiers/util.go | 323 ----- 5 files changed, 1977 deletions(-) delete mode 100644 kitcom/internal/tsgo/modulespecifiers/compare.go delete mode 100644 kitcom/internal/tsgo/modulespecifiers/preferences.go delete mode 100644 kitcom/internal/tsgo/modulespecifiers/specifiers.go delete mode 100644 kitcom/internal/tsgo/modulespecifiers/types.go delete mode 100644 kitcom/internal/tsgo/modulespecifiers/util.go diff --git a/kitcom/internal/tsgo/modulespecifiers/compare.go b/kitcom/internal/tsgo/modulespecifiers/compare.go deleted file mode 100644 index bcb6a27..0000000 --- a/kitcom/internal/tsgo/modulespecifiers/compare.go +++ /dev/null @@ -1,13 +0,0 @@ -package modulespecifiers - -import ( - "strings" -) - -func CountPathComponents(path string) int { - initial := 0 - if strings.HasPrefix(path, "./") { - initial = 2 - } - return strings.Count(path[initial:], "/") -} diff --git a/kitcom/internal/tsgo/modulespecifiers/preferences.go b/kitcom/internal/tsgo/modulespecifiers/preferences.go deleted file mode 100644 index a25150f..0000000 --- a/kitcom/internal/tsgo/modulespecifiers/preferences.go +++ /dev/null @@ -1,233 +0,0 @@ -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, - } -} diff --git a/kitcom/internal/tsgo/modulespecifiers/specifiers.go b/kitcom/internal/tsgo/modulespecifiers/specifiers.go deleted file mode 100644 index 1056d96..0000000 --- a/kitcom/internal/tsgo/modulespecifiers/specifiers.go +++ /dev/null @@ -1,1286 +0,0 @@ -package modulespecifiers - -import ( - "maps" - "slices" - "strings" - - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/core" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/debug" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/module" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/outputpaths" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/packagejson" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/stringutil" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" -) - -func GetModuleSpecifiers( - moduleSymbol *ast.Symbol, - checker CheckerShape, - compilerOptions *core.CompilerOptions, - importingSourceFile SourceFileForSpecifierGeneration, - host ModuleSpecifierGenerationHost, - userPreferences UserPreferences, - options ModuleSpecifierOptions, - forAutoImports bool, -) []string { - result, _ := GetModuleSpecifiersWithInfo( - moduleSymbol, - checker, - compilerOptions, - importingSourceFile, - host, - userPreferences, - options, - forAutoImports, - ) - return result -} - -func GetModuleSpecifiersWithInfo( - moduleSymbol *ast.Symbol, - checker CheckerShape, - compilerOptions *core.CompilerOptions, - importingSourceFile SourceFileForSpecifierGeneration, - host ModuleSpecifierGenerationHost, - userPreferences UserPreferences, - options ModuleSpecifierOptions, - forAutoImports bool, -) ([]string, ResultKind) { - ambient := tryGetModuleNameFromAmbientModule(moduleSymbol, checker) - if len(ambient) > 0 { - // !!! todo forAutoImport - return []string{ambient}, ResultKindAmbient - } - - moduleSourceFile := ast.GetSourceFileOfModule(moduleSymbol) - if moduleSourceFile == nil { - return nil, ResultKindNone - } - - modulePaths := getAllModulePathsWorker( - getInfo(host.GetSourceOfProjectReferenceIfOutputIncluded(importingSourceFile), host), - moduleSourceFile.FileName(), - host, - // compilerOptions, - // options, - ) - - return computeModuleSpecifiers( - modulePaths, - compilerOptions, - importingSourceFile, - host, - userPreferences, - options, - forAutoImports, - ) -} - -func tryGetModuleNameFromAmbientModule(moduleSymbol *ast.Symbol, checker CheckerShape) string { - for _, decl := range moduleSymbol.Declarations { - if isNonGlobalAmbientModule(decl) && (!ast.IsModuleAugmentationExternal(decl) || !tspath.IsExternalModuleNameRelative(decl.Name().AsStringLiteral().Text)) { - return decl.Name().AsStringLiteral().Text - } - } - - // the module could be a namespace, which is export through "export=" from an ambient module. - /** - * declare module "m" { - * namespace ns { - * class c {} - * } - * export = ns; - * } - */ - // `import {c} from "m";` is valid, in which case, `moduleSymbol` is "ns", but the module name should be "m" - for _, d := range moduleSymbol.Declarations { - if !ast.IsModuleDeclaration(d) { - continue - } - - possibleContainer := ast.FindAncestor(d, isNonGlobalAmbientModule) - if possibleContainer == nil || possibleContainer.Parent == nil || !ast.IsSourceFile(possibleContainer.Parent) { - continue - } - - sym, ok := possibleContainer.Symbol().Exports[ast.InternalSymbolNameExportEquals] - if !ok || sym == nil { - continue - } - exportAssignmentDecl := sym.ValueDeclaration - if exportAssignmentDecl == nil || exportAssignmentDecl.Kind != ast.KindExportAssignment { - continue - } - exportSymbol := checker.GetSymbolAtLocation(exportAssignmentDecl.Expression()) - if exportSymbol == nil { - continue - } - if exportSymbol.Flags&ast.SymbolFlagsAlias != 0 { - exportSymbol = checker.GetAliasedSymbol(exportSymbol) - } - // TODO: Possible strada bug - isn't this insufficient in the presence of merge symbols? - if exportSymbol == d.Symbol() { - return possibleContainer.Name().AsStringLiteral().Text - } - } - return "" -} - -type Info struct { - UseCaseSensitiveFileNames bool - ImportingSourceFileName string - SourceDirectory string -} - -func getInfo( - importingSourceFileName string, - host ModuleSpecifierGenerationHost, -) Info { - sourceDirectory := tspath.GetDirectoryPath(importingSourceFileName) - return Info{ - ImportingSourceFileName: importingSourceFileName, - SourceDirectory: sourceDirectory, - UseCaseSensitiveFileNames: host.UseCaseSensitiveFileNames(), - } -} - -func getAllModulePaths( - info Info, - importedFileName string, - host ModuleSpecifierGenerationHost, - compilerOptions *core.CompilerOptions, - preferences UserPreferences, - options ModuleSpecifierOptions, -) []ModulePath { - // !!! use new cache model - // importingFilePath := tspath.ToPath(info.ImportingSourceFileName, host.GetCurrentDirectory(), host.UseCaseSensitiveFileNames()); - // importedFilePath := tspath.ToPath(importedFileName, host.GetCurrentDirectory(), host.UseCaseSensitiveFileNames()); - // cache := host.getModuleSpecifierCache(); - // if (cache != nil) { - // cached := cache.get(importingFilePath, importedFilePath, preferences, options); - // if (cached.modulePaths) {return cached.modulePaths;} - // } - modulePaths := getAllModulePathsWorker(info, importedFileName, host) // , compilerOptions, options); - // if (cache != nil) { - // cache.setModulePaths(importingFilePath, importedFilePath, preferences, options, modulePaths); - // } - return modulePaths -} - -func getAllModulePathsWorker( - info Info, - importedFileName string, - host ModuleSpecifierGenerationHost, - // compilerOptions *core.CompilerOptions, - // options ModuleSpecifierOptions, -) []ModulePath { - // !!! TODO: Caches and symlink cache chicanery to support pulling in non-explicit package.json dep names - // cache := host.GetModuleResolutionCache() // !!! - // links := host.GetSymlinkCache() // !!! - // if cache != nil && links != nil && !strings.Contains(info.ImportingSourceFileName, "/node_modules/") { - // // Debug.type(host); // !!! - // // Cache resolutions for all `dependencies` of the `package.json` context of the input file. - // // This should populate all the relevant symlinks in the symlink cache, and most, if not all, of these resolutions - // // should get (re)used. - // // const state = getTemporaryModuleResolutionState(cache.getPackageJsonInfoCache(), host, {}); - // // const packageJson = getPackageScopeForPath(getDirectoryPath(info.importingSourceFileName), state); - // // if (packageJson) { - // // const toResolve = getAllRuntimeDependencies(packageJson.contents.packageJsonContent); - // // for (const depName of (toResolve || emptyArray)) { - // // const resolved = resolveModuleName(depName, combinePaths(packageJson.packageDirectory, "package.json"), compilerOptions, host, cache, /*redirectedReference*/ undefined, options.overrideImportMode); - // // links.setSymlinksFromResolution(resolved.resolvedModule); - // // } - // // } - // } - - allFileNames := make(map[string]ModulePath) - paths := GetEachFileNameOfModule(info.ImportingSourceFileName, importedFileName, host, true) - for _, p := range paths { - allFileNames[p.FileName] = p - } - - // Sort by paths closest to importing file Name directory - sortedPaths := make([]ModulePath, 0, len(paths)) - for directory := info.SourceDirectory; len(allFileNames) != 0; { - directoryStart := tspath.EnsureTrailingDirectorySeparator(directory) - var pathsInDirectory []ModulePath - for fileName, p := range allFileNames { - if strings.HasPrefix(fileName, directoryStart) { - pathsInDirectory = append(pathsInDirectory, p) - delete(allFileNames, fileName) - } - } - if len(pathsInDirectory) > 0 { - slices.SortStableFunc(pathsInDirectory, comparePathsByRedirectAndNumberOfDirectorySeparators) - sortedPaths = append(sortedPaths, pathsInDirectory...) - } - newDirectory := tspath.GetDirectoryPath(directory) - if newDirectory == directory { - break - } - directory = newDirectory - } - if len(allFileNames) > 0 { - remainingPaths := slices.Collect(maps.Values(allFileNames)) - slices.SortStableFunc(remainingPaths, comparePathsByRedirectAndNumberOfDirectorySeparators) - sortedPaths = append(sortedPaths, remainingPaths...) - } - return sortedPaths -} - -func containsIgnoredPath(s string) bool { - return strings.Contains(s, "/node_modules/.") || - strings.Contains(s, "/.git") || - strings.Contains(s, "/.#") -} - -func ContainsNodeModules(s string) bool { - return strings.Contains(s, "/node_modules/") -} - -func GetEachFileNameOfModule( - importingFileName string, - importedFileName string, - host ModuleSpecifierGenerationHost, - preferSymlinks bool, -) []ModulePath { - cwd := host.GetCurrentDirectory() - importedPath := tspath.ToPath(importedFileName, cwd, host.UseCaseSensitiveFileNames()) - var referenceRedirect string - outputAndReference := host.GetProjectReferenceFromSource(importedPath) - if outputAndReference != nil && outputAndReference.OutputDts != "" { - referenceRedirect = outputAndReference.OutputDts - } - - redirects := host.GetRedirectTargets(importedPath) - importedFileNames := make([]string, 0, 2+len(redirects)) - if len(referenceRedirect) > 0 { - importedFileNames = append(importedFileNames, referenceRedirect) - } - importedFileNames = append(importedFileNames, importedFileName) - importedFileNames = append(importedFileNames, redirects...) - targets := core.Map(importedFileNames, func(f string) string { return tspath.GetNormalizedAbsolutePath(f, cwd) }) - shouldFilterIgnoredPaths := !core.Every(targets, containsIgnoredPath) - - results := make([]ModulePath, 0, 2) - if !preferSymlinks { - // Symlinks inside ignored paths are already filtered out of the symlink cache, - // so we only need to remove them from the realpath filenames. - for _, p := range targets { - if !(shouldFilterIgnoredPaths && containsIgnoredPath(p)) { - results = append(results, ModulePath{ - FileName: p, - IsInNodeModules: ContainsNodeModules(p), - IsRedirect: referenceRedirect == p, - }) - } - } - } - - // !!! TODO: Symlink directory handling - // const symlinkedDirectories = host.getSymlinkCache?.().getSymlinkedDirectoriesByRealpath(); - // const fullImportedFileName = getNormalizedAbsolutePath(importedFileName, cwd); - // const result = symlinkedDirectories && forEachAncestorDirectoryStoppingAtGlobalCache( - // host, - // getDirectoryPath(fullImportedFileName), - // realPathDirectory => { - // const symlinkDirectories = symlinkedDirectories.get(ensureTrailingDirectorySeparator(toPath(realPathDirectory, cwd, getCanonicalFileName))); - // if (!symlinkDirectories) return undefined; // Continue to ancestor directory - - // // Don't want to a package to globally import from itself (importNameCodeFix_symlink_own_package.ts) - // if (startsWithDirectory(importingFileName, realPathDirectory, getCanonicalFileName)) { - // return false; // Stop search, each ancestor directory will also hit this condition - // } - - // return forEach(targets, target => { - // if (!startsWithDirectory(target, realPathDirectory, getCanonicalFileName)) { - // return; - // } - - // const relative = getRelativePathFromDirectory(realPathDirectory, target, getCanonicalFileName); - // for (const symlinkDirectory of symlinkDirectories) { - // const option = resolvePath(symlinkDirectory, relative); - // const result = cb(option, target === referenceRedirect); - // shouldFilterIgnoredPaths = true; // We found a non-ignored path in symlinks, so we can reject ignored-path realpaths - // if (result) return result; - // } - // }); - // }, - // ); - - if preferSymlinks { - for _, p := range targets { - if !(shouldFilterIgnoredPaths && containsIgnoredPath(p)) { - results = append(results, ModulePath{ - FileName: p, - IsInNodeModules: ContainsNodeModules(p), - IsRedirect: referenceRedirect == p, - }) - } - } - } - - return results -} - -func computeModuleSpecifiers( - modulePaths []ModulePath, - compilerOptions *core.CompilerOptions, - importingSourceFile SourceFileForSpecifierGeneration, - host ModuleSpecifierGenerationHost, - userPreferences UserPreferences, - options ModuleSpecifierOptions, - forAutoImport bool, -) ([]string, ResultKind) { - info := getInfo(importingSourceFile.FileName(), host) - preferences := getModuleSpecifierPreferences(userPreferences, host, compilerOptions, importingSourceFile, "") - - var existingSpecifier string - for _, modulePath := range modulePaths { - targetPath := tspath.ToPath(modulePath.FileName, host.GetCurrentDirectory(), info.UseCaseSensitiveFileNames) - var existingImport *ast.StringLiteralLike - for _, importSpecifier := range importingSourceFile.Imports() { - resolvedModule := host.GetResolvedModuleFromModuleSpecifier(importingSourceFile, importSpecifier) - if resolvedModule.IsResolved() && tspath.ToPath(resolvedModule.ResolvedFileName, host.GetCurrentDirectory(), info.UseCaseSensitiveFileNames) == targetPath { - existingImport = importSpecifier - break - } - } - if existingImport != nil { - if preferences.relativePreference == RelativePreferenceNonRelative && tspath.PathIsRelative(existingImport.Text()) { - // If the preference is for non-relative and the module specifier is relative, ignore it - continue - } - existingMode := host.GetModeForUsageLocation(importingSourceFile, existingImport) - targetMode := options.OverrideImportMode - if targetMode == core.ResolutionModeNone { - targetMode = host.GetDefaultResolutionModeForFile(importingSourceFile) - } - if existingMode != targetMode && existingMode != core.ResolutionModeNone && targetMode != core.ResolutionModeNone { - // If the candidate import mode doesn't match the mode we're generating for, don't consider it - continue - } - existingSpecifier = existingImport.Text() - break - } - } - - if existingSpecifier != "" { - return []string{existingSpecifier}, ResultKindNone - } - - importedFileIsInNodeModules := core.Some(modulePaths, func(p ModulePath) bool { return p.IsInNodeModules }) - - // Module specifier priority: - // 1. "Bare package specifiers" (e.g. "@foo/bar") resulting from a path through node_modules to a package.json's "types" entry - // 2. Specifiers generated using "paths" from tsconfig - // 3. Non-relative specfiers resulting from a path through node_modules (e.g. "@foo/bar/path/to/file") - // 4. Relative paths - var pathsSpecifiers []string - var redirectPathsSpecifiers []string - var nodeModulesSpecifiers []string - var relativeSpecifiers []string - - for _, modulePath := range modulePaths { - var specifier string - if modulePath.IsInNodeModules { - specifier = tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, userPreferences /*packageNameOnly*/, false, options.OverrideImportMode) - } - if len(specifier) > 0 && !(forAutoImport && isExcludedByRegex(specifier, preferences.excludeRegexes)) { - nodeModulesSpecifiers = append(nodeModulesSpecifiers, specifier) - if modulePath.IsRedirect { - // If we got a specifier for a redirect, it was a bare package specifier (e.g. "@foo/bar", - // not "@foo/bar/path/to/file"). No other specifier will be this good, so stop looking. - return nodeModulesSpecifiers, ResultKindNodeModules - } - } - - importMode := options.OverrideImportMode - if importMode == core.ResolutionModeNone { - importMode = host.GetDefaultResolutionModeForFile(importingSourceFile) - } - local := getLocalModuleSpecifier( - modulePath.FileName, - info, - compilerOptions, - host, - importMode, - preferences, - /*pathsOnly*/ modulePath.IsRedirect || len(specifier) > 0, - ) - if len(local) == 0 || forAutoImport && isExcludedByRegex(local, preferences.excludeRegexes) { - continue - } - if modulePath.IsRedirect { - redirectPathsSpecifiers = append(redirectPathsSpecifiers, local) - } else if PathIsBareSpecifier(local) { - if ContainsNodeModules(local) { - // We could be in this branch due to inappropriate use of `baseUrl`, not intentional `paths` - // usage. It's impossible to reason about where to prioritize baseUrl-generated module - // specifiers, but if they contain `/node_modules/`, they're going to trigger a portability - // error, so *at least* don't prioritize those. - relativeSpecifiers = append(relativeSpecifiers, local) - } else { - pathsSpecifiers = append(pathsSpecifiers, local) - } - } else if forAutoImport || !importedFileIsInNodeModules || modulePath.IsInNodeModules { - // Why this extra conditional, not just an `else`? If some path to the file contained - // 'node_modules', but we can't create a non-relative specifier (e.g. "@foo/bar/path/to/file"), - // that means we had to go through a *sibling's* node_modules, not one we can access directly. - // If some path to the file was in node_modules but another was not, this likely indicates that - // we have a monorepo structure with symlinks. In this case, the non-nodeModules path is - // probably the realpath, e.g. "../bar/path/to/file", but a relative path to another package - // in a monorepo is probably not portable. So, the module specifier we actually go with will be - // the relative path through node_modules, so that the declaration emitter can produce a - // portability error. (See declarationEmitReexportedSymlinkReference3) - relativeSpecifiers = append(relativeSpecifiers, local) - } - } - - if len(pathsSpecifiers) > 0 { - return pathsSpecifiers, ResultKindPaths - } - if len(redirectPathsSpecifiers) > 0 { - return redirectPathsSpecifiers, ResultKindRedirect - } - if len(nodeModulesSpecifiers) > 0 { - return nodeModulesSpecifiers, ResultKindNodeModules - } - return relativeSpecifiers, ResultKindRelative -} - -func getLocalModuleSpecifier( - moduleFileName string, - info Info, - compilerOptions *core.CompilerOptions, - host ModuleSpecifierGenerationHost, - importMode core.ResolutionMode, - preferences ModuleSpecifierPreferences, - pathsOnly bool, -) string { - paths := compilerOptions.Paths - rootDirs := compilerOptions.RootDirs - - if pathsOnly && paths == nil { - return "" - } - - sourceDirectory := info.SourceDirectory - - allowedEndings := preferences.getAllowedEndingsInPreferredOrder(importMode) - var relativePath string - if len(rootDirs) > 0 { - relativePath = tryGetModuleNameFromRootDirs(rootDirs, moduleFileName, sourceDirectory, allowedEndings, compilerOptions, host) - } - if len(relativePath) == 0 { - relativePath = processEnding(ensurePathIsNonModuleName(tspath.GetRelativePathFromDirectory(sourceDirectory, moduleFileName, tspath.ComparePathsOptions{ - UseCaseSensitiveFileNames: host.UseCaseSensitiveFileNames(), - CurrentDirectory: host.GetCurrentDirectory(), - })), allowedEndings, compilerOptions, host) - } - - root := compilerOptions.GetPathsBasePath(host.GetCurrentDirectory()) - baseDirectory := tspath.GetNormalizedAbsolutePath(root, host.GetCurrentDirectory()) - relativeToBaseUrl := getRelativePathIfInSameVolume(moduleFileName, baseDirectory, host.UseCaseSensitiveFileNames()) - if len(relativeToBaseUrl) == 0 { - if pathsOnly { - return "" - } - return relativePath - } - - var fromPackageJsonImports string - if !pathsOnly { - fromPackageJsonImports = tryGetModuleNameFromPackageJsonImports( - moduleFileName, - sourceDirectory, - compilerOptions, - host, - importMode, - prefersTsExtension(allowedEndings), - ) - } - - var fromPaths string - if (pathsOnly || len(fromPackageJsonImports) == 0) && paths != nil { - fromPaths = tryGetModuleNameFromPaths( - relativeToBaseUrl, - paths, - allowedEndings, - baseDirectory, - host, - compilerOptions, - ) - } - - if pathsOnly { - return fromPaths - } - - var maybeNonRelative string - if len(fromPackageJsonImports) > 0 { - maybeNonRelative = fromPackageJsonImports - } else { - maybeNonRelative = fromPaths - } - if len(maybeNonRelative) == 0 { - return relativePath - } - - relativeIsExcluded := isExcludedByRegex(relativePath, preferences.excludeRegexes) - nonRelativeIsExcluded := isExcludedByRegex(maybeNonRelative, preferences.excludeRegexes) - if !relativeIsExcluded && nonRelativeIsExcluded { - return relativePath - } - if relativeIsExcluded && !nonRelativeIsExcluded { - return maybeNonRelative - } - - if preferences.relativePreference == RelativePreferenceNonRelative && !tspath.PathIsRelative(maybeNonRelative) { - return maybeNonRelative - } - - if preferences.relativePreference == RelativePreferenceExternalNonRelative && !tspath.PathIsRelative(maybeNonRelative) { - var projectDirectory tspath.Path - if len(compilerOptions.ConfigFilePath) > 0 { - projectDirectory = tspath.ToPath(compilerOptions.ConfigFilePath, host.GetCurrentDirectory(), host.UseCaseSensitiveFileNames()) - } else { - projectDirectory = tspath.ToPath(host.GetCurrentDirectory(), host.GetCurrentDirectory(), host.UseCaseSensitiveFileNames()) - } - canonicalSourceDirectory := tspath.ToPath(sourceDirectory, host.GetCurrentDirectory(), host.UseCaseSensitiveFileNames()) - modulePath := tspath.ToPath(moduleFileName, string(projectDirectory), host.UseCaseSensitiveFileNames()) - - sourceIsInternal := strings.HasPrefix(string(canonicalSourceDirectory), string(projectDirectory)) - targetIsInternal := strings.HasPrefix(string(modulePath), string(projectDirectory)) - if sourceIsInternal && !targetIsInternal || !sourceIsInternal && targetIsInternal { - // 1. The import path crosses the boundary of the tsconfig.json-containing directory. - // - // src/ - // tsconfig.json - // index.ts ------- - // lib/ | (path crosses tsconfig.json) - // imported.ts <--- - // - return maybeNonRelative - } - - nearestTargetPackageJson := host.GetNearestAncestorDirectoryWithPackageJson(tspath.GetDirectoryPath(string(modulePath))) - nearestSourcePackageJson := host.GetNearestAncestorDirectoryWithPackageJson(sourceDirectory) - - if !packageJsonPathsAreEqual(nearestTargetPackageJson, nearestSourcePackageJson, tspath.ComparePathsOptions{ - UseCaseSensitiveFileNames: host.UseCaseSensitiveFileNames(), - CurrentDirectory: host.GetCurrentDirectory(), - }) { - // 2. The importing and imported files are part of different packages. - // - // packages/a/ - // package.json - // index.ts -------- - // packages/b/ | (path crosses package.json) - // package.json | - // component.ts <--- - // - return maybeNonRelative - } - } - - // Prefer a relative import over a baseUrl import if it has fewer components. - if isPathRelativeToParent(maybeNonRelative) || strings.Count(relativePath, "/") < strings.Count(maybeNonRelative, "/") { - return relativePath - } - return maybeNonRelative -} - -func processEnding( - fileName string, - allowedEndings []ModuleSpecifierEnding, - options *core.CompilerOptions, - host ModuleSpecifierGenerationHost, -) string { - if tspath.FileExtensionIsOneOf(fileName, []string{tspath.ExtensionJson, tspath.ExtensionMjs, tspath.ExtensionCjs}) { - return fileName - } - - noExtension := tspath.RemoveFileExtension(fileName) - if fileName == noExtension { - return fileName - } - - jsPriority := slices.Index(allowedEndings, ModuleSpecifierEndingJsExtension) - tsPriority := slices.Index(allowedEndings, ModuleSpecifierEndingTsExtension) - if tspath.FileExtensionIsOneOf(fileName, []string{tspath.ExtensionMts, tspath.ExtensionCts}) && tsPriority < jsPriority { - return fileName - } - if tspath.FileExtensionIsOneOf(fileName, []string{tspath.ExtensionDmts, tspath.ExtensionMts, tspath.ExtensionDcts, tspath.ExtensionCts}) { - inputExt := tspath.GetDeclarationFileExtension(fileName) - ext := getJsExtensionForDeclarationFileExtension(inputExt) - return tspath.RemoveExtension(fileName, inputExt) + ext - } - - switch allowedEndings[0] { - case ModuleSpecifierEndingMinimal: - withoutIndex := strings.TrimSuffix(noExtension, "/index") - if host != nil && withoutIndex != noExtension && tryGetAnyFileFromPath(host, withoutIndex) { - // Can't remove index if there's a file by the same name as the directory. - // Probably more callers should pass `host` so we can determine this? - return noExtension - } - return withoutIndex - case ModuleSpecifierEndingIndex: - return noExtension - case ModuleSpecifierEndingJsExtension: - return noExtension + getJSExtensionForFile(fileName, options) - case ModuleSpecifierEndingTsExtension: - // declaration files are already handled first with a remap back to input js paths, - // and mjs/cjs/json are already singled out, - // so we know fileName has to be either an input .js or .ts path already - // TODO: possible dead code in strada in this branch to do with declaration file name handling - return fileName - default: - debug.AssertNever(allowedEndings[0]) - return "" - } -} - -func tryGetModuleNameFromRootDirs( - rootDirs []string, - moduleFileName string, - sourceDirectory string, - allowedEndings []ModuleSpecifierEnding, - compilerOptions *core.CompilerOptions, - host ModuleSpecifierGenerationHost, -) string { - normalizedTargetPaths := getPathsRelativeToRootDirs(moduleFileName, rootDirs, host.UseCaseSensitiveFileNames()) - if len(normalizedTargetPaths) == 0 { - return "" - } - - normalizedSourcePaths := getPathsRelativeToRootDirs(sourceDirectory, rootDirs, host.UseCaseSensitiveFileNames()) - var shortest string - var shortestSepCount int - for _, sourcePath := range normalizedSourcePaths { - for _, targetPath := range normalizedTargetPaths { - candidate := ensurePathIsNonModuleName(tspath.GetRelativePathFromDirectory(sourcePath, targetPath, tspath.ComparePathsOptions{ - UseCaseSensitiveFileNames: host.UseCaseSensitiveFileNames(), - CurrentDirectory: host.GetCurrentDirectory(), - })) - candidateSepCount := strings.Count(candidate, "/") - if len(shortest) == 0 || candidateSepCount < shortestSepCount { - shortest = candidate - shortestSepCount = candidateSepCount - } - } - } - - if len(shortest) == 0 { - return "" - } - return processEnding(shortest, allowedEndings, compilerOptions, host) -} - -func tryGetModuleNameAsNodeModule( - pathObj ModulePath, - info Info, - importingSourceFile SourceFileForSpecifierGeneration, - host ModuleSpecifierGenerationHost, - options *core.CompilerOptions, - userPreferences UserPreferences, - packageNameOnly bool, - overrideMode core.ResolutionMode, -) string { - parts := GetNodeModulePathParts(pathObj.FileName) - if parts == nil { - return "" - } - - // Simplify the full file path to something that can be resolved by Node. - preferences := getModuleSpecifierPreferences(userPreferences, host, options, importingSourceFile, "") - allowedEndings := preferences.getAllowedEndingsInPreferredOrder(core.ResolutionModeNone) - - caseSensitive := host.UseCaseSensitiveFileNames() - moduleSpecifier := pathObj.FileName - isPackageRootPath := false - if !packageNameOnly { - packageRootIndex := parts.PackageRootIndex - var moduleFileName string - for true { - // If the module could be imported by a directory name, use that directory's name - pkgJsonResults := tryDirectoryWithPackageJson( - *parts, - pathObj, - importingSourceFile, - host, - overrideMode, - options, - allowedEndings, - ) - moduleFileToTry := pkgJsonResults.moduleFileToTry - packageRootPath := pkgJsonResults.packageRootPath - blockedByExports := pkgJsonResults.blockedByExports - verbatimFromExports := pkgJsonResults.verbatimFromExports - if blockedByExports { - return "" // File is under this package.json, but is not publicly exported - there's no way to name it via `node_modules` resolution - } - if verbatimFromExports { - return moduleFileToTry - } - //} - if len(packageRootPath) > 0 { - moduleSpecifier = packageRootPath - isPackageRootPath = true - break - } - if len(moduleFileName) == 0 { - moduleFileName = moduleFileToTry - } - // try with next level of directory - packageRootIndex = core.IndexAfter(pathObj.FileName, "/", packageRootIndex+1) - if packageRootIndex == -1 { - moduleSpecifier = processEnding(moduleFileName, allowedEndings, options, host) - break - } - } - } - - if pathObj.IsRedirect && !isPackageRootPath { - return "" - } - - globalTypingsCacheLocation := host.GetGlobalTypingsCacheLocation() - // Get a path that's relative to node_modules or the importing file's path - // if node_modules folder is in this folder or any of its parent folders, no need to keep it. - pathToTopLevelNodeModules := moduleSpecifier[0:parts.TopLevelNodeModulesIndex] - - if !stringutil.HasPrefix(info.SourceDirectory, pathToTopLevelNodeModules, caseSensitive) || len(globalTypingsCacheLocation) > 0 && stringutil.HasPrefix(globalTypingsCacheLocation, pathToTopLevelNodeModules, caseSensitive) { - return "" - } - - // If the module was found in @types, get the actual Node package name - nodeModulesDirectoryName := moduleSpecifier[parts.TopLevelPackageNameIndex+1:] - return GetPackageNameFromTypesPackageName(nodeModulesDirectoryName) -} - -type pkgJsonDirAttemptResult struct { - moduleFileToTry string - packageRootPath string - blockedByExports bool - verbatimFromExports bool -} - -func tryDirectoryWithPackageJson( - parts NodeModulePathParts, - pathObj ModulePath, - importingSourceFile SourceFileForSpecifierGeneration, - host ModuleSpecifierGenerationHost, - overrideMode core.ResolutionMode, - options *core.CompilerOptions, - allowedEndings []ModuleSpecifierEnding, -) pkgJsonDirAttemptResult { - rootIdx := parts.PackageRootIndex - if rootIdx == -1 { - rootIdx = len(pathObj.FileName) // TODO: possible strada bug? -1 in js slice removes characters from the end, in go it panics - js behavior seems unwanted here? - } - packageRootPath := pathObj.FileName[0:rootIdx] - packageJsonPath := tspath.CombinePaths(packageRootPath, "package.json") - moduleFileToTry := pathObj.FileName - maybeBlockedByTypesVersions := false - packageJson := host.GetPackageJsonInfo(packageJsonPath) - if packageJson == nil { - // No package.json exists; an index.js will still resolve as the package name - fileName := moduleFileToTry[parts.PackageRootIndex+1:] - if fileName == "index.d.ts" || fileName == "index.js" || fileName == "index.ts" || fileName == "index.tsx" { - return pkgJsonDirAttemptResult{moduleFileToTry: moduleFileToTry, packageRootPath: packageRootPath} - } - } - - importMode := overrideMode - if importMode == core.ResolutionModeNone { - importMode = host.GetDefaultResolutionModeForFile(importingSourceFile) - } - - var packageJsonContent *packagejson.PackageJson - if packageJson != nil { - packageJsonContent = packageJson.GetContents() - } - - if options.GetResolvePackageJsonImports() { - // The package name that we found in node_modules could be different from the package - // name in the package.json content via url/filepath dependency specifiers. We need to - // use the actual directory name, so don't look at `packageJsonContent.name` here. - nodeModulesDirectoryName := packageRootPath[parts.TopLevelPackageNameIndex+1:] - packageName := GetPackageNameFromTypesPackageName(nodeModulesDirectoryName) - conditions := module.GetConditions(options, importMode) - - var fromExports string - if packageJsonContent != nil && packageJsonContent.Fields.Exports.Type != packagejson.JSONValueTypeNotPresent { - fromExports = tryGetModuleNameFromExports( - options, - host, - pathObj.FileName, - packageRootPath, - packageName, - packageJsonContent.Fields.Exports, - conditions, - ) - } - if len(fromExports) > 0 { - return pkgJsonDirAttemptResult{ - moduleFileToTry: fromExports, - verbatimFromExports: true, - } - } - if packageJsonContent != nil && packageJsonContent.Fields.Exports.Type != packagejson.JSONValueTypeNotPresent { - return pkgJsonDirAttemptResult{ - moduleFileToTry: pathObj.FileName, - blockedByExports: true, - } - } - } - - var versionPaths packagejson.VersionPaths - if packageJsonContent != nil && packageJsonContent.TypesVersions.Type == packagejson.JSONValueTypeObject { - versionPaths = packageJsonContent.GetVersionPaths(nil) - } - if versionPaths.GetPaths() != nil { - subModuleName := pathObj.FileName[len(packageRootPath)+1:] - fromPaths := tryGetModuleNameFromPaths( - subModuleName, - versionPaths.GetPaths(), - allowedEndings, - packageRootPath, - host, - options, - ) - if len(fromPaths) == 0 { - maybeBlockedByTypesVersions = true - } else { - moduleFileToTry = tspath.CombinePaths(packageRootPath, fromPaths) - } - } - // If the file is the main module, it can be imported by the package name - mainFileRelative := "index.js" - if packageJsonContent != nil { - if packageJsonContent.Typings.Valid { - mainFileRelative = packageJsonContent.Typings.Value - } else if packageJsonContent.Types.Valid { - mainFileRelative = packageJsonContent.Types.Value - } else if packageJsonContent.Main.Valid { - mainFileRelative = packageJsonContent.Main.Value - } - } - - if len(mainFileRelative) > 0 && !(maybeBlockedByTypesVersions && module.MatchPatternOrExact(module.TryParsePatterns(versionPaths.GetPaths()), mainFileRelative) != core.Pattern{}) { - // The 'main' file is also subject to mapping through typesVersions, and we couldn't come up with a path - // explicitly through typesVersions, so if it matches a key in typesVersions now, it's not reachable. - // (The only way this can happen is if some file in a package that's not resolvable from outside the - // package got pulled into the program anyway, e.g. transitively through a file that *is* reachable. It - // happens very easily in fourslash tests though, since every test file listed gets included. See - // importNameCodeFix_typesVersions.ts for an example.) - mainExportFile := tspath.ToPath(mainFileRelative, packageRootPath, host.UseCaseSensitiveFileNames()) - compareOpt := tspath.ComparePathsOptions{ - UseCaseSensitiveFileNames: host.UseCaseSensitiveFileNames(), - CurrentDirectory: host.GetCurrentDirectory(), - } - if tspath.ComparePaths(tspath.RemoveFileExtension(string(mainExportFile)), tspath.RemoveFileExtension(moduleFileToTry), compareOpt) == 0 { - // ^ An arbitrary removal of file extension for this comparison is almost certainly wrong - return pkgJsonDirAttemptResult{packageRootPath: packageRootPath, moduleFileToTry: moduleFileToTry} - } else if packageJsonContent == nil || packageJsonContent.Type.Value != "module" && - !tspath.FileExtensionIsOneOf(moduleFileToTry, tspath.ExtensionsNotSupportingExtensionlessResolution) && - stringutil.HasPrefix(moduleFileToTry, string(mainExportFile), host.UseCaseSensitiveFileNames()) && - tspath.ComparePaths(tspath.GetDirectoryPath(moduleFileToTry), tspath.RemoveTrailingDirectorySeparator(string(mainExportFile)), compareOpt) == 0 && - tspath.RemoveFileExtension(tspath.GetBaseFileName(moduleFileToTry)) == "index" { - // if mainExportFile is a directory, which contains moduleFileToTry, we just try index file - // example mainExportFile: `pkg/lib` and moduleFileToTry: `pkg/lib/index`, we can use packageRootPath - // but this behavior is deprecated for packages with "type": "module", so we only do this for packages without "type": "module" - // and make sure that the extension on index.{???} is something that supports omitting the extension - return pkgJsonDirAttemptResult{packageRootPath: packageRootPath, moduleFileToTry: moduleFileToTry} - } - } - - return pkgJsonDirAttemptResult{moduleFileToTry: moduleFileToTry} -} - -func tryGetModuleNameFromExports( - options *core.CompilerOptions, - host ModuleSpecifierGenerationHost, - targetFilePath string, - packageDirectory string, - packageName string, - exports packagejson.ExportsOrImports, - conditions []string, -) string { - if exports.IsSubpaths() { - // sub-mappings - // 3 cases: - // * directory mappings (legacyish, key ends with / (technically allows index/extension resolution under cjs mode)) - // * pattern mappings (contains a *) - // * exact mappings (no *, does not end with /) - for k, subk := range exports.AsObject().Entries() { - subPackageName := tspath.GetNormalizedAbsolutePath(tspath.CombinePaths(packageName, k), "") - mode := MatchingModeExact - if strings.HasSuffix(k, "/") { - mode = MatchingModeDirectory - } else if strings.Contains(k, "*") { - mode = MatchingModePattern - } - result := tryGetModuleNameFromExportsOrImports(options, host, targetFilePath, packageDirectory, subPackageName, subk, conditions, mode /*isImports*/, false /*preferTsExtension*/, false) - if len(result) > 0 { - return result - } - } - } - return tryGetModuleNameFromExportsOrImports( - options, - host, - targetFilePath, - packageDirectory, - packageName, - exports, - conditions, - MatchingModeExact, - /*isImports*/ false, - /*preferTsExtension*/ false, - ) -} - -func tryGetModuleNameFromPackageJsonImports( - moduleFileName string, - sourceDirectory string, - options *core.CompilerOptions, - host ModuleSpecifierGenerationHost, - importMode core.ResolutionMode, - preferTsExtension bool, -) string { - if !options.GetResolvePackageJsonImports() { - return "" - } - - ancestorDirectoryWithPackageJson := host.GetNearestAncestorDirectoryWithPackageJson(sourceDirectory) - if len(ancestorDirectoryWithPackageJson) == 0 { - return "" - } - packageJsonPath := tspath.CombinePaths(ancestorDirectoryWithPackageJson, "package.json") - - info := host.GetPackageJsonInfo(packageJsonPath) - if info == nil { - return "" - } - - imports := info.GetContents().Fields.Imports - switch imports.Type { - case packagejson.JSONValueTypeNotPresent, packagejson.JSONValueTypeArray, packagejson.JSONValueTypeString: - return "" // not present or invalid for imports - case packagejson.JSONValueTypeObject: - conditions := module.GetConditions(options, importMode) - top := imports.AsObject() - entries := top.Entries() - for k, value := range entries { - if !strings.HasPrefix(k, "#") || k == "#" || strings.HasPrefix(k, "#/") { - continue // invalid imports entry - } - mode := MatchingModeExact - if strings.HasSuffix(k, "/") { - mode = MatchingModeDirectory - } else if strings.Contains(k, "*") { - mode = MatchingModePattern - } - result := tryGetModuleNameFromExportsOrImports( - options, - host, - moduleFileName, - ancestorDirectoryWithPackageJson, - k, - value, - conditions, - mode, - true, - preferTsExtension, - ) - if len(result) > 0 { - return result - } - } - } - - return "" -} - -type specPair struct { - ending ModuleSpecifierEnding - value string -} - -func tryGetModuleNameFromPaths( - relativeToBaseUrl string, - paths *collections.OrderedMap[string, []string], - allowedEndings []ModuleSpecifierEnding, - baseDirectory string, - host ModuleSpecifierGenerationHost, - compilerOptions *core.CompilerOptions, -) string { - caseSensitive := host.UseCaseSensitiveFileNames() - for key, values := range paths.Entries() { - for _, patternText := range values { - normalized := tspath.NormalizePath(patternText) - pattern := getRelativePathIfInSameVolume(normalized, baseDirectory, caseSensitive) - if len(pattern) == 0 { - pattern = normalized - } - indexOfStar := strings.Index(pattern, "*") - - // In module resolution, if `pattern` itself has an extension, a file with that extension is looked up directly, - // meaning a '.ts' or '.d.ts' extension is allowed to resolve. This is distinct from the case where a '*' substitution - // causes a module specifier to have an extension, i.e. the extension comes from the module specifier in a JS/TS file - // and matches the '*'. For example: - // - // Module Specifier | Path Mapping (key: [pattern]) | Interpolation | Resolution Action - // ---------------------->------------------------------->--------------------->--------------------------------------------------------------- - // import "@app/foo" -> "@app/*": ["./src/app/*.ts"] -> "./src/app/foo.ts" -> tryFile("./src/app/foo.ts") || [continue resolution algorithm] - // import "@app/foo.ts" -> "@app/*": ["./src/app/*"] -> "./src/app/foo.ts" -> [continue resolution algorithm] - // - // (https://github.com/microsoft/TypeScript/blob/ad4ded80e1d58f0bf36ac16bea71bc10d9f09895/src/compiler/moduleNameResolver.ts#L2509-L2516) - // - // The interpolation produced by both scenarios is identical, but only in the former, where the extension is encoded in - // the path mapping rather than in the module specifier, will we prioritize a file lookup on the interpolation result. - // (In fact, currently, the latter scenario will necessarily fail since no resolution mode recognizes '.ts' as a valid - // extension for a module specifier.) - // - // Here, this means we need to be careful about whether we generate a match from the target filename (typically with a - // .ts extension) or the possible relative module specifiers representing that file: - // - // Filename | Relative Module Specifier Candidates | Path Mapping | Filename Result | Module Specifier Results - // --------------------<----------------------------------------------<------------------------------<-------------------||---------------------------- - // dist/haha.d.ts <- dist/haha, dist/haha.js <- "@app/*": ["./dist/*.d.ts"] <- @app/haha || (none) - // dist/haha.d.ts <- dist/haha, dist/haha.js <- "@app/*": ["./dist/*"] <- (none) || @app/haha, @app/haha.js - // dist/foo/index.d.ts <- dist/foo, dist/foo/index, dist/foo/index.js <- "@app/*": ["./dist/*.d.ts"] <- @app/foo/index || (none) - // dist/foo/index.d.ts <- dist/foo, dist/foo/index, dist/foo/index.js <- "@app/*": ["./dist/*"] <- (none) || @app/foo, @app/foo/index, @app/foo/index.js - // dist/wow.js.js <- dist/wow.js, dist/wow.js.js <- "@app/*": ["./dist/*.js"] <- @app/wow.js || @app/wow, @app/wow.js - // - // The "Filename Result" can be generated only if `pattern` has an extension. Care must be taken that the list of - // relative module specifiers to run the interpolation (a) is actually valid for the module resolution mode, (b) takes - // into account the existence of other files (e.g. 'dist/wow.js' cannot refer to 'dist/wow.js.js' if 'dist/wow.js' - // exists) and (c) that they are ordered by preference. The last row shows that the filename result and module - // specifier results are not mutually exclusive. Note that the filename result is a higher priority in module - // resolution, but as long criteria (b) above is met, I don't think its result needs to be the highest priority result - // in module specifier generation. I have included it last, as it's difficult to tell exactly where it should be - // sorted among the others for a particular value of `importModuleSpecifierEnding`. - - var candidates []specPair - for _, ending := range allowedEndings { - result := processEnding( - relativeToBaseUrl, - []ModuleSpecifierEnding{ending}, - compilerOptions, - host, - ) - candidates = append(candidates, specPair{ - ending: ending, - value: result, - }) - } - if len(tspath.TryGetExtensionFromPath(pattern)) > 0 { - candidates = append(candidates, specPair{ - ending: ModuleSpecifierEndingJsExtension, - value: relativeToBaseUrl, - }) - } - - if indexOfStar != -1 { - prefix := pattern[0:indexOfStar] - suffix := pattern[indexOfStar+1:] - for _, c := range candidates { - value := c.value - if len(value) >= len(prefix)+len(suffix) && - stringutil.HasPrefix(value, prefix, caseSensitive) && // TODO: possible strada bug: these are not case-switched in strada - stringutil.HasSuffix(value, suffix, caseSensitive) && - validateEnding(c, relativeToBaseUrl, compilerOptions, host) { - matchedStar := value[len(prefix) : len(value)-len(suffix)] - if !tspath.PathIsRelative(matchedStar) { - return replaceFirstStar(key, matchedStar) - } - } - } - } else if core.Some(candidates, func(c specPair) bool { return c.ending != ModuleSpecifierEndingMinimal && pattern == c.value }) || - core.Some(candidates, func(c specPair) bool { - return c.ending == ModuleSpecifierEndingMinimal && pattern == c.value && validateEnding(c, relativeToBaseUrl, compilerOptions, host) - }) { - return key - } - } - } - return "" -} - -func validateEnding(c specPair, relativeToBaseUrl string, compilerOptions *core.CompilerOptions, host ModuleSpecifierGenerationHost) bool { - // Optimization: `removeExtensionAndIndexPostFix` can query the file system (a good bit) if `ending` is `Minimal`, the basename - // is 'index', and a `host` is provided. To avoid that until it's unavoidable, we ran the function with no `host` above. Only - // here, after we've checked that the minimal ending is indeed a match (via the length and prefix/suffix checks / `some` calls), - // do we check that the host-validated result is consistent with the answer we got before. If it's not, it falls back to the - // `ModuleSpecifierEnding.Index` result, which should already be in the list of candidates if `Minimal` was. (Note: the assumption here is - // that every module resolution mode that supports dropping extensions also supports dropping `/index`. Like literally - // everything else in this file, this logic needs to be updated if that's not true in some future module resolution mode.) - return c.ending != ModuleSpecifierEndingMinimal || c.value == processEnding(relativeToBaseUrl, []ModuleSpecifierEnding{c.ending}, compilerOptions, host) -} - -func tryGetModuleNameFromExportsOrImports( - options *core.CompilerOptions, - host ModuleSpecifierGenerationHost, - targetFilePath string, - packageDirectory string, - packageName string, - exports packagejson.ExportsOrImports, - conditions []string, - mode MatchingMode, - isImports bool, - preferTsExtension bool, -) string { - switch exports.Type { - case packagejson.JSONValueTypeNotPresent: - return "" - case packagejson.JSONValueTypeString: - strValue := exports.Value.(string) - - // possible strada bug? Always uses compilerOptions of the host project, not those applicable to the targeted package.json! - var outputFile string - var declarationFile string - if isImports { - outputFile = outputpaths.GetOutputJSFileNameWorker(targetFilePath, options, host) - declarationFile = outputpaths.GetOutputDeclarationFileNameWorker(targetFilePath, options, host) - } - - pathOrPattern := tspath.GetNormalizedAbsolutePath(tspath.CombinePaths(packageDirectory, strValue), "") - var extensionSwappedTarget string - if tspath.HasTSFileExtension(targetFilePath) { - extensionSwappedTarget = tspath.RemoveFileExtension(targetFilePath) + tryGetJSExtensionForFile(targetFilePath, options) - } - canTryTsExtension := preferTsExtension && tspath.HasImplementationTSFileExtension(targetFilePath) - - compareOpts := tspath.ComparePathsOptions{ - UseCaseSensitiveFileNames: host.UseCaseSensitiveFileNames(), - CurrentDirectory: host.GetCurrentDirectory(), - } - - switch mode { - case MatchingModeExact: - if len(extensionSwappedTarget) > 0 && tspath.ComparePaths(extensionSwappedTarget, pathOrPattern, compareOpts) == 0 || - tspath.ComparePaths(targetFilePath, pathOrPattern, compareOpts) == 0 || - len(outputFile) > 0 && tspath.ComparePaths(outputFile, pathOrPattern, compareOpts) == 0 || - len(declarationFile) > 0 && tspath.ComparePaths(declarationFile, pathOrPattern, compareOpts) == 0 { - return packageName - } - case MatchingModeDirectory: - if canTryTsExtension && tspath.ContainsPath(targetFilePath, pathOrPattern, compareOpts) { - fragment := tspath.GetRelativePathFromDirectory(pathOrPattern, targetFilePath, compareOpts) - return tspath.GetNormalizedAbsolutePath(tspath.CombinePaths(tspath.CombinePaths(packageName, strValue), fragment), "") - } - if len(extensionSwappedTarget) > 0 && tspath.ContainsPath(pathOrPattern, extensionSwappedTarget, compareOpts) { - fragment := tspath.GetRelativePathFromDirectory(pathOrPattern, extensionSwappedTarget, compareOpts) - return tspath.GetNormalizedAbsolutePath(tspath.CombinePaths(tspath.CombinePaths(packageName, strValue), fragment), "") - } - if !canTryTsExtension && tspath.ContainsPath(pathOrPattern, targetFilePath, compareOpts) { - fragment := tspath.GetRelativePathFromDirectory(pathOrPattern, targetFilePath, compareOpts) - return tspath.GetNormalizedAbsolutePath(tspath.CombinePaths(tspath.CombinePaths(packageName, strValue), fragment), "") - } - if len(outputFile) > 0 && tspath.ContainsPath(pathOrPattern, outputFile, compareOpts) { - fragment := tspath.GetRelativePathFromDirectory(pathOrPattern, outputFile, compareOpts) - return tspath.CombinePaths(packageName, fragment) - } - if len(declarationFile) > 0 && tspath.ContainsPath(pathOrPattern, declarationFile, compareOpts) { - fragment := tspath.GetRelativePathFromDirectory(pathOrPattern, declarationFile, compareOpts) - jsExtension := getJSExtensionForFile(declarationFile, options) - fragmentWithJsExtension := tspath.ChangeExtension(fragment, jsExtension) - return tspath.CombinePaths(packageName, fragmentWithJsExtension) - } - case MatchingModePattern: - starPos := strings.Index(pathOrPattern, "*") - leadingSlice := pathOrPattern[0:starPos] - trailingSlice := pathOrPattern[starPos+1:] - caseSensitive := host.UseCaseSensitiveFileNames() - if canTryTsExtension && stringutil.HasPrefix(targetFilePath, leadingSlice, caseSensitive) && stringutil.HasSuffix(targetFilePath, trailingSlice, caseSensitive) { - starReplacement := targetFilePath[len(leadingSlice) : len(targetFilePath)-len(trailingSlice)] - return replaceFirstStar(packageName, starReplacement) - } - if len(extensionSwappedTarget) > 0 && stringutil.HasPrefix(extensionSwappedTarget, leadingSlice, caseSensitive) && stringutil.HasSuffix(extensionSwappedTarget, trailingSlice, caseSensitive) { - starReplacement := extensionSwappedTarget[len(leadingSlice) : len(extensionSwappedTarget)-len(trailingSlice)] - return replaceFirstStar(packageName, starReplacement) - } - if !canTryTsExtension && stringutil.HasPrefix(targetFilePath, leadingSlice, caseSensitive) && stringutil.HasSuffix(targetFilePath, trailingSlice, caseSensitive) { - starReplacement := targetFilePath[len(leadingSlice) : len(targetFilePath)-len(trailingSlice)] - return replaceFirstStar(packageName, starReplacement) - } - if len(outputFile) > 0 && stringutil.HasPrefix(outputFile, leadingSlice, caseSensitive) && stringutil.HasSuffix(outputFile, trailingSlice, caseSensitive) { - starReplacement := outputFile[len(leadingSlice) : len(outputFile)-len(trailingSlice)] - return replaceFirstStar(packageName, starReplacement) - } - if len(declarationFile) > 0 && stringutil.HasPrefix(declarationFile, leadingSlice, caseSensitive) && stringutil.HasSuffix(declarationFile, trailingSlice, caseSensitive) { - starReplacement := declarationFile[len(leadingSlice) : len(declarationFile)-len(trailingSlice)] - substituted := replaceFirstStar(packageName, starReplacement) - jsExtension := tryGetJSExtensionForFile(declarationFile, options) - if len(jsExtension) > 0 { - return tspath.ChangeFullExtension(substituted, jsExtension) - } - } - } - return "" - case packagejson.JSONValueTypeArray: - arr := exports.AsArray() - for _, e := range arr { - result := tryGetModuleNameFromExportsOrImports(options, host, targetFilePath, packageDirectory, packageName, e, conditions, mode, isImports, preferTsExtension) - if len(result) > 0 { - return result - } - } - case packagejson.JSONValueTypeObject: - // conditional mapping - obj := exports.AsObject() - for key, value := range obj.Entries() { - if key == "default" || slices.Contains(conditions, key) || isApplicableVersionedTypesKey(conditions, key) { - result := tryGetModuleNameFromExportsOrImports(options, host, targetFilePath, packageDirectory, packageName, value, conditions, mode, isImports, preferTsExtension) - if len(result) > 0 { - return result - } - } - } - case packagejson.JSONValueTypeNull: - return "" - } - return "" -} - -// `importingSourceFile` and `importingSourceFileName`? Why not just use `importingSourceFile.path`? -// Because when this is called by the declaration emitter, `importingSourceFile` is the implementation -// file, but `importingSourceFileName` and `toFileName` refer to declaration files (the former to the -// one currently being produced; the latter to the one being imported). We need an implementation file -// just to get its `impliedNodeFormat` and to detect certain preferences from existing import module -// specifiers. -func GetModuleSpecifier( - compilerOptions *core.CompilerOptions, - host ModuleSpecifierGenerationHost, - importingSourceFile *ast.SourceFile, // !!! | FutureSourceFile - importingSourceFileName string, - oldImportSpecifier string, // used only in updatingModuleSpecifier - toFileName string, - options ModuleSpecifierOptions, -) string { - userPreferences := UserPreferences{} - info := getInfo(importingSourceFileName, host) - modulePaths := getAllModulePaths(info, toFileName, host, compilerOptions, userPreferences, options) - preferences := getModuleSpecifierPreferences(userPreferences, host, compilerOptions, importingSourceFile, oldImportSpecifier) - - resolutionMode := options.OverrideImportMode - if resolutionMode == core.ResolutionModeNone { - resolutionMode = host.GetDefaultResolutionModeForFile(importingSourceFile) - } - - for _, modulePath := range modulePaths { - if firstDefined := tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, userPreferences, false /*packageNameOnly*/, options.OverrideImportMode); len(firstDefined) > 0 { - return firstDefined - } else if firstDefined := getLocalModuleSpecifier(toFileName, info, compilerOptions, host, resolutionMode, preferences, false); len(firstDefined) > 0 { - return firstDefined - } - } - return "" -} diff --git a/kitcom/internal/tsgo/modulespecifiers/types.go b/kitcom/internal/tsgo/modulespecifiers/types.go deleted file mode 100644 index dc3e63b..0000000 --- a/kitcom/internal/tsgo/modulespecifiers/types.go +++ /dev/null @@ -1,122 +0,0 @@ -package modulespecifiers - -import ( - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/core" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/module" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/packagejson" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tsoptions" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/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 PackageJsonInfo interface { - GetDirectory() string - GetContents() *packagejson.PackageJson -} - -type ModuleSpecifierGenerationHost interface { - // GetModuleResolutionCache() any // !!! TODO: adapt new resolution cache model - // GetSymlinkCache() any // !!! TODO: adapt new resolution cache model - // 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) PackageJsonInfo - 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 -) diff --git a/kitcom/internal/tsgo/modulespecifiers/util.go b/kitcom/internal/tsgo/modulespecifiers/util.go deleted file mode 100644 index a0284b2..0000000 --- a/kitcom/internal/tsgo/modulespecifiers/util.go +++ /dev/null @@ -1,323 +0,0 @@ -package modulespecifiers - -import ( - "fmt" - "slices" - "strings" - - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/core" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/module" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/packagejson" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/semver" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tsoptions" - "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" - "github.com/dlclark/regexp2" -) - -func isNonGlobalAmbientModule(node *ast.Node) bool { - return ast.IsModuleDeclaration(node) && ast.IsStringLiteral(node.Name()) -} - -func comparePathsByRedirectAndNumberOfDirectorySeparators(a ModulePath, b ModulePath) int { - if a.IsRedirect == b.IsRedirect { - return strings.Count(a.FileName, "/") - strings.Count(b.FileName, "/") - } - if a.IsRedirect { - return 1 - } - return -1 -} - -func PathIsBareSpecifier(path string) bool { - return !tspath.PathIsAbsolute(path) && !tspath.PathIsRelative(path) -} - -func isExcludedByRegex(moduleSpecifier string, excludes []string) bool { - for _, pattern := range excludes { - compiled, err := regexp2.Compile(pattern, regexp2.None) - if err != nil { - continue - } - match, _ := compiled.MatchString(moduleSpecifier) - if match { - return true - } - } - return false -} - -/** - * 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)] - } -} - -func getJSExtensionForFile(fileName string, options *core.CompilerOptions) string { - result := 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 tryGetJSExtensionForFile(fileName string, options *core.CompilerOptions) string { - ext := tspath.TryGetExtensionFromPath(fileName) - switch ext { - case tspath.ExtensionTs, tspath.ExtensionDts: - return tspath.ExtensionJs - case tspath.ExtensionTsx: - if options.Jsx == core.JsxEmitPreserve { - return tspath.ExtensionJsx - } - return tspath.ExtensionJs - case tspath.ExtensionJs, tspath.ExtensionJsx, tspath.ExtensionJson: - return ext - case tspath.ExtensionDmts, tspath.ExtensionMts, tspath.ExtensionMjs: - return tspath.ExtensionMjs - case tspath.ExtensionDcts, tspath.ExtensionCts, tspath.ExtensionCjs: - return tspath.ExtensionCjs - default: - return "" - } -} - -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 len(relativePath) > 0 && 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 -} - -var typeScriptVersion = semver.MustParse(core.Version()) // TODO: unify with clone inside module resolver? - -func isApplicableVersionedTypesKey(conditions []string, key string) bool { - if !slices.Contains(conditions, "types") { - return false // only apply versioned types conditions if the types condition is applied - } - if !strings.HasPrefix(key, "types@") { - return false - } - range_, ok := semver.TryParseVersionRange(key[len("types@"):]) - if !ok { - return false - } - return range_.Test(&typeScriptVersion) -} - -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 GetPackageNameFromTypesPackageName(mangledName string) string { - withoutAtTypePrefix := strings.TrimPrefix(mangledName, "@types/") - if withoutAtTypePrefix != mangledName { - return module.UnmangleScopedPackageName(withoutAtTypePrefix) - } - return mangledName -} - -func allKeysStartWithDot(obj *collections.OrderedMap[string, packagejson.ExportsOrImports]) bool { - for k := range obj.Keys() { - if !strings.HasPrefix(k, ".") { - return false - } - } - return true -}