vendor tsgo

This commit is contained in:
2026-07-09 16:50:43 -04:00
parent c06ea2e5a4
commit 98978e4930
5804 changed files with 1556156 additions and 101 deletions

View File

@@ -0,0 +1,134 @@
package symlinks
import (
"strings"
"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/tspath"
)
type KnownDirectoryLink struct {
// Matches the casing returned by `realpath`. Used to compute the `realpath` of children.
// Always has trailing directory separator
Real string
// toPath(real). Stored to avoid repeated recomputation.
// Always has trailing directory separator
RealPath tspath.Path
}
type KnownSymlinks struct {
directories collections.SyncMap[tspath.Path, *KnownDirectoryLink]
directoriesByRealpath collections.SyncMap[tspath.Path, *collections.SyncSet[string]]
files collections.SyncMap[tspath.Path, string]
filesByRealpath collections.SyncMap[tspath.Path, *collections.SyncSet[string]]
cwd string
useCaseSensitiveFileNames bool
}
func (cache *KnownSymlinks) HasDirectory(symlinkPath tspath.Path) bool {
_, ok := cache.directories.Load(symlinkPath.EnsureTrailingDirectorySeparator())
return ok
}
// Gets a map from symlink to realpath. Keys have trailing directory separators.
func (cache *KnownSymlinks) Directories() *collections.SyncMap[tspath.Path, *KnownDirectoryLink] {
return &cache.directories
}
func (cache *KnownSymlinks) DirectoriesByRealpath() *collections.SyncMap[tspath.Path, *collections.SyncSet[string]] {
return &cache.directoriesByRealpath
}
// Gets a map from symlink to realpath
func (cache *KnownSymlinks) Files() *collections.SyncMap[tspath.Path, string] {
return &cache.files
}
// Gets a map from realpath to symlinks
func (cache *KnownSymlinks) FilesByRealpath() *collections.SyncMap[tspath.Path, *collections.SyncSet[string]] {
return &cache.filesByRealpath
}
func (cache *KnownSymlinks) SetDirectory(symlink string, symlinkPath tspath.Path, realDirectory *KnownDirectoryLink) {
if realDirectory != nil {
if _, ok := cache.directories.Load(symlinkPath); !ok {
set, _ := cache.directoriesByRealpath.LoadOrStore(realDirectory.RealPath, &collections.SyncSet[string]{})
set.Add(symlink)
}
}
cache.directories.Store(symlinkPath, realDirectory)
}
func (cache *KnownSymlinks) SetFile(symlink string, symlinkPath tspath.Path, realpath string) {
if _, ok := cache.files.Load(symlinkPath); !ok {
realpathPath := tspath.ToPath(realpath, cache.cwd, cache.useCaseSensitiveFileNames)
set, _ := cache.filesByRealpath.LoadOrStore(realpathPath, &collections.SyncSet[string]{})
set.Add(symlink)
}
cache.files.Store(symlinkPath, realpath)
}
func NewKnownSymlink(currentDirectory string, useCaseSensitiveFileNames bool) *KnownSymlinks {
return &KnownSymlinks{
cwd: currentDirectory,
useCaseSensitiveFileNames: useCaseSensitiveFileNames,
}
}
func (cache *KnownSymlinks) SetSymlinksFromResolutions(
forEachResolvedModule func(callback func(resolution *module.ResolvedModule, moduleName string, mode core.ResolutionMode, filePath tspath.Path), file *ast.SourceFile),
forEachResolvedTypeReferenceDirective func(callback func(resolution *module.ResolvedTypeReferenceDirective, moduleName string, mode core.ResolutionMode, filePath tspath.Path), file *ast.SourceFile),
) {
forEachResolvedModule(func(resolution *module.ResolvedModule, moduleName string, mode core.ResolutionMode, filePath tspath.Path) {
cache.ProcessResolution(resolution.OriginalPath, resolution.ResolvedFileName)
}, nil)
forEachResolvedTypeReferenceDirective(func(resolution *module.ResolvedTypeReferenceDirective, moduleName string, mode core.ResolutionMode, filePath tspath.Path) {
cache.ProcessResolution(resolution.OriginalPath, resolution.ResolvedFileName)
}, nil)
}
func (cache *KnownSymlinks) ProcessResolution(originalPath string, resolvedFileName string) {
if originalPath == "" || resolvedFileName == "" {
return
}
cache.SetFile(originalPath, tspath.ToPath(originalPath, cache.cwd, cache.useCaseSensitiveFileNames), resolvedFileName)
commonResolved, commonOriginal := cache.guessDirectorySymlink(resolvedFileName, originalPath, cache.cwd)
if commonResolved != "" && commonOriginal != "" {
symlinkPath := tspath.ToPath(commonOriginal, cache.cwd, cache.useCaseSensitiveFileNames)
if !tspath.ContainsIgnoredPath(string(symlinkPath)) {
cache.SetDirectory(
commonOriginal,
symlinkPath.EnsureTrailingDirectorySeparator(),
&KnownDirectoryLink{
Real: tspath.EnsureTrailingDirectorySeparator(commonResolved),
RealPath: tspath.ToPath(commonResolved, cache.cwd, cache.useCaseSensitiveFileNames).EnsureTrailingDirectorySeparator(),
},
)
}
}
}
func (cache *KnownSymlinks) guessDirectorySymlink(a string, b string, cwd string) (string, string) {
aParts := tspath.GetPathComponents(tspath.GetNormalizedAbsolutePath(a, cwd), "")
bParts := tspath.GetPathComponents(tspath.GetNormalizedAbsolutePath(b, cwd), "")
isDirectory := false
for len(aParts) >= 2 && len(bParts) >= 2 &&
!cache.isNodeModulesOrScopedPackageDirectory(aParts[len(aParts)-2]) &&
!cache.isNodeModulesOrScopedPackageDirectory(bParts[len(bParts)-2]) &&
tspath.GetCanonicalFileName(aParts[len(aParts)-1], cache.useCaseSensitiveFileNames) == tspath.GetCanonicalFileName(bParts[len(bParts)-1], cache.useCaseSensitiveFileNames) {
aParts = aParts[:len(aParts)-1]
bParts = bParts[:len(bParts)-1]
isDirectory = true
}
if isDirectory {
return tspath.GetPathFromPathComponents(aParts), tspath.GetPathFromPathComponents(bParts)
}
return "", ""
}
func (cache *KnownSymlinks) isNodeModulesOrScopedPackageDirectory(s string) bool {
return s != "" && (tspath.GetCanonicalFileName(s, cache.useCaseSensitiveFileNames) == "node_modules" || strings.HasPrefix(s, "@"))
}

