From 9f2adcbb94c2416d2604419aa617356b3b25daae Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Sat, 6 Dec 2025 08:23:39 +0000 Subject: [PATCH 1/2] Make `(*WatchedFiles[T]).Clone` propagate `nil` --- internal/project/ata/ata_test.go | 45 ++++++++++++++++++++++++++++++++ internal/project/watch.go | 3 +++ internal/project/watch_test.go | 8 ++++++ 3 files changed, 56 insertions(+) diff --git a/internal/project/ata/ata_test.go b/internal/project/ata/ata_test.go index 7a6f2915ed..2dc92358d0 100644 --- a/internal/project/ata/ata_test.go +++ b/internal/project/ata/ata_test.go @@ -7,6 +7,7 @@ import ( "github.com/microsoft/typescript-go/internal/bundled" "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/project" "github.com/microsoft/typescript-go/internal/testutil/projecttestutil" "gotest.tools/v3/assert" ) @@ -618,4 +619,48 @@ func TestATA(t *testing.T) { emberComponentTypesFile := program.GetSourceFile(projecttestutil.TestTypingsLocation + "/node_modules/@types/ember__component/index.d.ts") assert.Assert(t, emberComponentTypesFile != nil, "ember__component types should be installed") }) + + // Test that ATA works correctly when `WatchEnabled` is false but `TypingsLocation` is set. + // Previously if `WatchEnabled` was false but `TypingsLocation` was set, ATA would run but + // crash when cloning file-watcher data for a new snapshot. + t.Run("ATA with WatchEnabled false should not panic", func(t *testing.T) { + t.Parallel() + + files := map[string]any{ + "/user/username/projects/project/app.js": ``, + "/user/username/projects/project/package.json": `{ + "name": "test", + "dependencies": { + "jquery": "^3.1.0" + } + }`, + } + + session, utils := projecttestutil.SetupWithOptionsAndTypingsInstaller(files, &project.SessionOptions{ + CurrentDirectory: "/", + DefaultLibraryPath: bundled.LibPath(), + TypingsLocation: projecttestutil.TestTypingsLocation, + PositionEncoding: lsproto.PositionEncodingKindUTF8, + WatchEnabled: false, + LoggingEnabled: true, + }, &projecttestutil.TypingsInstallerOptions{ + PackageToFile: map[string]string{ + "jquery": `declare const $: { x: number }`, + }, + }) + + // Open a file to trigger project creation and ATA. + session.DidOpenFile(context.Background(), lsproto.DocumentUri("file:///user/username/projects/project/app.js"), 1, files["/user/username/projects/project/app.js"].(string), lsproto.LanguageKindJavaScript) + session.WaitForBackgroundTasks() + + // ATA should have run + calls := utils.NpmExecutor().NpmInstallCalls() + assert.Equal(t, 2, len(calls), "Expected exactly 2 npm install calls") + + // Getting the language service should not panic after + // applying ATA changes and grabbing the latest snapshot. + ls, err := session.GetLanguageService(context.Background(), lsproto.DocumentUri("file:///user/username/projects/project/app.js")) + assert.NilError(t, err) + assert.Assert(t, ls != nil) + }) } diff --git a/internal/project/watch.go b/internal/project/watch.go index cdab89ad13..38e4f42d45 100644 --- a/internal/project/watch.go +++ b/internal/project/watch.go @@ -118,6 +118,9 @@ func (w *WatchedFiles[T]) WatchKind() lsproto.WatchKind { } func (w *WatchedFiles[T]) Clone(input T) *WatchedFiles[T] { + if w == nil { + return nil + } w.mu.RLock() defer w.mu.RUnlock() return &WatchedFiles[T]{ diff --git a/internal/project/watch_test.go b/internal/project/watch_test.go index ed3159a2ed..f9d0ce4024 100644 --- a/internal/project/watch_test.go +++ b/internal/project/watch_test.go @@ -18,3 +18,11 @@ func TestGetPathComponentsForWatching(t *testing.T) { assert.DeepEqual(t, getPathComponentsForWatching("/home", ""), []string{"/home"}) assert.DeepEqual(t, getPathComponentsForWatching("/home/andrew/project", ""), []string{"/home/andrew", "project"}) } + +func TestNilWatchedFilesClone(t *testing.T) { + t.Parallel() + + var w *WatchedFiles[int] + result := w.Clone(42) + assert.Equal(t, result, nil, "clone on a nil `WatchedFiles` should return nil") +} From c8036f55bccab03e0ff58a298b089b942204e333 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Sat, 6 Dec 2025 08:43:08 +0000 Subject: [PATCH 2/2] D'oh. --- internal/project/watch_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/project/watch_test.go b/internal/project/watch_test.go index f9d0ce4024..28dd915c62 100644 --- a/internal/project/watch_test.go +++ b/internal/project/watch_test.go @@ -24,5 +24,5 @@ func TestNilWatchedFilesClone(t *testing.T) { var w *WatchedFiles[int] result := w.Clone(42) - assert.Equal(t, result, nil, "clone on a nil `WatchedFiles` should return nil") + assert.Assert(t, result == nil, "clone on a nil `WatchedFiles` should return nil") }