From c0dd8167a6fc469105e2cd8ef8a4e73cdf04e4d8 Mon Sep 17 00:00:00 2001 From: halfcrazy Date: Sun, 25 Jan 2026 15:25:25 +0800 Subject: [PATCH] fix gopls without setting ale_go_gopls_init_options (#5059) --- lua/ale/lsp.lua | 8 ++++++ test/linter/test_gopls.vader | 22 +++++++++++++++++ test/lua/ale_lsp_spec.lua | 48 ++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+) diff --git a/lua/ale/lsp.lua b/lua/ale/lsp.lua index 6c54db12..5df3097a 100644 --- a/lua/ale/lsp.lua +++ b/lua/ale/lsp.lua @@ -8,6 +8,14 @@ module.start = function(config) config.init_options[true] = nil end + -- ensure init_options uses empty_dict if empty + if type(config.init_options) == "table" + and next(config.init_options) == nil + and getmetatable(config.init_options) == nil + then + config.init_options = vim.empty_dict() + end + -- If configuring LSP via a socket connection, then generate the cmd -- using vim.lsp.rpc.connect(), as defined in Neovim documentation. if config.host then diff --git a/test/linter/test_gopls.vader b/test/linter/test_gopls.vader index 1c91fa10..26fcf9d9 100644 --- a/test/linter/test_gopls.vader +++ b/test/linter/test_gopls.vader @@ -94,3 +94,25 @@ Execute('go.mod' should be ignored if modules are off): call delete(b:git_dir, 'd') unlet! b:parent_dir unlet! b:git_dir + +Execute(Init options should handle empty and non-empty values correctly): + " Regression test for: Invalid settings: invalid options type []interface {} + " This ensures empty init_options {} is passed as an object, not array + call ale#test#SetFilename('../test-files/go/go1/prj1/file.go') + + " Test 1: Default empty dict + AssertLSPOptions {} + + " Test 2: Explicitly set to empty + let b:ale_go_gopls_init_options = {} + AssertLSPOptions {} + + " Test 3: Non-empty options should be preserved + let b:ale_go_gopls_init_options = { + \ 'ui.diagnostic.analyses': {'composites': v:false}, + \ 'completeUnimported': v:true + \} + AssertLSPOptions { + \ 'ui.diagnostic.analyses': {'composites': v:false}, + \ 'completeUnimported': v:true + \} diff --git a/test/lua/ale_lsp_spec.lua b/test/lua/ale_lsp_spec.lua index b37fbe28..9a74cea6 100644 --- a/test/lua/ale_lsp_spec.lua +++ b/test/lua/ale_lsp_spec.lua @@ -13,6 +13,10 @@ describe("ale.lsp.start", function() defer_fn = function(func, delay) table.insert(defer_calls, {func, delay}) end, + empty_dict = function() + -- Returns a table with a metatable to distinguish it from arrays + return setmetatable({}, {__empty_dict = true}) + end, fn = setmetatable({}, { __index = function(_, key) return function(...) @@ -105,6 +109,50 @@ describe("ale.lsp.start", function() eq({}, vim_fn_calls) end) + it("should convert empty init_options to vim.empty_dict", function() + -- Mock vim.empty_dict + local empty_dict_called = false + _G.vim.empty_dict = function() + empty_dict_called = true + return setmetatable({}, {__empty_dict = true}) + end + + lsp.start({ + name = "gopls:/code", + cmd = "gopls", + root_dir = "/code", + -- Empty table without metatable (like from VimScript {}) + init_options = {}, + }) + + -- Verify that empty_dict was called + eq(true, empty_dict_called) + + -- Verify init_options has metatable now + eq(1, #start_calls) + local init_opts = start_calls[1][1].init_options + eq(true, getmetatable(init_opts) ~= nil) + end) + + it("should preserve non-empty init_options", function() + lsp.start({ + name = "gopls:/code", + cmd = "gopls", + root_dir = "/code", + init_options = {foo = "bar", nested = {baz = 123}}, + }) + + -- Remove functions we can't compare + for _, args in pairs(start_calls) do + args[1].handlers = nil + args[1].on_init = nil + args[1].get_language_id = nil + end + + eq(1, #start_calls) + eq({foo = "bar", nested = {baz = 123}}, start_calls[1][1].init_options) + end) + it("should start lsp socket connections with the correct arguments", function() lsp.start({ name = "localhost:1234:/code",