From 43cd55bf0825e441cb4770f37a9eadce7aef3dbd Mon Sep 17 00:00:00 2001 From: Harsh Vardhan Chauhan Date: Sun, 25 Oct 2026 23:29:19 +0530 Subject: [PATCH] Fix auto-import bucket dirty tracking dropping later file changes in a batch markBucketsDirty removed a bucket from the clean set after its first dirty file (when multipleFilesDirty was false) instead of once it was fully dirty. Subsequent changed files in the same batch were skipped, so an export that only existed as unsaved content was never re-indexed. Add a regression test. --- tsc/internal/ls/autoimport/registry.go | 6 ++--- tsc/internal/ls/autoimport/registry_test.go | 29 +++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/tsc/internal/ls/autoimport/registry.go b/tsc/internal/ls/autoimport/registry.go index 4b8a71042a41a..ec92e50b18066 100644 --- a/tsc/internal/ls/autoimport/registry.go +++ b/tsc/internal/ls/autoimport/registry.go @@ -752,7 +752,7 @@ func (b *registryBuilder) markBucketsDirty(change RegistryChange, logger *loggin // Look up the package name for granular updates packageName := entry.Value().Paths[path] entry.Change(func(bucket *RegistryBucket) { bucket.markNodeModulesDirty(packageName) }) - if !entry.Value().state.multipleFilesDirty { + if entry.Value().state.multipleFilesDirty { delete(cleanNodeModulesBuckets, dirPath) } } @@ -764,7 +764,7 @@ func (b *registryBuilder) markBucketsDirty(change RegistryChange, logger *loggin if packageName, ok := entry.Value().Paths[path]; ok { // Use the package name for granular updates entry.Change(func(bucket *RegistryBucket) { bucket.markNodeModulesDirty(packageName) }) - if !entry.Value().state.multipleFilesDirty { + if entry.Value().state.multipleFilesDirty { delete(cleanNodeModulesBuckets, bucketDirPath) } } @@ -780,7 +780,7 @@ func (b *registryBuilder) markBucketsDirty(change RegistryChange, logger *loggin if _, ok := entry.Value().Paths[path]; ok { // Project buckets don't use package-based granular updates entry.Change(func(bucket *RegistryBucket) { bucket.markProjectFileDirty(path) }) - if !entry.Value().state.multipleFilesDirty { + if entry.Value().state.multipleFilesDirty { delete(cleanProjectBuckets, projectDirPath) } } diff --git a/tsc/internal/ls/autoimport/registry_test.go b/tsc/internal/ls/autoimport/registry_test.go index 996c73cd0ceea..41628c23aac87 100644 --- a/tsc/internal/ls/autoimport/registry_test.go +++ b/tsc/internal/ls/autoimport/registry_test.go @@ -1276,3 +1276,32 @@ func singleBucket(t *testing.T, buckets []autoimport.BucketStats) autoimport.Buc } return buckets[0] } + +func TestUnsavedExportAfterAutoImportsComputed(t *testing.T) { + t.Parallel() + root := "/home/src/proj" + files := map[string]any{ + root + "/exporter.ts": "", + root + "/tsconfig.json": `{"compilerOptions":{"module":"esnext","moduleResolution":"bundler","strict":true,"noEmit":true},"include":["*.ts"]}`, + } + session, _ := projecttestutil.Setup(files) + t.Cleanup(session.Close) + ctx := context.Background() + importer := lsproto.DocumentUri("file://" + root + "/importer.ts") + exporter := lsproto.DocumentUri("file://" + root + "/exporter.ts") + + session.DidOpenFile(ctx, importer, 1, "export {};", lsproto.LanguageKindTypeScript) + _, err := session.GetCurrentLanguageServiceWithAutoImports(ctx, importer) + assert.NilError(t, err) + session.DidCloseFile(ctx, importer) + + session.DidOpenFile(ctx, exporter, 1, "export function unsavedFn() {}", lsproto.LanguageKindTypeScript) + _, err = session.GetLanguageService(ctx, exporter) + assert.NilError(t, err) + session.DidOpenFile(ctx, importer, 1, "unsavedFn;", lsproto.LanguageKindTypeScript) + _, err = session.GetCurrentLanguageServiceWithAutoImports(ctx, importer) + assert.NilError(t, err) + stats := autoImportStats(t, session) + bucket := singleBucket(t, stats.ProjectBuckets) + assert.Assert(t, bucket.ExportCount > 0, "exporter.ts exports should be indexed: %+v", bucket) +}