From 562fcf2911e3b792561f65b1e3455f2c9cd078ee Mon Sep 17 00:00:00 2001 From: Ievgenii Shepeliuk Date: Fri, 17 Nov 2023 09:31:08 +0000 Subject: [PATCH] feat(diagnostics): add regal linter Signed-off-by: Ievgenii Shepeliuk --- doc/tags | 23 +++++ lua/null-ls/builtins/diagnostics/regal.lua | 54 +++++++++++ test/spec/builtins/diagnostics_spec.lua | 107 +++++++++++++++++---- 3 files changed, 163 insertions(+), 21 deletions(-) create mode 100644 doc/tags create mode 100644 lua/null-ls/builtins/diagnostics/regal.lua diff --git a/doc/tags b/doc/tags new file mode 100644 index 00000000..e8d7b068 --- /dev/null +++ b/doc/tags @@ -0,0 +1,23 @@ +null-ls-alternatives null-ls.txt /*null-ls-alternatives* +null-ls-community null-ls.txt /*null-ls-community* +null-ls-contributing null-ls.txt /*null-ls-contributing* +null-ls-documentation null-ls.txt /*null-ls-documentation* +null-ls-does-it-work-with-(other-plugin)? null-ls.txt /*null-ls-does-it-work-with-(other-plugin)?* +null-ls-examples null-ls.txt /*null-ls-examples* +null-ls-faq null-ls.txt /*null-ls-faq* +null-ls-features null-ls.txt /*null-ls-features* +null-ls-how-do-i-format-files-on-save? null-ls.txt /*null-ls-how-do-i-format-files-on-save?* +null-ls-how-do-i-format-files? null-ls.txt /*null-ls-how-do-i-format-files?* +null-ls-how-does-it-work? null-ls.txt /*null-ls-how-does-it-work?* +null-ls-migration null-ls.txt /*null-ls-migration* +null-ls-motivation null-ls.txt /*null-ls-motivation* +null-ls-none-ls.nvim null-ls.txt /*null-ls-none-ls.nvim* +null-ls-null-ls.nvim null-ls.txt /*null-ls-null-ls.nvim* +null-ls-parsing-buffer-content null-ls.txt /*null-ls-parsing-buffer-content* +null-ls-parsing-cli-program-output null-ls.txt /*null-ls-parsing-cli-program-output* +null-ls-setup null-ls.txt /*null-ls-setup* +null-ls-status null-ls.txt /*null-ls-status* +null-ls-table-of-contents null-ls.txt /*null-ls-table-of-contents* +null-ls-tests null-ls.txt /*null-ls-tests* +null-ls-will-it-affect-my-performance? null-ls.txt /*null-ls-will-it-affect-my-performance?* +null-ls.txt null-ls.txt /*null-ls.txt* diff --git a/lua/null-ls/builtins/diagnostics/regal.lua b/lua/null-ls/builtins/diagnostics/regal.lua new file mode 100644 index 00000000..a1204fba --- /dev/null +++ b/lua/null-ls/builtins/diagnostics/regal.lua @@ -0,0 +1,54 @@ +local h = require("null-ls.helpers") +local methods = require("null-ls.methods") +local log = require("null-ls.logger") + +local handle_regal_output = function(params) + local diags = {} + if params.output.violations ~= nil then + for _, d in ipairs(params.output.violations) do + if d.location ~= nil then + table.insert(diags, { + row = d.location.row, + col = d.location.col, + source = "regal", + message = d.description, + severity = vim.diagnostic.severity.ERROR, + filename = d.location.file, + code = d.title, + }) + end + end + elseif params.err ~= nil then + log:error(params.output) + end + + return diags +end + +return h.make_builtin({ + name = "regal", + meta = { + url = "https://docs.styra.com/regal", + description = "Regal is a linter for Rego, with the goal of making your Rego magnificent!.", + }, + method = methods.internal.DIAGNOSTICS_ON_SAVE, + filetypes = { "rego" }, + generator_opts = { + command = "regal", + args = { + "lint", + "-f", + "json", + "$ROOT", + }, + format = "json_raw", + check_exit_code = function(code) + return code <= 1 + end, + to_stdin = false, + from_stderr = true, + multiple_files = true, + on_output = handle_regal_output, + }, + factory = h.generator_factory, +}) diff --git a/test/spec/builtins/diagnostics_spec.lua b/test/spec/builtins/diagnostics_spec.lua index 2ce1d7d3..b126f4d7 100644 --- a/test/spec/builtins/diagnostics_spec.lua +++ b/test/spec/builtins/diagnostics_spec.lua @@ -1,9 +1,9 @@ -local mock = require("luassert.mock") local stub = require("luassert.stub") local spy = require("luassert.spy") local diagnostics = require("null-ls.builtins").diagnostics -mock(require("null-ls.logger"), true) + +stub(vim, "notify") describe("diagnostics", function() describe("spectral", function() @@ -1390,19 +1390,20 @@ describe("diagnostics", function() it("should create a diagnostic with error severity", function() local output = vim.json.decode([[ - { - "errors": [ - { - "message": "var tenant_id is unsafe", - "code": "rego_unsafe_var_error", - "location": { - "file": "src/geo.rego", - "row": 49, - "col": 3 + { + "errors": [ + { + "message": "var tenant_id is unsafe", + "code": "rego_unsafe_var_error", + "location": { + "file": "src/geo.rego", + "row": 49, + "col": 3 + } } - } - ] - } ]]) + ] + } + ]]) local diagnostic = parser({ output = output }) assert.same({ { @@ -1419,18 +1420,82 @@ describe("diagnostics", function() it("should not create a diagnostic without location", function() local output = vim.json.decode([[ - { - "errors": [ + { + "errors": [ + { + "message": "var tenant_id is unsafe", + "code": "rego_unsafe_var_error" + } + ] + } + ]]) + local diagnostic = parser({ output = output }) + assert.same({}, diagnostic) + end) + + end) + + describe("regal", function() + local linter = diagnostics.regal + local parser = linter._opts.on_output + + it("should create a diagnostic with error severity", function() + local output = vim.json.decode([[ + { + "violations": [ + { + "title": "prefer-snake-case", + "description": "Prefer snake_case for names", + "category": "style", + "level": "error", + "location": { + "col": 9, + "row": 3, + "file": "test.rego", + "text": "default allowRbac := true" + } + } + ] + } + ]]) + local diagnostic = parser({ output = output }) + assert.same({ { - "message": "var tenant_id is unsafe", - "code": "rego_unsafe_var_error" - } - ] - } ]]) + row = 3, + col = 9, + severity = 1, + message = "Prefer snake_case for names", + filename = "test.rego", + source = "regal", + code = "prefer-snake-case", + }, + }, diagnostic) + end) + + it("should not create a diagnostic without location", function() + local output = vim.json.decode([[ + { + "violations": [ + { + "title": "prefer-snake-case", + "description": "Prefer snake_case for names", + "category": "style", + "level": "error" + } + ] + } + ]]) local diagnostic = parser({ output = output }) assert.same({}, diagnostic) end) + + it("should log error for non-json output", function() + local diagnostic = parser({ output = "non-json-output", err = "json error" }) + assert.same({}, diagnostic) + assert.stub(vim.notify).was_called_with("[null-ls] non-json-output", vim.log.levels.ERROR, { title = "null-ls" }) + end) end) + describe("glslc", function() local linter = diagnostics.glslc local parser = linter._opts.on_output