package project import ( "sync" "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/collections" "github.com/microsoft/typescript-go/internal/compiler" "github.com/microsoft/typescript-go/internal/ls/autoimport" "github.com/microsoft/typescript-go/internal/packagejson" "github.com/microsoft/typescript-go/internal/tspath" "github.com/microsoft/typescript-go/internal/vfs" ) type autoImportBuilderFS struct { snapshotFSBuilder *snapshotFSBuilder untrackedFiles collections.SyncMap[tspath.Path, FileHandle] } var _ FileSource = (*autoImportBuilderFS)(nil) // FS implements FileSource. func (a *autoImportBuilderFS) FS() vfs.FS { return a.snapshotFSBuilder.fs } // GetFile implements FileSource. func (a *autoImportBuilderFS) GetFile(fileName string) FileHandle { path := a.snapshotFSBuilder.toPath(fileName) return a.GetFileByPath(fileName, path) } // GetFileByPath implements FileSource. func (a *autoImportBuilderFS) GetFileByPath(fileName string, path tspath.Path) FileHandle { // We want to avoid long-term caching of files referenced only by auto-imports, so we // override GetFileByPath to avoid collecting more files into the snapshotFSBuilder's // diskFiles. (Note the reason we can't just use the finalized SnapshotFS is that changed // files not read during other parts of the snapshot clone will be marked as dirty, but // not yet refreshed from disk.) if overlay, ok := a.snapshotFSBuilder.overlays[path]; ok { return overlay } if diskFile, ok := a.snapshotFSBuilder.diskFiles.Load(path); ok { return a.snapshotFSBuilder.reloadEntryIfNeeded(diskFile) } if fh, ok := a.untrackedFiles.Load(path); ok { return fh } var fh FileHandle content, ok := a.snapshotFSBuilder.fs.ReadFile(fileName) if ok { fh = newDiskFile(fileName, content) } fh, _ = a.untrackedFiles.LoadOrStore(path, fh) return fh } func (a *autoImportBuilderFS) GetAccessibleEntries(path string) vfs.Entries { return a.snapshotFSBuilder.GetAccessibleEntries(path) } // FileExists implements FileSource. func (a *autoImportBuilderFS) FileExists(fileName string, path tspath.Path) bool { return a.snapshotFSBuilder.FileExists(fileName, path) } type autoImportRegistryCloneHost struct { projectCollection *ProjectCollection parseCache *ParseCache fs *sourceFS currentDirectory string filesMu sync.Mutex files []ParseCacheKey } var _ autoimport.RegistryCloneHost = (*autoImportRegistryCloneHost)(nil) func newAutoImportRegistryCloneHost( projectCollection *ProjectCollection, parseCache *ParseCache, snapshotFSBuilder *snapshotFSBuilder, currentDirectory string, toPath func(fileName string) tspath.Path, ) *autoImportRegistryCloneHost { return &autoImportRegistryCloneHost{ projectCollection: projectCollection, parseCache: parseCache, fs: newSourceFS(false, &autoImportBuilderFS{snapshotFSBuilder: snapshotFSBuilder}, toPath), currentDirectory: currentDirectory, } } // FS implements autoimport.RegistryCloneHost. func (a *autoImportRegistryCloneHost) FS() vfs.FS { return a.fs } // GetCurrentDirectory implements autoimport.RegistryCloneHost. func (a *autoImportRegistryCloneHost) GetCurrentDirectory() string { return a.currentDirectory } // GetDefaultProject implements autoimport.RegistryCloneHost. func (a *autoImportRegistryCloneHost) GetDefaultProject(path tspath.Path) (tspath.Path, *compiler.Program) { project := a.projectCollection.GetDefaultProject(path) if project == nil { return "", nil } return project.configFilePath, project.GetProgram() } // GetPackageJson implements autoimport.RegistryCloneHost. func (a *autoImportRegistryCloneHost) GetPackageJson(fileName string) *packagejson.InfoCacheEntry { // !!! ref-counted shared cache fh := a.fs.GetFile(fileName) packageDirectory := tspath.GetDirectoryPath(fileName) if fh == nil { return &packagejson.InfoCacheEntry{ DirectoryExists: a.fs.DirectoryExists(packageDirectory), PackageDirectory: packageDirectory, } } fields, err := packagejson.Parse([]byte(fh.Content())) if err != nil { return &packagejson.InfoCacheEntry{ DirectoryExists: true, PackageDirectory: tspath.GetDirectoryPath(fileName), Contents: &packagejson.PackageJson{ Parseable: false, }, } } return &packagejson.InfoCacheEntry{ DirectoryExists: true, PackageDirectory: tspath.GetDirectoryPath(fileName), Contents: &packagejson.PackageJson{ Fields: fields, Parseable: true, }, } } // GetProgramForProject implements autoimport.RegistryCloneHost. func (a *autoImportRegistryCloneHost) GetProgramForProject(projectPath tspath.Path) *compiler.Program { project := a.projectCollection.GetProjectByPath(projectPath) if project == nil { return nil } return project.GetProgram() } // GetSourceFile implements autoimport.RegistryCloneHost. func (a *autoImportRegistryCloneHost) GetSourceFile(fileName string, path tspath.Path) *ast.SourceFile { fh := a.fs.GetFile(fileName) if fh == nil { return nil } opts := ast.SourceFileParseOptions{ FileName: fileName, Path: path, } key := NewParseCacheKey(opts, fh.Hash(), fh.Kind()) result := a.parseCache.Acquire(key, fh) a.filesMu.Lock() a.files = append(a.files, key) a.filesMu.Unlock() return result } // Dispose implements autoimport.RegistryCloneHost. func (a *autoImportRegistryCloneHost) Dispose() { a.filesMu.Lock() defer a.filesMu.Unlock() for _, key := range a.files { a.parseCache.Deref(key) } }