diff --git a/internal/compiler/fileloader.go b/internal/compiler/fileloader.go index d39c5b74e5..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() { 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/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 1b5acb0243..a573d3537c 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -164,6 +164,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] @@ -347,37 +348,48 @@ 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) - for _, resolvedModulesInFile := range p.program.GetResolvedModules() { - for _, resolvedModule := range resolvedModulesInFile { - for _, failedLookupLocation := range resolvedModule.FailedLookupLocations { - path := p.toPath(failedLookupLocation) +func (p *Project) updatedResolutionWatchers(ctx context.Context, program *compiler.Program) { + client := p.Client() + if !p.host.IsWatchEnabled() || client == nil { + return + } + + 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, 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 resolvedModule.AffectingLocations { - path := p.toPath(affectingLocation) - if _, ok := affectingLocaions[path]; !ok { - affectingLocaions[path] = affectingLocation + for _, affectingLocation := range resolution.GetLookupLocations().AffectingLocations { + path := tspath.ToPath(affectingLocation, program.GetCurrentDirectory(), program.UseCaseSensitiveFileNames()) + if _, ok := affectingLocations[path]; !ok { + affectingLocations[path] = affectingLocation } } } } - return failedLookups, affectingLocaions -} - -func (p *Project) updateWatchers(ctx context.Context) { - client := p.Client() - if !p.host.IsWatchEnabled() || client == nil { - return - } - - failedLookupGlobs, affectingLocationGlobs := p.getModuleResolutionWatchGlobs() - p.failedLookupsWatch.update(ctx, failedLookupGlobs) - p.affectingLocationsWatch.update(ctx, affectingLocationGlobs) } // onWatchEventForNilScriptInfo is fired for watch events that are not the @@ -389,6 +401,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 { @@ -528,9 +542,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.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 @@ -768,10 +780,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 { @@ -817,6 +830,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) }