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) } }) } }) }