From c35d2bd2e8c3c42fdd50ea4208911676a88c3c2e Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 17 Jun 2025 13:19:47 -0700 Subject: [PATCH 1/3] Watch type reference locations too and handle auto type refs --- internal/compiler/fileloader.go | 57 +++++++++++++++++---------------- internal/compiler/parsetask.go | 28 +++++++++++----- internal/compiler/program.go | 4 +++ internal/module/types.go | 8 +++++ internal/project/project.go | 22 ++++++++++--- 5 files changed, 80 insertions(+), 39 deletions(-) diff --git a/internal/compiler/fileloader.go b/internal/compiler/fileloader.go index 1751edc63e..4d20561070 100644 --- a/internal/compiler/fileloader.go +++ b/internal/compiler/fileloader.go @@ -113,10 +113,16 @@ func processAllProgramFiles( var unsupportedExtensions []string loader.parseTasks.collect(&loader, loader.rootTasks, func(task *parseTask, _ []tspath.Path) { - file := task.file if task.isRedirected { return } + + if task.isForAutomaticTypeDirective { + typeResolutionsInFile[task.path] = task.typeResolutionsInFile + return + } + file := task.file + path := task.path if file == nil { missingFiles = append(missingFiles, task.normalizedFilePath) return @@ -126,7 +132,6 @@ func processAllProgramFiles( } else { files = append(files, file) } - path := file.Path() filesByPath[path] = file resolvedModules[path] = task.resolutionsInFile @@ -189,14 +194,31 @@ func (p *fileLoader) addAutomaticTypeDirectiveTasks() { containingDirectory = p.opts.Host.GetCurrentDirectory() } containingFileName := tspath.CombinePaths(containingDirectory, module.InferredTypesContainingFile) + p.rootTasks = append(p.rootTasks, &parseTask{normalizedFilePath: containingFileName, isLib: false, isForAutomaticTypeDirective: true}) +} - automaticTypeDirectiveNames := module.GetAutomaticTypeDirectiveNames(compilerOptions, p.opts.Host) - for _, name := range automaticTypeDirectiveNames { - resolved := p.resolver.ResolveTypeReferenceDirective(name, containingFileName, core.ModuleKindNodeNext, nil) - if resolved.IsResolved() { - p.rootTasks = append(p.rootTasks, &parseTask{normalizedFilePath: resolved.ResolvedFileName, isLib: false}) +func (p *fileLoader) resolveAutomaticTypeDirectives(containingFileName string) ( + toParse []resolvedRef, + typeResolutionsInFile module.ModeAwareCache[*module.ResolvedTypeReferenceDirective], +) { + automaticTypeDirectiveNames := module.GetAutomaticTypeDirectiveNames(p.opts.Config.CompilerOptions(), p.opts.Host) + if len(automaticTypeDirectiveNames) != 0 { + toParse = make([]resolvedRef, 0, len(automaticTypeDirectiveNames)) + typeResolutionsInFile = make(module.ModeAwareCache[*module.ResolvedTypeReferenceDirective], len(automaticTypeDirectiveNames)) + for _, name := range automaticTypeDirectiveNames { + resolutionMode := core.ModuleKindNodeNext + resolved := p.resolver.ResolveTypeReferenceDirective(name, containingFileName, resolutionMode, nil) + typeResolutionsInFile[module.ModeAwareCacheKey{Name: name, Mode: resolutionMode}] = resolved + if resolved.IsResolved() { + toParse = append(toParse, resolvedRef{ + fileName: resolved.ResolvedFileName, + increaseDepth: resolved.IsExternalLibraryImport, + elideOnDepth: false, + }) + } } } + return toParse, typeResolutionsInFile } func (p *fileLoader) addProjectReferenceTasks() { @@ -382,7 +404,7 @@ func (p *fileLoader) resolveImportsAndModuleAugmentations(file *ast.SourceFile, continue } - mode := getModeForUsageLocation(file.FileName(), meta, entry, module.GetCompilerOptionsWithRedirect(p.opts.Config.CompilerOptions(), redirect)) + mode := getModeForUsageLocation(file.FileName(), meta, entry, optionsForFile) resolvedModule := p.resolver.ResolveModuleName(moduleName, file.FileName(), mode, redirect) resolutionsInFile[module.ModeAwareCacheKey{Name: moduleName, Mode: mode}] = resolvedModule @@ -474,25 +496,6 @@ func getResolutionDiagnostic(options *core.CompilerOptions, resolvedModule *modu } } -func (p *fileLoader) resolveModuleNames(entries []*ast.Node, file *ast.SourceFile, meta ast.SourceFileMetaData, redirect *tsoptions.ParsedCommandLine) []*resolution { - if len(entries) == 0 { - return nil - } - - resolvedModules := make([]*resolution, 0, len(entries)) - - for _, entry := range entries { - moduleName := entry.Text() - if moduleName == "" { - continue - } - resolvedModule := p.resolver.ResolveModuleName(moduleName, file.FileName(), getModeForUsageLocation(file.FileName(), meta, entry, module.GetCompilerOptionsWithRedirect(p.opts.Config.CompilerOptions(), redirect)), redirect) - resolvedModules = append(resolvedModules, &resolution{node: entry, resolvedModule: resolvedModule}) - } - - return resolvedModules -} - func (p *fileLoader) createSyntheticImport(text string, file *ast.SourceFile) *ast.Node { p.factoryMu.Lock() defer p.factoryMu.Unlock() diff --git a/internal/compiler/parsetask.go b/internal/compiler/parsetask.go index fab536949f..5ff235e239 100644 --- a/internal/compiler/parsetask.go +++ b/internal/compiler/parsetask.go @@ -9,13 +9,14 @@ import ( ) type parseTask struct { - normalizedFilePath string - path tspath.Path - file *ast.SourceFile - isLib bool - isRedirected bool - subTasks []*parseTask - loaded bool + normalizedFilePath string + path tspath.Path + file *ast.SourceFile + isLib bool + isRedirected bool + subTasks []*parseTask + loaded bool + isForAutomaticTypeDirective bool metadata ast.SourceFileMetaData resolutionsInFile module.ModeAwareCache[*module.ResolvedModule] @@ -36,8 +37,11 @@ func (t *parseTask) Path() tspath.Path { func (t *parseTask) load(loader *fileLoader) { t.loaded = true - t.path = loader.toPath(t.normalizedFilePath) + if t.isForAutomaticTypeDirective { + t.loadAutomaticTypeDirectives(loader) + return + } redirect := loader.projectReferenceFileMapper.getParseFileRedirect(t) if redirect != "" { t.redirect(loader, redirect) @@ -97,6 +101,14 @@ func (t *parseTask) redirect(loader *fileLoader, fileName string) { t.subTasks = []*parseTask{{normalizedFilePath: tspath.NormalizePath(fileName), isLib: t.isLib}} } +func (t *parseTask) loadAutomaticTypeDirectives(loader *fileLoader) { + toParseTypeRefs, typeResolutionsInFile := loader.resolveAutomaticTypeDirectives(t.normalizedFilePath) + t.typeResolutionsInFile = typeResolutionsInFile + for _, typeResolution := range toParseTypeRefs { + t.addSubTask(typeResolution, false) + } +} + type resolvedRef struct { fileName string increaseDepth bool diff --git a/internal/compiler/program.go b/internal/compiler/program.go index 8928bba703..0b0a2a62bd 100644 --- a/internal/compiler/program.go +++ b/internal/compiler/program.go @@ -813,6 +813,10 @@ func (p *Program) GetResolvedTypeReferenceDirectiveFromTypeReferenceDirective(ty return nil } +func (p *Program) GetResolvedTypeReferenceDirectives() map[tspath.Path]module.ModeAwareCache[*module.ResolvedTypeReferenceDirective] { + return p.typeResolutionsInFile +} + func (p *Program) getModeForTypeReferenceDirectiveInFile(ref *ast.FileReference, sourceFile *ast.SourceFile) core.ResolutionMode { if ref.ResolutionMode != core.ResolutionModeNone { return ref.ResolutionMode diff --git a/internal/module/types.go b/internal/module/types.go index 5077d6e568..d0acc036da 100644 --- a/internal/module/types.go +++ b/internal/module/types.go @@ -81,6 +81,10 @@ func (r *ResolvedModule) IsResolved() bool { return r != nil && r.ResolvedFileName != "" } +func (r *ResolvedModule) GetLookupLocations() *LookupLocations { + return &r.LookupLocations +} + type ResolvedTypeReferenceDirective struct { LookupLocations Primary bool @@ -94,6 +98,10 @@ func (r *ResolvedTypeReferenceDirective) IsResolved() bool { return r.ResolvedFileName != "" } +func (r *ResolvedTypeReferenceDirective) GetLookupLocations() *LookupLocations { + return &r.LookupLocations +} + type extensions int32 const ( diff --git a/internal/project/project.go b/internal/project/project.go index 3ed15c5096..3d4e5c9b98 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -353,15 +353,30 @@ func (p *Project) GetLanguageServiceForRequest(ctx context.Context) (*ls.Languag func (p *Project) getModuleResolutionWatchGlobs() (failedLookups map[tspath.Path]string, affectingLocaions map[tspath.Path]string) { failedLookups = make(map[tspath.Path]string) affectingLocaions = make(map[tspath.Path]string) - for _, resolvedModulesInFile := range p.program.GetResolvedModules() { + extractLookups(p, failedLookups, affectingLocaions, p.program.GetResolvedModules()) + extractLookups(p, failedLookups, affectingLocaions, p.program.GetResolvedTypeReferenceDirectives()) + return failedLookups, affectingLocaions +} + +type ResolutionWithLookupLocations interface { + GetLookupLocations() *module.LookupLocations +} + +func extractLookups[T ResolutionWithLookupLocations]( + p *Project, + failedLookups map[tspath.Path]string, + affectingLocaions map[tspath.Path]string, + cache map[tspath.Path]module.ModeAwareCache[T], +) { + for _, resolvedModulesInFile := range cache { for _, resolvedModule := range resolvedModulesInFile { - for _, failedLookupLocation := range resolvedModule.FailedLookupLocations { + for _, failedLookupLocation := range resolvedModule.GetLookupLocations().FailedLookupLocations { path := p.toPath(failedLookupLocation) if _, ok := failedLookups[path]; !ok { failedLookups[path] = failedLookupLocation } } - for _, affectingLocation := range resolvedModule.AffectingLocations { + for _, affectingLocation := range resolvedModule.GetLookupLocations().AffectingLocations { path := p.toPath(affectingLocation) if _, ok := affectingLocaions[path]; !ok { affectingLocaions[path] = affectingLocation @@ -369,7 +384,6 @@ func (p *Project) getModuleResolutionWatchGlobs() (failedLookups map[tspath.Path } } } - return failedLookups, affectingLocaions } func (p *Project) updateWatchers(ctx context.Context) { From ad93588a820dd1c7226b14c7bd5030325ecb653d Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 17 Jun 2025 14:16:01 -0700 Subject: [PATCH 2/3] Watch in a separate routine instead of doing it directly --- internal/project/ata.go | 2 +- internal/project/project.go | 54 +++++++++---------------------------- internal/project/watch.go | 38 ++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 43 deletions(-) diff --git a/internal/project/ata.go b/internal/project/ata.go index d27f8f9711..0c6b686921 100644 --- a/internal/project/ata.go +++ b/internal/project/ata.go @@ -158,7 +158,7 @@ func (ti *TypingsInstaller) discoverAndInstallTypings(p *Project, typingsInfo *T ) // start watching files - p.WatchTypingLocations(filesToWatch) + go p.WatchTypingLocations(filesToWatch) requestId := ti.installRunCount.Add(1) // install typings diff --git a/internal/project/project.go b/internal/project/project.go index 3d4e5c9b98..8c7315bd67 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -167,6 +167,7 @@ type Project struct { typingFiles []string // Watchers + watchMu sync.RWMutex failedLookupsWatch *watchedFiles[map[tspath.Path]string] affectingLocationsWatch *watchedFiles[map[tspath.Path]string] typingsFilesWatch *watchedFiles[map[tspath.Path]string] @@ -350,49 +351,15 @@ func (p *Project) GetLanguageServiceForRequest(ctx context.Context) (*ls.Languag return languageService, cleanup } -func (p *Project) getModuleResolutionWatchGlobs() (failedLookups map[tspath.Path]string, affectingLocaions map[tspath.Path]string) { - failedLookups = make(map[tspath.Path]string) - affectingLocaions = make(map[tspath.Path]string) - extractLookups(p, failedLookups, affectingLocaions, p.program.GetResolvedModules()) - extractLookups(p, failedLookups, affectingLocaions, p.program.GetResolvedTypeReferenceDirectives()) - return failedLookups, affectingLocaions -} - -type ResolutionWithLookupLocations interface { - GetLookupLocations() *module.LookupLocations -} - -func extractLookups[T ResolutionWithLookupLocations]( - p *Project, - failedLookups map[tspath.Path]string, - affectingLocaions map[tspath.Path]string, - cache map[tspath.Path]module.ModeAwareCache[T], -) { - for _, resolvedModulesInFile := range cache { - for _, resolvedModule := range resolvedModulesInFile { - for _, failedLookupLocation := range resolvedModule.GetLookupLocations().FailedLookupLocations { - path := p.toPath(failedLookupLocation) - if _, ok := failedLookups[path]; !ok { - failedLookups[path] = failedLookupLocation - } - } - for _, affectingLocation := range resolvedModule.GetLookupLocations().AffectingLocations { - path := p.toPath(affectingLocation) - if _, ok := affectingLocaions[path]; !ok { - affectingLocaions[path] = affectingLocation - } - } - } - } -} - -func (p *Project) updateWatchers(ctx context.Context) { +func (p *Project) updateWatchers(ctx context.Context, program *compiler.Program) { client := p.Client() if !p.host.IsWatchEnabled() || client == nil { return } - failedLookupGlobs, affectingLocationGlobs := p.getModuleResolutionWatchGlobs() + failedLookupGlobs, affectingLocationGlobs := getModuleResolutionWatchGlobs(program) + p.watchMu.Lock() + defer p.watchMu.Unlock() p.failedLookupsWatch.update(ctx, failedLookupGlobs) p.affectingLocationsWatch.update(ctx, affectingLocationGlobs) } @@ -406,6 +373,8 @@ func (p *Project) updateWatchers(ctx context.Context) { // part of the project, e.g., a .js file in a project without --allowJs. func (p *Project) onWatchEventForNilScriptInfo(fileName string) { path := p.toPath(fileName) + p.watchMu.RLock() + defer p.watchMu.RUnlock() if _, ok := p.failedLookupsWatch.data[path]; ok { p.markAsDirty() } else if _, ok := p.affectingLocationsWatch.data[path]; ok { @@ -545,9 +514,7 @@ func (p *Project) updateGraph() (*compiler.Program, bool) { }) } p.enqueueInstallTypingsForProject(oldProgram, hasAddedOrRemovedFiles) - // TODO: this is currently always synchronously called by some kind of updating request, - // but in Strada we throttle, so at least sometimes this should be considered top-level? - p.updateWatchers(context.TODO()) + go p.updateWatchers(context.TODO(), p.program) } p.Logf("Finishing updateGraph: Project: %s version: %d in %s", p.name, p.version, time.Since(start)) return p.program, true @@ -785,10 +752,11 @@ func (p *Project) UpdateTypingFiles(typingsInfo *TypingsInfo, typingFiles []stri func (p *Project) WatchTypingLocations(files []string) { p.mu.Lock() - defer p.mu.Unlock() if p.isClosed() { + p.mu.Unlock() return } + p.mu.Unlock() client := p.Client() if !p.host.IsWatchEnabled() || client == nil { @@ -834,6 +802,8 @@ func (p *Project) WatchTypingLocations(files []string) { } } ctx := context.Background() + p.watchMu.Lock() + defer p.watchMu.Unlock() p.typingsFilesWatch.update(ctx, typingsInstallerFileGlobs) p.typingsDirectoryWatch.update(ctx, typingsInstallerDirectoryGlobs) } diff --git a/internal/project/watch.go b/internal/project/watch.go index be463cec9e..a37d5c927f 100644 --- a/internal/project/watch.go +++ b/internal/project/watch.go @@ -9,7 +9,9 @@ import ( "time" "github.com/microsoft/typescript-go/internal/collections" + "github.com/microsoft/typescript-go/internal/compiler" "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/module" "github.com/microsoft/typescript-go/internal/tspath" ) @@ -362,3 +364,39 @@ func isInDirectoryPath(dirComponents []string, fileOrDirComponents []string) boo func ptrTo[T any](v T) *T { return &v } + +func getModuleResolutionWatchGlobs(program *compiler.Program) (failedLookups map[tspath.Path]string, affectingLocaions map[tspath.Path]string) { + failedLookups = make(map[tspath.Path]string) + affectingLocaions = make(map[tspath.Path]string) + extractLookups(program, failedLookups, affectingLocaions, program.GetResolvedModules()) + extractLookups(program, failedLookups, affectingLocaions, program.GetResolvedTypeReferenceDirectives()) + return failedLookups, affectingLocaions +} + +type ResolutionWithLookupLocations interface { + GetLookupLocations() *module.LookupLocations +} + +func extractLookups[T ResolutionWithLookupLocations]( + program *compiler.Program, + failedLookups map[tspath.Path]string, + affectingLocaions map[tspath.Path]string, + cache map[tspath.Path]module.ModeAwareCache[T], +) { + for _, resolvedModulesInFile := range cache { + for _, resolvedModule := range resolvedModulesInFile { + for _, failedLookupLocation := range resolvedModule.GetLookupLocations().FailedLookupLocations { + path := tspath.ToPath(failedLookupLocation, program.GetCurrentDirectory(), program.UseCaseSensitiveFileNames()) + if _, ok := failedLookups[path]; !ok { + failedLookups[path] = failedLookupLocation + } + } + for _, affectingLocation := range resolvedModule.GetLookupLocations().AffectingLocations { + path := tspath.ToPath(affectingLocation, program.GetCurrentDirectory(), program.UseCaseSensitiveFileNames()) + if _, ok := affectingLocaions[path]; !ok { + affectingLocaions[path] = affectingLocation + } + } + } + } +} From 5ff7bfda5a31b913cb5c49b3c9b067f0e413e909 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 17 Jun 2025 15:32:39 -0700 Subject: [PATCH 3/3] Refactor --- internal/project/project.go | 41 ++++++++++++++++++++++++++++++++----- internal/project/watch.go | 38 ---------------------------------- 2 files changed, 36 insertions(+), 43 deletions(-) diff --git a/internal/project/project.go b/internal/project/project.go index 8c7315bd67..39f042acec 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -351,17 +351,48 @@ func (p *Project) GetLanguageServiceForRequest(ctx context.Context) (*ls.Languag return languageService, cleanup } -func (p *Project) updateWatchers(ctx context.Context, program *compiler.Program) { +func (p *Project) updatedResolutionWatchers(ctx context.Context, program *compiler.Program) { client := p.Client() if !p.host.IsWatchEnabled() || client == nil { return } - failedLookupGlobs, affectingLocationGlobs := getModuleResolutionWatchGlobs(program) + failedLookups := make(map[tspath.Path]string) + affectingLocations := make(map[tspath.Path]string) + extractLookups(program, failedLookups, affectingLocations, program.GetResolvedModules()) + extractLookups(program, failedLookups, affectingLocations, program.GetResolvedTypeReferenceDirectives()) p.watchMu.Lock() defer p.watchMu.Unlock() - p.failedLookupsWatch.update(ctx, failedLookupGlobs) - p.affectingLocationsWatch.update(ctx, affectingLocationGlobs) + p.failedLookupsWatch.update(ctx, failedLookups) + p.affectingLocationsWatch.update(ctx, affectingLocations) +} + +type ResolutionWithLookupLocations interface { + GetLookupLocations() *module.LookupLocations +} + +func extractLookups[T ResolutionWithLookupLocations]( + program *compiler.Program, + failedLookups map[tspath.Path]string, + affectingLocations map[tspath.Path]string, + allResolutions map[tspath.Path]module.ModeAwareCache[T], +) { + for _, resolutionsInFile := range allResolutions { + for _, resolution := range resolutionsInFile { + for _, failedLookupLocation := range resolution.GetLookupLocations().FailedLookupLocations { + path := tspath.ToPath(failedLookupLocation, program.GetCurrentDirectory(), program.UseCaseSensitiveFileNames()) + if _, ok := failedLookups[path]; !ok { + failedLookups[path] = failedLookupLocation + } + } + for _, affectingLocation := range resolution.GetLookupLocations().AffectingLocations { + path := tspath.ToPath(affectingLocation, program.GetCurrentDirectory(), program.UseCaseSensitiveFileNames()) + if _, ok := affectingLocations[path]; !ok { + affectingLocations[path] = affectingLocation + } + } + } + } } // onWatchEventForNilScriptInfo is fired for watch events that are not the @@ -514,7 +545,7 @@ func (p *Project) updateGraph() (*compiler.Program, bool) { }) } p.enqueueInstallTypingsForProject(oldProgram, hasAddedOrRemovedFiles) - go p.updateWatchers(context.TODO(), p.program) + go p.updatedResolutionWatchers(context.TODO(), p.program) } p.Logf("Finishing updateGraph: Project: %s version: %d in %s", p.name, p.version, time.Since(start)) return p.program, true diff --git a/internal/project/watch.go b/internal/project/watch.go index a37d5c927f..be463cec9e 100644 --- a/internal/project/watch.go +++ b/internal/project/watch.go @@ -9,9 +9,7 @@ import ( "time" "github.com/microsoft/typescript-go/internal/collections" - "github.com/microsoft/typescript-go/internal/compiler" "github.com/microsoft/typescript-go/internal/lsp/lsproto" - "github.com/microsoft/typescript-go/internal/module" "github.com/microsoft/typescript-go/internal/tspath" ) @@ -364,39 +362,3 @@ func isInDirectoryPath(dirComponents []string, fileOrDirComponents []string) boo func ptrTo[T any](v T) *T { return &v } - -func getModuleResolutionWatchGlobs(program *compiler.Program) (failedLookups map[tspath.Path]string, affectingLocaions map[tspath.Path]string) { - failedLookups = make(map[tspath.Path]string) - affectingLocaions = make(map[tspath.Path]string) - extractLookups(program, failedLookups, affectingLocaions, program.GetResolvedModules()) - extractLookups(program, failedLookups, affectingLocaions, program.GetResolvedTypeReferenceDirectives()) - return failedLookups, affectingLocaions -} - -type ResolutionWithLookupLocations interface { - GetLookupLocations() *module.LookupLocations -} - -func extractLookups[T ResolutionWithLookupLocations]( - program *compiler.Program, - failedLookups map[tspath.Path]string, - affectingLocaions map[tspath.Path]string, - cache map[tspath.Path]module.ModeAwareCache[T], -) { - for _, resolvedModulesInFile := range cache { - for _, resolvedModule := range resolvedModulesInFile { - for _, failedLookupLocation := range resolvedModule.GetLookupLocations().FailedLookupLocations { - path := tspath.ToPath(failedLookupLocation, program.GetCurrentDirectory(), program.UseCaseSensitiveFileNames()) - if _, ok := failedLookups[path]; !ok { - failedLookups[path] = failedLookupLocation - } - } - for _, affectingLocation := range resolvedModule.GetLookupLocations().AffectingLocations { - path := tspath.ToPath(affectingLocation, program.GetCurrentDirectory(), program.UseCaseSensitiveFileNames()) - if _, ok := affectingLocaions[path]; !ok { - affectingLocaions[path] = affectingLocation - } - } - } - } -}