diff --git a/internal/project/projectcollectionbuilder.go b/internal/project/projectcollectionbuilder.go index e3d3e6a4c46..41ad6821f5f 100644 --- a/internal/project/projectcollectionbuilder.go +++ b/internal/project/projectcollectionbuilder.go @@ -612,7 +612,10 @@ func (b *ProjectCollectionBuilder) markProjectsAffectedByConfigChanges( for projectPath := range configChangeResult.affectedProjects { project, ok := b.configuredProjects.Load(projectPath) if !ok { - panic(fmt.Sprintf("project %s affected by config change not found", projectPath)) + if logger != nil { + logger.Logf("Skipping stale project %s affected by config change (project may have been removed)", projectPath) + } + continue } project.ChangeIf( func(p *Project) bool { return !p.dirty || p.dirtyFilePath != "" }, diff --git a/internal/project/projectcollectionbuilder_panic_repro_test.go b/internal/project/projectcollectionbuilder_panic_repro_test.go new file mode 100644 index 00000000000..52db80e745b --- /dev/null +++ b/internal/project/projectcollectionbuilder_panic_repro_test.go @@ -0,0 +1,70 @@ +package project + +import ( + "context" + "testing" + + "github.com/microsoft/typescript-go/internal/bundled" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/project/logging" + "github.com/microsoft/typescript-go/internal/tspath" + "github.com/microsoft/typescript-go/internal/vfs/vfstest" +) + +func TestProjectCollectionBuilder_HandlesStaleConfigRetainer(t *testing.T) { + t.Parallel() + + if !bundled.Embedded { + t.Skip("bundled files are not embedded") + } + + files := map[string]any{ + "/src/tsconfig.json": `{"compilerOptions": {"strict": true}}`, + "/src/index.ts": `export const x = 1;`, + } + + fs := bundled.WrapFS(vfstest.FromMap(files, false /*useCaseSensitiveFileNames*/)) + session := NewSession(&SessionInit{ + BackgroundCtx: context.Background(), + Options: &SessionOptions{ + CurrentDirectory: "/", + DefaultLibraryPath: bundled.LibPath(), + TypingsLocation: "/home/src/Library/Caches/typescript", + PositionEncoding: lsproto.PositionEncodingKindUTF8, + WatchEnabled: false, + LoggingEnabled: false, + }, + FS: fs, + Client: noopClient{}, + Logger: logging.NewTestLogger(), + NpmExecutor: nil, + }) + + session.DidOpenFile( + context.Background(), + lsproto.DocumentUri("file:///src/index.ts"), + 1, + files["/src/index.ts"].(string), + lsproto.LanguageKindTypeScript, + ) + + snapshot := session.Snapshot() + configPath := session.toPath("/src/tsconfig.json") + entry := snapshot.ConfigFileRegistry.configs[configPath] + if entry == nil { + t.Fatal("expected /src/tsconfig.json config entry to exist") + } + if entry.retainingProjects == nil { + entry.retainingProjects = make(map[tspath.Path]struct{}) + } + entry.retainingProjects[session.toPath("/stale/tsconfig.json")] = struct{}{} + + session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{ + { + Uri: lsproto.DocumentUri("file:///src/tsconfig.json"), + Type: lsproto.FileChangeTypeChanged, + }, + }) + + _, _ = session.GetLanguageService(context.Background(), lsproto.DocumentUri("file:///src/index.ts")) +}