View File

@@ -0,0 +1,73 @@
package symlinks
import (
"testing"
"github.com/microsoft/typescript-go/internal/tspath"
)
func BenchmarkPopulateSymlinksFromResolutions(b *testing.B) {
cache := NewKnownSymlink("/project", true)
deps := make([]struct{ orig, resolved string }, 50)
for i := range 50 {
deps[i].orig = "/project/node_modules/pkg" + string(rune('A'+i)) + "/index.js"
deps[i].resolved = "/real/pkg" + string(rune('A'+i)) + "/index.js"
}
for b.Loop() {
for _, dep := range deps {
cache.ProcessResolution(dep.orig, dep.resolved)
}
}
}
func BenchmarkSetFile(b *testing.B) {
cache := NewKnownSymlink("/project", true)
symlink := "/project/file.ts"
path := tspath.ToPath(symlink, "/project", true)
for b.Loop() {
cache.SetFile(symlink, path, "/real/file.ts")
}
}
func BenchmarkSetDirectory(b *testing.B) {
cache := NewKnownSymlink("/project", true)
symlinkPath := tspath.ToPath("/project/symlink", "/project", true).EnsureTrailingDirectorySeparator()
realDir := &KnownDirectoryLink{
Real: "/real/path/",
RealPath: tspath.ToPath("/real/path", "/project", true).EnsureTrailingDirectorySeparator(),
}
for b.Loop() {
cache.SetDirectory("/project/symlink", symlinkPath, realDir)
}
}
func BenchmarkGuessDirectorySymlink(b *testing.B) {
cache := NewKnownSymlink("/project", true)
for b.Loop() {
cache.guessDirectorySymlink(
"/real/node_modules/package/dist/index.js",
"/project/symlink/package/dist/index.js",
"/project",
)
}
}
func BenchmarkConcurrentAccess(b *testing.B) {
cache := NewKnownSymlink("/project", true)
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
symlink := "/project/file" + string(rune('A'+(i%26))) + ".ts"
path := tspath.ToPath(symlink, "/project", true)
cache.SetFile(symlink, path, "/real/file.ts")
cache.Files().Load(path)
i++
}
})
}

View File

