From 3861a9f6e8bc2000791eff879ad773028fc1ebfb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 16:25:44 +0000 Subject: [PATCH 1/3] Initial plan From 067a529141356b012004d32ccd00d53dbe6d1500 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 16:40:45 +0000 Subject: [PATCH 2/3] Handle stale config-retained projects without panic Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- internal/project/projectcollectionbuilder.go | 5 +- ...ojectcollectionbuilder_panic_repro_test.go | 70 +++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 internal/project/projectcollectionbuilder_panic_repro_test.go diff --git a/internal/project/projectcollectionbuilder.go b/internal/project/projectcollectionbuilder.go index e3d3e6a4c46..e826c8207ec 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", 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..860d657a097 --- /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_PanicOnStaleConfigRetainer(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")) +} From 96b194a189ccef43da42bf0a6c675856b6c28237 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 16:41:54 +0000 Subject: [PATCH 3/3] Polish stale-config repro and logging Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- internal/project/projectcollectionbuilder.go | 2 +- internal/project/projectcollectionbuilder_panic_repro_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/project/projectcollectionbuilder.go b/internal/project/projectcollectionbuilder.go index e826c8207ec..41ad6821f5f 100644 --- a/internal/project/projectcollectionbuilder.go +++ b/internal/project/projectcollectionbuilder.go @@ -613,7 +613,7 @@ func (b *ProjectCollectionBuilder) markProjectsAffectedByConfigChanges( project, ok := b.configuredProjects.Load(projectPath) if !ok { if logger != nil { - logger.Logf("Skipping stale project %s affected by config change", projectPath) + logger.Logf("Skipping stale project %s affected by config change (project may have been removed)", projectPath) } continue } diff --git a/internal/project/projectcollectionbuilder_panic_repro_test.go b/internal/project/projectcollectionbuilder_panic_repro_test.go index 860d657a097..52db80e745b 100644 --- a/internal/project/projectcollectionbuilder_panic_repro_test.go +++ b/internal/project/projectcollectionbuilder_panic_repro_test.go @@ -11,7 +11,7 @@ import ( "github.com/microsoft/typescript-go/internal/vfs/vfstest" ) -func TestProjectCollectionBuilder_PanicOnStaleConfigRetainer(t *testing.T) { +func TestProjectCollectionBuilder_HandlesStaleConfigRetainer(t *testing.T) { t.Parallel() if !bundled.Embedded {