From bb434ca20d289cc76bf8a09151b805824cea3631 Mon Sep 17 00:00:00 2001 From: Julian Doherty Date: Thu, 23 Mar 2023 12:54:02 +1100 Subject: [PATCH] feat(builtins/diagnostics/credo): toggle per file/full workspace diagnostics Add `full_workspace` param to credo. When false (default), credo will run for current file in buffer. When true, will run credo globally for all files in the project workspace --- doc/BUILTINS.md | 17 +++- lua/null-ls/builtins/diagnostics/credo.lua | 32 +++++++- test/spec/builtins/diagnostics_spec.lua | 90 +++++++++++++++++++--- 3 files changed, 128 insertions(+), 11 deletions(-) diff --git a/doc/BUILTINS.md b/doc/BUILTINS.md index 35df53ab0..e16cb3972 100644 --- a/doc/BUILTINS.md +++ b/doc/BUILTINS.md @@ -689,7 +689,22 @@ local sources = { null_ls.builtins.diagnostics.credo } - Filetypes: `{ "elixir" }` - Method: `diagnostics` - Command: `mix` -- Args: `{ "credo", "suggest", "--format", "json", "--read-from-stdin", "$FILENAME" }` +- Args: dynamically resolved (see [source](https://github.com/jose-elias-alvarez/null-ls.nvim/blob/main/lua/null-ls/builtins/diagnostics/credo.lua)) + +#### Config + +##### `full_workspace` (boolean) + +- `false` (default) - run credo for a single file +- `true` - run credo on the entire workspace. If this is slow on large projects, you may wish to set `method = null_ls.methods.DIAGNOSTICS_ON_SAVE` in `with()` call. + +```lua +local credo = null_ls.builtins.diagnostics.credo.with({ + config = { + full_workspace = true + }, +}) +``` #### Notes diff --git a/lua/null-ls/builtins/diagnostics/credo.lua b/lua/null-ls/builtins/diagnostics/credo.lua index 63b1d0545..1d8aa5a82 100644 --- a/lua/null-ls/builtins/diagnostics/credo.lua +++ b/lua/null-ls/builtins/diagnostics/credo.lua @@ -19,6 +19,15 @@ return h.make_builtin({ notes = { "Searches upwards from the buffer to the project root and tries to find the first `.credo.exs` file in case the project has nested `credo` configs.", }, + config = { + { + key = "full_workspace", + type = "boolean", + description = [[- `false` (default) - run credo for a single file +- `true` - run credo on the entire workspace. If this is slow on large projects, you may wish to set `method = null_ls.methods.DIAGNOSTICS_ON_SAVE` in `with()` call.]], + usage = [[true]], + }, + }, }, method = DIAGNOSTICS, filetypes = { "elixir" }, @@ -34,12 +43,29 @@ return h.make_builtin({ return params.root end end, - args = { "credo", "suggest", "--format", "json", "--read-from-stdin", "$FILENAME" }, + args = function(params) + if params:get_config().full_workspace then + return { "credo", "suggest", "--format", "json" } + else + return { "credo", "suggest", "--format", "json", "--read-from-stdin", "$FILENAME" } + end + end, format = "raw", to_stdin = true, on_output = function(params, done) local issues = {} + -- `multiple_files = true` must be set ONLY if running + -- `full_workspace=true`. + -- If it is set when credo is only generating diagnostics per file, + -- then existing diagnostics in open buffers will be cleared on + -- each subsequent execution in a different buffer + if params:get_config().full_workspace then + -- this is hacky, but there isn't any way to set `multiple_files` + -- dynamically based on user config properly + params:get_source().generator.multiple_files = true + end + -- credo is behaving in a bit of a tricky way: -- 1. if there are no elixir warnings, it will give its output -- on stderr, and stdout will be nil @@ -84,6 +110,10 @@ return h.make_builtin({ source = "credo", } + if params:get_config().full_workspace then + err.filename = issue.filename + end + -- using the dynamic priority ranges from credo source if issue.priority >= 10 then err.severity = h.diagnostics.severities.error diff --git a/test/spec/builtins/diagnostics_spec.lua b/test/spec/builtins/diagnostics_spec.lua index 5f3e49157..7bff13cdd 100644 --- a/test/spec/builtins/diagnostics_spec.lua +++ b/test/spec/builtins/diagnostics_spec.lua @@ -99,10 +99,15 @@ describe("diagnostics", function() describe("credo", function() local linter = diagnostics.credo local parser = linter._opts.on_output + local args = linter._opts.args local credo_diagnostics local done = function(_diagnostics) credo_diagnostics = _diagnostics end + local config = {} + local get_config = function() + return config + end after_each(function() credo_diagnostics = nil end) @@ -125,7 +130,7 @@ describe("diagnostics", function() } ] } ]] - parser({ output = output }, done) + parser({ output = output, get_config = get_config }, done) assert.same({ { source = "credo", @@ -153,7 +158,7 @@ describe("diagnostics", function() "trigger": "@impl true" }] } ]] - parser({ output = output }, done) + parser({ output = output, get_config = get_config }, done) assert.same({ { source = "credo", @@ -181,7 +186,7 @@ describe("diagnostics", function() "trigger": "TODO: implement check" }] } ]] - parser({ output = output }, done) + parser({ output = output, get_config = get_config }, done) assert.same({ { source = "credo", @@ -209,7 +214,7 @@ describe("diagnostics", function() "trigger": "|>" }] } ]] - parser({ output = output }, done) + parser({ output = output, get_config = get_config }, done) assert.same({ { source = "credo", @@ -224,7 +229,7 @@ describe("diagnostics", function() it("returns errors as diagnostics", function() local error = [[** (Mix) The task "credo" could not be found\nNote no mix.exs was found in the current directory]] - parser({ err = error }, done) + parser({ err = error, get_config = get_config }, done) assert.same({ { source = "credo", @@ -233,7 +238,7 @@ describe("diagnostics", function() }, }, credo_diagnostics) end) - it("should handle compile warnings preceeding output", function() + it("should handle compile warnings preceding output", function() local output = [[ 00:00:00.000 [warn] IMPORTING DEV.SECRET @@ -253,7 +258,8 @@ describe("diagnostics", function() } ] } ]] - parser({ output = output }, done) + + parser({ output = output, get_config = get_config }, done) assert.same({ { source = "credo", @@ -267,7 +273,7 @@ describe("diagnostics", function() end) it("should handle messages with incomplete json", function() local output = [[Some incomplete message that shouldn't really happen { "issues": ]] - parser({ output = output }, done) + parser({ output = output, get_config = get_config }, done) assert.same({ { source = "credo", @@ -278,7 +284,7 @@ describe("diagnostics", function() end) it("should handle messages without json", function() local output = [[Another message that shouldn't really happen]] - parser({ output = output }, done) + parser({ output = output, get_config = get_config }, done) assert.same({ { source = "credo", @@ -287,6 +293,72 @@ describe("diagnostics", function() }, }, credo_diagnostics) end) + + describe("full_workspace is falsey", function() + config = { + full_workspace = false, + } + + it("creates diagnostic without filename", function() + local output = [[ + { + "issues": [{ + "category": "design", + "check": "Credo.Check.Design.TagTODO", + "column": null, + "column_end": null, + "filename": "./foo.ex", + "line_no": 8, + "message": "Found a TODO tag in a comment: \"TODO: implement check\"", + "priority": -5, + "scope": null, + "trigger": "TODO: implement check" + }] + } ]] + parser({ output = output, get_config = get_config }, done) + assert.same(credo_diagnostics[1].filename, nil) + end) + + it("calls credo for a specific file", function() + assert.same( + args({ get_config = get_config }), + { "credo", "suggest", "--format", "json", "--read-from-stdin", "$FILENAME" } + ) + end) + end) + + describe("full_workspace is truthy", function() + local get_source = function() + return { generator = {} } + end + config = { + full_workspace = true, + } + + it("creates diagnostic with filename", function() + local output = [[ + { + "issues": [{ + "category": "design", + "check": "Credo.Check.Design.TagTODO", + "column": null, + "column_end": null, + "filename": "./foo.ex", + "line_no": 8, + "message": "Found a TODO tag in a comment: \"TODO: implement check\"", + "priority": -5, + "scope": null, + "trigger": "TODO: implement check" + }] + } ]] + parser({ output = output, get_config = get_config, get_source = get_source }, done) + assert.same("./foo.ex", credo_diagnostics[1].filename) + end) + + it("calls credo for workspace", function() + assert.same(args({ get_config = get_config }), { "credo", "suggest", "--format", "json" }) + end) + end) end) describe("luacheck", function()