From b98124514b96a8cc9d2e5b4deb6bbd83881b91d0 Mon Sep 17 00:00:00 2001 From: Andrew Ghostuhin Date: Wed, 1 Jul 2026 00:17:32 +0300 Subject: [PATCH 1/6] fix(lsp): support TypeScript source action kinds --- internal/fourslash/fourslash.go | 8 ++- ...ts_removeUnused_preservesMultiline_test.go | 25 +++++++ .../tests/sourceFixAllImports_test.go | 22 ++++++ internal/ls/codeactions.go | 31 +++++++- internal/ls/codeactions_test.go | 33 +++++++++ internal/ls/organizeimports.go | 9 +-- internal/lsp/lsproto/lsp.go | 8 ++- internal/lsp/server.go | 4 ++ internal/lsp/server_capabilities_test.go | 72 +++++++++++++++++++ 9 files changed, 201 insertions(+), 11 deletions(-) create mode 100644 internal/ls/codeactions_test.go create mode 100644 internal/lsp/server_capabilities_test.go diff --git a/internal/fourslash/fourslash.go b/internal/fourslash/fourslash.go index e1b51d15438..adb632d3855 100644 --- a/internal/fourslash/fourslash.go +++ b/internal/fourslash/fourslash.go @@ -1787,9 +1787,13 @@ func (f *FourslashTest) VerifyCodeFixAll(t *testing.T, options VerifyCodeFixAllO // VerifySourceFixAll verifies that requesting a source.fixAll code action produces the expected file content. // This tests the on-save code path where VS Code requests source.fixAll. func (f *FourslashTest) VerifySourceFixAll(t *testing.T, expectedContent string) { + f.VerifySourceFixAllWithKind(t, expectedContent, lsproto.CodeActionKindSourceFixAll) +} + +func (f *FourslashTest) VerifySourceFixAllWithKind(t *testing.T, expectedContent string, codeActionKind lsproto.CodeActionKind) { t.Helper() - only := []lsproto.CodeActionKind{lsproto.CodeActionKindSourceFixAll} + only := []lsproto.CodeActionKind{codeActionKind} params := &lsproto.CodeActionParams{ TextDocument: lsproto.TextDocumentIdentifier{ Uri: lsconv.FileNameToDocumentURI(f.activeFilename), @@ -1811,7 +1815,7 @@ func (f *FourslashTest) VerifySourceFixAll(t *testing.T, expectedContent string) var selected *lsproto.CodeAction for _, item := range *result.CommandOrCodeActionArray { - if item.CodeAction == nil || item.CodeAction.Kind == nil || *item.CodeAction.Kind != lsproto.CodeActionKindSourceFixAll { + if item.CodeAction == nil || item.CodeAction.Kind == nil || *item.CodeAction.Kind != codeActionKind { continue } selected = item.CodeAction diff --git a/internal/fourslash/tests/organizeImports_removeUnused_preservesMultiline_test.go b/internal/fourslash/tests/organizeImports_removeUnused_preservesMultiline_test.go index 88abfb222d9..df3b7681fdd 100644 --- a/internal/fourslash/tests/organizeImports_removeUnused_preservesMultiline_test.go +++ b/internal/fourslash/tests/organizeImports_removeUnused_preservesMultiline_test.go @@ -58,3 +58,28 @@ export { a, c };`, nil, ) } + +func TestOrganizeImports_removeUnusedTsKind(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `import { + a, + b, + c, +} from "module"; + +export { a, c };` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.VerifyOrganizeImports( + t, + `import { + a, + c +} from "module"; + +export { a, c };`, + lsproto.CodeActionKindSourceRemoveUnusedImportsTs, + nil, + ) +} diff --git a/internal/fourslash/tests/sourceFixAllImports_test.go b/internal/fourslash/tests/sourceFixAllImports_test.go index a4626b822e2..861153e9836 100644 --- a/internal/fourslash/tests/sourceFixAllImports_test.go +++ b/internal/fourslash/tests/sourceFixAllImports_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" "github.com/microsoft/typescript-go/internal/testutil" ) @@ -53,3 +54,24 @@ import { b } from "./b"; a; b;`) } + +func TestSourceFixAllCodeActionTsKind(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /a.ts +export const a: number = 1; +// @Filename: /b.ts +export const b: number = 2; +// @Filename: /main.ts +a; +b;` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.GoToFile(t, "/main.ts") + + f.VerifySourceFixAllWithKind(t, `import { a } from "./a"; +import { b } from "./b"; + +a; +b;`, lsproto.CodeActionKindSourceFixAllTs) +} diff --git a/internal/ls/codeactions.go b/internal/ls/codeactions.go index d04f64c73d0..de4434c7a89 100644 --- a/internal/ls/codeactions.go +++ b/internal/ls/codeactions.go @@ -89,7 +89,7 @@ func (l *LanguageService) ProvideCodeActions(ctx context.Context, params *lsprot } if isFixAllKind(kind) { - fixAllAction, err := l.createFixAllAction(ctx, program, file, params.TextDocument.Uri) + fixAllAction, err := l.createFixAllAction(ctx, program, file, params.TextDocument.Uri, kind) if err != nil { return lsproto.CodeActionResponse{}, err } @@ -239,7 +239,8 @@ func codeActionKindContains(requestedKind, actionKind lsproto.CodeActionKind) bo // isFixAllKind returns true if the requested kind matches source.fixAll func isFixAllKind(kind lsproto.CodeActionKind) bool { - return codeActionKindContains(kind, lsproto.CodeActionKindSourceFixAll) + return codeActionKindContains(kind, lsproto.CodeActionKindSourceFixAll) || + kind == lsproto.CodeActionKindSourceFixAllTs } // wantsQuickFixes returns true if the Only filter is nil/empty (meaning all kinds are wanted) @@ -263,8 +264,12 @@ func (l *LanguageService) createFixAllAction( program *compiler.Program, file *ast.SourceFile, uri lsproto.DocumentUri, + requestedKind lsproto.CodeActionKind, ) (*lsproto.CommandOrCodeAction, error) { kind := lsproto.CodeActionKindSourceFixAll + if requestedKind == lsproto.CodeActionKindSourceFixAllTs { + kind = requestedKind + } lspChanges := make(map[lsproto.DocumentUri][]*lsproto.TextEdit) for _, provider := range codeFixProviders { @@ -303,7 +308,7 @@ func (l *LanguageService) createFixAllAction( // getOrganizeImportsActionTitle returns the appropriate title for the given organize imports kind func getOrganizeImportsActionTitle(ctx context.Context, kind lsproto.CodeActionKind) string { loc := locale.FromContext(ctx) - switch kind { + switch getBaseOrganizeImportsKind(kind) { case lsproto.CodeActionKindSourceRemoveUnusedImports: return diagnostics.Remove_Unused_Imports.Localize(loc) case lsproto.CodeActionKindSourceSortImports: @@ -316,6 +321,13 @@ func getOrganizeImportsActionTitle(ctx context.Context, kind lsproto.CodeActionK // getOrganizeImportsActionsForKind returns the organize imports code action kinds that should be // returned for the given requested kind. func getOrganizeImportsActionsForKind(requestedKind lsproto.CodeActionKind) []lsproto.CodeActionKind { + switch requestedKind { + case lsproto.CodeActionKindSourceOrganizeImportsTs, + lsproto.CodeActionKindSourceRemoveUnusedImportsTs, + lsproto.CodeActionKindSourceSortImportsTs: + return []lsproto.CodeActionKind{requestedKind} + } + organizeImportsKinds := []lsproto.CodeActionKind{ lsproto.CodeActionKindSourceOrganizeImports, lsproto.CodeActionKindSourceRemoveUnusedImports, @@ -336,6 +348,19 @@ func getOrganizeImportsActionsForKind(requestedKind lsproto.CodeActionKind) []ls return result } +func getBaseOrganizeImportsKind(kind lsproto.CodeActionKind) lsproto.CodeActionKind { + switch kind { + case lsproto.CodeActionKindSourceOrganizeImportsTs: + return lsproto.CodeActionKindSourceOrganizeImports + case lsproto.CodeActionKindSourceRemoveUnusedImportsTs: + return lsproto.CodeActionKindSourceRemoveUnusedImports + case lsproto.CodeActionKindSourceSortImportsTs: + return lsproto.CodeActionKindSourceSortImports + default: + return kind + } +} + // createOrganizeImportsAction creates the organize imports code action func (l *LanguageService) createOrganizeImportsAction( ctx context.Context, diff --git a/internal/ls/codeactions_test.go b/internal/ls/codeactions_test.go new file mode 100644 index 00000000000..270feb12066 --- /dev/null +++ b/internal/ls/codeactions_test.go @@ -0,0 +1,33 @@ +package ls + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "gotest.tools/v3/assert" +) + +func TestGetOrganizeImportsActionsForTypeScriptKinds(t *testing.T) { + t.Parallel() + + tests := []struct { + requested lsproto.CodeActionKind + expected lsproto.CodeActionKind + expectedBase lsproto.CodeActionKind + }{ + {lsproto.CodeActionKindSourceOrganizeImportsTs, lsproto.CodeActionKindSourceOrganizeImportsTs, lsproto.CodeActionKindSourceOrganizeImports}, + {lsproto.CodeActionKindSourceRemoveUnusedImportsTs, lsproto.CodeActionKindSourceRemoveUnusedImportsTs, lsproto.CodeActionKindSourceRemoveUnusedImports}, + {lsproto.CodeActionKindSourceSortImportsTs, lsproto.CodeActionKindSourceSortImportsTs, lsproto.CodeActionKindSourceSortImports}, + } + + for _, test := range tests { + assert.DeepEqual(t, getOrganizeImportsActionsForKind(test.requested), []lsproto.CodeActionKind{test.expected}) + assert.Equal(t, getBaseOrganizeImportsKind(test.requested), test.expectedBase) + } +} + +func TestIsFixAllKindAcceptsTypeScriptKind(t *testing.T) { + t.Parallel() + + assert.Assert(t, isFixAllKind(lsproto.CodeActionKindSourceFixAllTs)) +} diff --git a/internal/ls/organizeimports.go b/internal/ls/organizeimports.go index ddea88bffd1..90f7b38bd7c 100644 --- a/internal/ls/organizeimports.go +++ b/internal/ls/organizeimports.go @@ -28,9 +28,10 @@ func (l *LanguageService) OrganizeImports( kind lsproto.CodeActionKind, ) map[string][]*lsproto.TextEdit { changeTracker := change.NewTracker(ctx, program.Options(), l.FormatOptions(), l.converters) - shouldSort := kind == lsproto.CodeActionKindSourceSortImports || kind == lsproto.CodeActionKindSourceOrganizeImports + baseKind := getBaseOrganizeImportsKind(kind) + shouldSort := baseKind == lsproto.CodeActionKindSourceSortImports || baseKind == lsproto.CodeActionKindSourceOrganizeImports shouldCombine := shouldSort - shouldRemove := kind == lsproto.CodeActionKindSourceRemoveUnusedImports || kind == lsproto.CodeActionKindSourceOrganizeImports + shouldRemove := baseKind == lsproto.CodeActionKindSourceRemoveUnusedImports || baseKind == lsproto.CodeActionKindSourceOrganizeImports topLevelImportDecls := lsutil.FilterImportDeclarations(sourceFile.Statements.Nodes) topLevelImportGroupDecls := groupByNewlineContiguous(sourceFile, topLevelImportDecls) @@ -74,7 +75,7 @@ func (l *LanguageService) OrganizeImports( organizeImportsWorker(importGroupDecl, comparer, shouldSort, shouldCombine, shouldRemove, sourceFile, program, changeTracker, ctx) } - if kind != lsproto.CodeActionKindSourceRemoveUnusedImports { + if baseKind != lsproto.CodeActionKindSourceRemoveUnusedImports { topLevelExportGroupDecls := getTopLevelExportGroups(sourceFile) for _, exportGroupDecl := range topLevelExportGroupDecls { organizeExportsWorker(exportGroupDecl, comparer, sourceFile, changeTracker) @@ -100,7 +101,7 @@ func (l *LanguageService) OrganizeImports( organizeImportsWorker(importGroupDecl, comparer, shouldSort, shouldCombine, shouldRemove, sourceFile, program, changeTracker, ctx) } - if kind != lsproto.CodeActionKindSourceRemoveUnusedImports { + if baseKind != lsproto.CodeActionKindSourceRemoveUnusedImports { var ambientModuleExportDecls []*ast.Statement for _, s := range moduleBody.Statements.Nodes { if s.Kind == ast.KindExportDeclaration { diff --git a/internal/lsp/lsproto/lsp.go b/internal/lsp/lsproto/lsp.go index 60c4b447797..1c8901ea93c 100644 --- a/internal/lsp/lsproto/lsp.go +++ b/internal/lsp/lsproto/lsp.go @@ -310,6 +310,10 @@ func PreferredMarkupKind(formats []MarkupKind) MarkupKind { } const ( - CodeActionKindSourceRemoveUnusedImports CodeActionKind = "source.removeUnusedImports" - CodeActionKindSourceSortImports CodeActionKind = "source.sortImports" + CodeActionKindSourceOrganizeImportsTs CodeActionKind = "source.organizeImports.ts" + CodeActionKindSourceRemoveUnusedImports CodeActionKind = "source.removeUnusedImports" + CodeActionKindSourceRemoveUnusedImportsTs CodeActionKind = "source.removeUnusedImports.ts" + CodeActionKindSourceSortImports CodeActionKind = "source.sortImports" + CodeActionKindSourceSortImportsTs CodeActionKind = "source.sortImports.ts" + CodeActionKindSourceFixAllTs CodeActionKind = "source.fixAll.ts" ) diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 4f55c721093..f6e89db904d 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -1150,9 +1150,13 @@ func (s *Server) handleInitialize(ctx context.Context, params *lsproto.Initializ CodeActionKinds: &[]lsproto.CodeActionKind{ lsproto.CodeActionKindQuickFix, lsproto.CodeActionKindSourceOrganizeImports, + lsproto.CodeActionKindSourceOrganizeImportsTs, lsproto.CodeActionKindSourceRemoveUnusedImports, + lsproto.CodeActionKindSourceRemoveUnusedImportsTs, lsproto.CodeActionKindSourceSortImports, + lsproto.CodeActionKindSourceSortImportsTs, lsproto.CodeActionKindSourceFixAll, + lsproto.CodeActionKindSourceFixAllTs, }, }, }, diff --git a/internal/lsp/server_capabilities_test.go b/internal/lsp/server_capabilities_test.go new file mode 100644 index 00000000000..6a35ba90d2f --- /dev/null +++ b/internal/lsp/server_capabilities_test.go @@ -0,0 +1,72 @@ +package lsp_test + +import ( + "context" + "io" + "testing" + + "github.com/microsoft/typescript-go/internal/bundled" + "github.com/microsoft/typescript-go/internal/lsp" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/testutil/lsptestutil" + "github.com/microsoft/typescript-go/internal/vfs/vfstest" + "gotest.tools/v3/assert" +) + +func TestInitializeAdvertisesTypeScriptSourceActionKinds(t *testing.T) { + t.Parallel() + + fs := bundled.WrapFS(vfstest.FromMap(map[string]string{}, false)) + onServerRequest := func(_ context.Context, req *lsproto.RequestMessage) *lsproto.ResponseMessage { + switch req.Method { + case lsproto.MethodClientRegisterCapability, lsproto.MethodClientUnregisterCapability: + return &lsproto.ResponseMessage{ + ID: req.ID, + JSONRPC: req.JSONRPC, + Result: lsproto.Null{}, + } + default: + return nil + } + } + + client, closeClient := lsptestutil.NewLSPClient(t, lsp.ServerOptions{ + Err: io.Discard, + Cwd: "/home/projects", + FS: fs, + DefaultLibraryPath: bundled.LibPath(), + }, onServerRequest) + t.Cleanup(func() { assert.NilError(t, closeClient()) }) + + initMsg, result, ok := lsptestutil.SendRequest(t, client, lsproto.InitializeInfo, &lsproto.InitializeParams{ + Capabilities: &lsproto.ClientCapabilities{}, + }) + assert.Assert(t, ok && initMsg.AsResponse().Error == nil, "Initialize failed") + + codeActionProvider := result.Capabilities.CodeActionProvider + assert.Assert(t, codeActionProvider != nil && codeActionProvider.CodeActionOptions != nil) + kinds := codeActionProvider.CodeActionOptions.CodeActionKinds + assert.Assert(t, kinds != nil) + + for _, kind := range []lsproto.CodeActionKind{ + lsproto.CodeActionKindSourceOrganizeImports, + lsproto.CodeActionKindSourceOrganizeImportsTs, + lsproto.CodeActionKindSourceRemoveUnusedImports, + lsproto.CodeActionKindSourceRemoveUnusedImportsTs, + lsproto.CodeActionKindSourceSortImports, + lsproto.CodeActionKindSourceSortImportsTs, + lsproto.CodeActionKindSourceFixAll, + lsproto.CodeActionKindSourceFixAllTs, + } { + assert.Assert(t, containsCodeActionKind(*kinds, kind), "missing code action kind %q", kind) + } +} + +func containsCodeActionKind(kinds []lsproto.CodeActionKind, kind lsproto.CodeActionKind) bool { + for _, candidate := range kinds { + if candidate == kind { + return true + } + } + return false +} From 8675c332930fe818a33d2eae040e02f8478b5c92 Mon Sep 17 00:00:00 2001 From: Andrew Ghostuhin Date: Wed, 1 Jul 2026 00:27:25 +0300 Subject: [PATCH 2/6] test(lsp): cover TypeScript organize import kinds --- .../organizeImports_coalesceImports_test.go | 17 +++++++++++++++++ ...rganizeImports_sortModuleSpecifiers_test.go | 18 ++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/internal/fourslash/tests/organizeImports_coalesceImports_test.go b/internal/fourslash/tests/organizeImports_coalesceImports_test.go index 17ebba013f2..5f2a6de5405 100644 --- a/internal/fourslash/tests/organizeImports_coalesceImports_test.go +++ b/internal/fourslash/tests/organizeImports_coalesceImports_test.go @@ -25,6 +25,23 @@ M; n; B; y; O;`, ) } +func TestOrganizeImports_coalesceImportsTsKind(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `import x from "lib"; +import y from "lib"; +x; y;` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.VerifyOrganizeImports( + t, + `import { default as x, default as y } from "lib"; +x; y;`, + lsproto.CodeActionKindSourceOrganizeImportsTs, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, + ) +} + func TestOrganizeImports_coalesceImports_combineSideEffectOnly(t *testing.T) { t.Parallel() defer testutil.RecoverAndFail(t, "Panic on fourslash test") diff --git a/internal/fourslash/tests/organizeImports_sortModuleSpecifiers_test.go b/internal/fourslash/tests/organizeImports_sortModuleSpecifiers_test.go index b3eff84f6d6..d2ae9d0ea5f 100644 --- a/internal/fourslash/tests/organizeImports_sortModuleSpecifiers_test.go +++ b/internal/fourslash/tests/organizeImports_sortModuleSpecifiers_test.go @@ -27,6 +27,24 @@ x; y;`, ) } +func TestOrganizeImports_sortModuleSpecifiersTsKind(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `import x from "lib2"; +import y from "lib1"; +x; y;` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.VerifyOrganizeImports( + t, + `import y from "lib1"; +import x from "lib2"; +x; y;`, + lsproto.CodeActionKindSourceSortImportsTs, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, + ) +} + func TestOrganizeImports_sortModuleSpecifiers_relativeVsRelative(t *testing.T) { t.Parallel() defer testutil.RecoverAndFail(t, "Panic on fourslash test") From 856211878c05f6c9ed812c2f0e52b4ab76ac5607 Mon Sep 17 00:00:00 2001 From: Andrew Ghostuhin Date: Wed, 1 Jul 2026 01:43:47 +0300 Subject: [PATCH 3/6] test(lsp): skip source action capability test without bundled libs --- internal/lsp/server_capabilities_test.go | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/internal/lsp/server_capabilities_test.go b/internal/lsp/server_capabilities_test.go index 6a35ba90d2f..2f626a8ebdd 100644 --- a/internal/lsp/server_capabilities_test.go +++ b/internal/lsp/server_capabilities_test.go @@ -3,6 +3,7 @@ package lsp_test import ( "context" "io" + "slices" "testing" "github.com/microsoft/typescript-go/internal/bundled" @@ -16,6 +17,10 @@ import ( func TestInitializeAdvertisesTypeScriptSourceActionKinds(t *testing.T) { t.Parallel() + if !bundled.Embedded { + t.Skip("bundled files are not embedded") + } + fs := bundled.WrapFS(vfstest.FromMap(map[string]string{}, false)) onServerRequest := func(_ context.Context, req *lsproto.RequestMessage) *lsproto.ResponseMessage { switch req.Method { @@ -36,7 +41,7 @@ func TestInitializeAdvertisesTypeScriptSourceActionKinds(t *testing.T) { FS: fs, DefaultLibraryPath: bundled.LibPath(), }, onServerRequest) - t.Cleanup(func() { assert.NilError(t, closeClient()) }) + t.Cleanup(func() { _ = closeClient() }) initMsg, result, ok := lsptestutil.SendRequest(t, client, lsproto.InitializeInfo, &lsproto.InitializeParams{ Capabilities: &lsproto.ClientCapabilities{}, @@ -58,15 +63,6 @@ func TestInitializeAdvertisesTypeScriptSourceActionKinds(t *testing.T) { lsproto.CodeActionKindSourceFixAll, lsproto.CodeActionKindSourceFixAllTs, } { - assert.Assert(t, containsCodeActionKind(*kinds, kind), "missing code action kind %q", kind) - } -} - -func containsCodeActionKind(kinds []lsproto.CodeActionKind, kind lsproto.CodeActionKind) bool { - for _, candidate := range kinds { - if candidate == kind { - return true - } + assert.Assert(t, slices.Contains(*kinds, kind), "missing code action kind %q", kind) } - return false } From 6244b02b95fb4ea1471cc195df20fa26be7a95cc Mon Sep 17 00:00:00 2001 From: Andrew Ghostuhin Date: Tue, 18 Aug 2026 22:42:56 +0300 Subject: [PATCH 4/6] test(lsp): move source action capabilities test --- internal/lsp/server_capabilities_test.go | 68 ------------------------ internal/lsp/server_test.go | 44 +++++++++++++++ 2 files changed, 44 insertions(+), 68 deletions(-) delete mode 100644 internal/lsp/server_capabilities_test.go diff --git a/internal/lsp/server_capabilities_test.go b/internal/lsp/server_capabilities_test.go deleted file mode 100644 index 2f626a8ebdd..00000000000 --- a/internal/lsp/server_capabilities_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package lsp_test - -import ( - "context" - "io" - "slices" - "testing" - - "github.com/microsoft/typescript-go/internal/bundled" - "github.com/microsoft/typescript-go/internal/lsp" - "github.com/microsoft/typescript-go/internal/lsp/lsproto" - "github.com/microsoft/typescript-go/internal/testutil/lsptestutil" - "github.com/microsoft/typescript-go/internal/vfs/vfstest" - "gotest.tools/v3/assert" -) - -func TestInitializeAdvertisesTypeScriptSourceActionKinds(t *testing.T) { - t.Parallel() - - if !bundled.Embedded { - t.Skip("bundled files are not embedded") - } - - fs := bundled.WrapFS(vfstest.FromMap(map[string]string{}, false)) - onServerRequest := func(_ context.Context, req *lsproto.RequestMessage) *lsproto.ResponseMessage { - switch req.Method { - case lsproto.MethodClientRegisterCapability, lsproto.MethodClientUnregisterCapability: - return &lsproto.ResponseMessage{ - ID: req.ID, - JSONRPC: req.JSONRPC, - Result: lsproto.Null{}, - } - default: - return nil - } - } - - client, closeClient := lsptestutil.NewLSPClient(t, lsp.ServerOptions{ - Err: io.Discard, - Cwd: "/home/projects", - FS: fs, - DefaultLibraryPath: bundled.LibPath(), - }, onServerRequest) - t.Cleanup(func() { _ = closeClient() }) - - initMsg, result, ok := lsptestutil.SendRequest(t, client, lsproto.InitializeInfo, &lsproto.InitializeParams{ - Capabilities: &lsproto.ClientCapabilities{}, - }) - assert.Assert(t, ok && initMsg.AsResponse().Error == nil, "Initialize failed") - - codeActionProvider := result.Capabilities.CodeActionProvider - assert.Assert(t, codeActionProvider != nil && codeActionProvider.CodeActionOptions != nil) - kinds := codeActionProvider.CodeActionOptions.CodeActionKinds - assert.Assert(t, kinds != nil) - - for _, kind := range []lsproto.CodeActionKind{ - lsproto.CodeActionKindSourceOrganizeImports, - lsproto.CodeActionKindSourceOrganizeImportsTs, - lsproto.CodeActionKindSourceRemoveUnusedImports, - lsproto.CodeActionKindSourceRemoveUnusedImportsTs, - lsproto.CodeActionKindSourceSortImports, - lsproto.CodeActionKindSourceSortImportsTs, - lsproto.CodeActionKindSourceFixAll, - lsproto.CodeActionKindSourceFixAllTs, - } { - assert.Assert(t, slices.Contains(*kinds, kind), "missing code action kind %q", kind) - } -} diff --git a/internal/lsp/server_test.go b/internal/lsp/server_test.go index 6602f62fc01..396616ba59e 100644 --- a/internal/lsp/server_test.go +++ b/internal/lsp/server_test.go @@ -3,6 +3,7 @@ package lsp import ( "context" "io" + "slices" "testing" "time" @@ -11,6 +12,7 @@ import ( "github.com/microsoft/typescript-go/internal/lsp/lsproto" "github.com/microsoft/typescript-go/internal/project" "github.com/microsoft/typescript-go/internal/vfs/vfstest" + "gotest.tools/v3/assert" ) type shutdownTestReader struct{} @@ -21,6 +23,48 @@ type shutdownTestWriter struct{} func (shutdownTestWriter) Write(*lsproto.Message) error { return nil } +func TestInitializeAdvertisesTypeScriptSourceActionKinds(t *testing.T) { + t.Parallel() + + if !bundled.Embedded { + t.Skip("bundled files are not embedded") + } + + fs := bundled.WrapFS(vfstest.FromMap(map[string]string{}, false)) + server := NewServer(&ServerOptions{ + In: shutdownTestReader{}, + Out: shutdownTestWriter{}, + Err: io.Discard, + Cwd: "/home/projects", + FS: fs, + DefaultLibraryPath: bundled.LibPath(), + }) + server.backgroundCtx = t.Context() + + result, err := server.handleInitialize(t.Context(), &lsproto.InitializeParams{ + Capabilities: &lsproto.ClientCapabilities{}, + }, nil) + assert.NilError(t, err, "Initialize failed") + + codeActionProvider := result.Capabilities.CodeActionProvider + assert.Assert(t, codeActionProvider != nil && codeActionProvider.CodeActionOptions != nil) + kinds := codeActionProvider.CodeActionOptions.CodeActionKinds + assert.Assert(t, kinds != nil) + + for _, kind := range []lsproto.CodeActionKind{ + lsproto.CodeActionKindSourceOrganizeImports, + lsproto.CodeActionKindSourceOrganizeImportsTs, + lsproto.CodeActionKindSourceRemoveUnusedImports, + lsproto.CodeActionKindSourceRemoveUnusedImportsTs, + lsproto.CodeActionKindSourceSortImports, + lsproto.CodeActionKindSourceSortImportsTs, + lsproto.CodeActionKindSourceFixAll, + lsproto.CodeActionKindSourceFixAllTs, + } { + assert.Assert(t, slices.Contains(*kinds, kind), "missing code action kind %q", kind) + } +} + // TestServerShutdownNoDeadlock verifies that operations after shutdown // don't block. func TestServerShutdownNoDeadlock(t *testing.T) { From c286ce25fb88d04010f52b9c7a9f62eff578c6c0 Mon Sep 17 00:00:00 2001 From: Andrew Ghostuhin Date: Wed, 19 Aug 2026 00:33:51 +0300 Subject: [PATCH 5/6] refactor(ls): reuse normalized organize imports kind --- internal/ls/organizeimports.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/ls/organizeimports.go b/internal/ls/organizeimports.go index 90f7b38bd7c..b34befbf37d 100644 --- a/internal/ls/organizeimports.go +++ b/internal/ls/organizeimports.go @@ -28,10 +28,10 @@ func (l *LanguageService) OrganizeImports( kind lsproto.CodeActionKind, ) map[string][]*lsproto.TextEdit { changeTracker := change.NewTracker(ctx, program.Options(), l.FormatOptions(), l.converters) - baseKind := getBaseOrganizeImportsKind(kind) - shouldSort := baseKind == lsproto.CodeActionKindSourceSortImports || baseKind == lsproto.CodeActionKindSourceOrganizeImports + kind = getBaseOrganizeImportsKind(kind) + shouldSort := kind == lsproto.CodeActionKindSourceSortImports || kind == lsproto.CodeActionKindSourceOrganizeImports shouldCombine := shouldSort - shouldRemove := baseKind == lsproto.CodeActionKindSourceRemoveUnusedImports || baseKind == lsproto.CodeActionKindSourceOrganizeImports + shouldRemove := kind == lsproto.CodeActionKindSourceRemoveUnusedImports || kind == lsproto.CodeActionKindSourceOrganizeImports topLevelImportDecls := lsutil.FilterImportDeclarations(sourceFile.Statements.Nodes) topLevelImportGroupDecls := groupByNewlineContiguous(sourceFile, topLevelImportDecls) @@ -75,7 +75,7 @@ func (l *LanguageService) OrganizeImports( organizeImportsWorker(importGroupDecl, comparer, shouldSort, shouldCombine, shouldRemove, sourceFile, program, changeTracker, ctx) } - if baseKind != lsproto.CodeActionKindSourceRemoveUnusedImports { + if kind != lsproto.CodeActionKindSourceRemoveUnusedImports { topLevelExportGroupDecls := getTopLevelExportGroups(sourceFile) for _, exportGroupDecl := range topLevelExportGroupDecls { organizeExportsWorker(exportGroupDecl, comparer, sourceFile, changeTracker) @@ -101,7 +101,7 @@ func (l *LanguageService) OrganizeImports( organizeImportsWorker(importGroupDecl, comparer, shouldSort, shouldCombine, shouldRemove, sourceFile, program, changeTracker, ctx) } - if baseKind != lsproto.CodeActionKindSourceRemoveUnusedImports { + if kind != lsproto.CodeActionKindSourceRemoveUnusedImports { var ambientModuleExportDecls []*ast.Statement for _, s := range moduleBody.Statements.Nodes { if s.Kind == ast.KindExportDeclaration { From 60fbce9e55ae8c2d61801c67f10d3413ef851d0d Mon Sep 17 00:00:00 2001 From: Andrew Ghostuhin Date: Wed, 19 Aug 2026 03:03:07 +0300 Subject: [PATCH 6/6] refactor(ls): simplify fix all kind check --- internal/ls/codeactions.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/ls/codeactions.go b/internal/ls/codeactions.go index de4434c7a89..07f9566bad9 100644 --- a/internal/ls/codeactions.go +++ b/internal/ls/codeactions.go @@ -239,8 +239,7 @@ func codeActionKindContains(requestedKind, actionKind lsproto.CodeActionKind) bo // isFixAllKind returns true if the requested kind matches source.fixAll func isFixAllKind(kind lsproto.CodeActionKind) bool { - return codeActionKindContains(kind, lsproto.CodeActionKindSourceFixAll) || - kind == lsproto.CodeActionKindSourceFixAllTs + return codeActionKindContains(kind, lsproto.CodeActionKindSourceFixAllTs) } // wantsQuickFixes returns true if the Only filter is nil/empty (meaning all kinds are wanted)