@@ -0,0 +1,291 @@
package symlinks
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/tspath"
)
func TestNewKnownSymlink(t *testing.T) {
t.Parallel()
cache := NewKnownSymlink("/test/dir", true)
if cache == nil {
t.Fatal("Expected non-nil cache")
}
if cache.cwd != "/test/dir" {
t.Errorf("Expected cwd to be '/test/dir', got '%s'", cache.cwd)
}
if !cache.useCaseSensitiveFileNames {
t.Error("Expected useCaseSensitiveFileNames to be true")
}
}
func TestSetDirectory(t *testing.T) {
t.Parallel()
cache := NewKnownSymlink("/test/dir", true)
symlinkPath := tspath.ToPath("/test/symlink", "/test/dir", true).EnsureTrailingDirectorySeparator()
realDirectory := &KnownDirectoryLink{
Real: "/real/path/",
RealPath: tspath.ToPath("/real/path", "/test/dir", true).EnsureTrailingDirectorySeparator(),
}
cache.SetDirectory("/test/symlink", symlinkPath, realDirectory)
// Check that directory was stored
stored, ok := cache.Directories().Load(symlinkPath)
if !ok {
t.Fatal("Expected directory to be stored")
}
if stored.Real != realDirectory.Real {
t.Errorf("Expected Real to be '%s', got '%s'", realDirectory.Real, stored.Real)
}
if stored.RealPath != realDirectory.RealPath {
t.Errorf("Expected RealPath to be '%s', got '%s'", realDirectory.RealPath, stored.RealPath)
}
// Check that realpath mapping was created
set, ok := cache.DirectoriesByRealpath().Load(realDirectory.RealPath)
if !ok || set.Size() == 0 {
t.Fatal("Expected realpath mapping to be created")
}
if !set.Has("/test/symlink") {
t.Error("Expected symlink '/test/symlink' to be in set")
}
}
func TestSetFile(t *testing.T) {
t.Parallel()
cache := NewKnownSymlink("/test/dir", true)
symlink := "/test/symlink/file.ts"
symlinkPath := tspath.ToPath(symlink, "/test/dir", true)
realpath := "/real/path/file.ts"
cache.SetFile(symlink, symlinkPath, realpath)
stored, ok := cache.Files().Load(symlinkPath)
if !ok {
t.Fatal("Expected file to be stored")
}
if stored != realpath {
t.Errorf("Expected realpath to be '%s', got '%s'", realpath, stored)
}
}
func TestProcessResolution(t *testing.T) {
t.Parallel()
cache := NewKnownSymlink("/test/dir", true)
// Test with empty paths
cache.ProcessResolution("", "")
cache.ProcessResolution("original", "")
cache.ProcessResolution("", "resolved")
// Test with valid paths
originalPath := "/test/original/file.ts"
resolvedPath := "/test/resolved/file.ts"
cache.ProcessResolution(originalPath, resolvedPath)
// Check that file was stored
symlinkPath := tspath.ToPath(originalPath, "/test/dir", true)
stored, ok := cache.Files().Load(symlinkPath)
if !ok {
t.Fatal("Expected file to be stored")
}
if stored != resolvedPath {
t.Errorf("Expected resolved path to be '%s', got '%s'", resolvedPath, stored)
}
}
func TestGuessDirectorySymlink(t *testing.T) {
t.Parallel()
cache := NewKnownSymlink("/test/dir", true)
tests := []struct {
name string
a string
b string
cwd string
expected [2]string // [commonResolved, commonOriginal]
}{
{
name: "identical paths",
a: "/test/path/file.ts",
b: "/test/path/file.ts",
cwd: "/test/dir",
expected: [2]string{"/", "/"},
},
{
name: "different files same directory",
a: "/test/path/file1.ts",
b: "/test/path/file2.ts",
cwd: "/test/dir",
expected: [2]string{"", ""},
},
{
name: "different directories",
a: "/test/path1/file.ts",
b: "/test/path2/file.ts",
cwd: "/test/dir",
expected: [2]string{"/test/path1", "/test/path2"},
},
{
name: "node_modules paths",
a: "/test/node_modules/pkg/file.ts",
b: "/test/node_modules/pkg/file.ts",
cwd: "/test/dir",
expected: [2]string{"/test/node_modules/pkg", "/test/node_modules/pkg"},
},
{
name: "scoped package paths",
a: "/test/node_modules/@scope/pkg/file.ts",
b: "/test/node_modules/@scope/pkg/file.ts",
cwd: "/test/dir",
expected: [2]string{"/test/node_modules/@scope/pkg", "/test/node_modules/@scope/pkg"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
commonResolved, commonOriginal := cache.guessDirectorySymlink(tt.a, tt.b, tt.cwd)
if commonResolved != tt.expected[0] {
t.Errorf("Expected commonResolved to be '%s', got '%s'", tt.expected[0], commonResolved)
}
if commonOriginal != tt.expected[1] {
t.Errorf("Expected commonOriginal to be '%s', got '%s'", tt.expected[1], commonOriginal)
}
})
}
}
func TestIsNodeModulesOrScopedPackageDirectory(t *testing.T) {
t.Parallel()
cache := NewKnownSymlink("/test/dir", true)
tests := []struct {
name string
dir string
expected bool
}{
{"node_modules", "node_modules", true},
{"scoped package", "@scope", true},
{"regular directory", "src", false},
{"empty string", "", false},
{"case insensitive node_modules", "NODE_MODULES", false}, // The function is case sensitive
{"case insensitive scoped", "@SCOPE", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
result := cache.isNodeModulesOrScopedPackageDirectory(tt.dir)
if result != tt.expected {
t.Errorf("Expected %v, got %v for directory '%s'", tt.expected, result, tt.dir)
}
})
}
}
func TestSetSymlinksFromResolutions(t *testing.T) {
t.Parallel()
cache := NewKnownSymlink("/test/dir", true)
// Mock resolution data
resolvedModules := []struct {
originalPath string
resolvedPath string
moduleName string
mode core.ResolutionMode
filePath tspath.Path
}{
{
originalPath: "/test/original/file1.ts",
resolvedPath: "/test/resolved/file1.ts",
moduleName: "module1",
mode: core.ResolutionModeNone,
filePath: tspath.ToPath("/test/source.ts", "/test/dir", true),
},
{
originalPath: "/test/original/file2.ts",
resolvedPath: "/test/resolved/file2.ts",
moduleName: "module2",
mode: core.ResolutionModeNone,
filePath: tspath.ToPath("/test/source.ts", "/test/dir", true),
},
}
// Mock callbacks
forEachResolvedModule := func(callback func(resolution *module.ResolvedModule, moduleName string, mode core.ResolutionMode, filePath tspath.Path), file *ast.SourceFile) {
for _, res := range resolvedModules {
resolution := &module.ResolvedModule{
OriginalPath: res.originalPath,
ResolvedFileName: res.resolvedPath,
}
callback(resolution, res.moduleName, res.mode, res.filePath)
}
}
forEachResolvedTypeReferenceDirective := func(callback func(resolution *module.ResolvedTypeReferenceDirective, moduleName string, mode core.ResolutionMode, filePath tspath.Path), file *ast.SourceFile) {
// No type reference directives for this test
}
cache.SetSymlinksFromResolutions(forEachResolvedModule, forEachResolvedTypeReferenceDirective)
// Check that files were stored
for _, res := range resolvedModules {
symlinkPath := tspath.ToPath(res.originalPath, "/test/dir", true)
stored, ok := cache.Files().Load(symlinkPath)
if !ok {
t.Errorf("Expected file '%s' to be stored", res.originalPath)
continue
}
if stored != res.resolvedPath {
t.Errorf("Expected resolved path to be '%s', got '%s'", res.resolvedPath, stored)
}
}
}
func TestKnownSymlinksThreadSafety(t *testing.T) {
t.Parallel()
cache := NewKnownSymlink("/test/dir", true)
// Test concurrent access
done := make(chan bool, 10)
for i := range 10 {
go func(id int) {
defer func() { done <- true }()
symlinkPath := tspath.ToPath("/test/symlink"+string(rune(id)), "/test/dir", true).EnsureTrailingDirectorySeparator()
realDirectory := &KnownDirectoryLink{
Real: "/real/path" + string(rune(id)) + "/",
RealPath: tspath.ToPath("/real/path"+string(rune(id)), "/test/dir", true).EnsureTrailingDirectorySeparator(),
}
cache.SetDirectory("/test/symlink"+string(rune(id)), symlinkPath, realDirectory)
// Read back
stored, ok := cache.Directories().Load(symlinkPath)
if !ok {
t.Errorf("Goroutine %d: Expected directory to be stored", id)
return
}
if stored.Real != realDirectory.Real {
t.Errorf("Goroutine %d: Expected Real to be '%s', got '%s'", id, realDirectory.Real, stored.Real)
}
}(i)
}
// Wait for all goroutines to complete
for range 10 {
<-done
}
// Verify all directories were stored
if cache.Directories().Size() != 10 {
t.Errorf("Expected 10 directories to be stored, got %d", cache.Directories().Size())
}
}