From 1545d8d314e7d9f942787571534a55b41b8bca73 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 23 Jan 2025 14:29:04 +0000 Subject: [PATCH] feat: add --check_format=json|pretty Adds a new CLI option --check_format which accepts the values: 'json' (current behaviour), and a new 'pretty' value which prints a colorized human readable report to stdout, similar to common compilers and linters. Results are printed with color unless NO_COLOR is defined in the users environment, as per https://no-color.org. If --check_out_path is provided, then the results are always saved to file regardless of the --check_format value. --- changelog.md | 1 + locale/en-us/script.lua | 4 +- locale/ja-jp/script.lua | 4 +- locale/pt-br/script.lua | 4 +- locale/zh-cn/script.lua | 4 +- locale/zh-tw/script.lua | 4 +- script/cli/check.lua | 37 +++++++---- script/cli/check_worker.lua | 122 +++++++++++++++++++++++++++++++++--- script/global.d.lua | 3 + 9 files changed, 156 insertions(+), 27 deletions(-) diff --git a/changelog.md b/changelog.md index 313cb53bf..89a2bf6fe 100644 --- a/changelog.md +++ b/changelog.md @@ -30,6 +30,7 @@ cfgs[2] = {} -- only warns missing `b` ``` This enables the previous missing field check behavior before [#2970](https://github.com/LuaLS/lua-language-server/issues/2970) +* `NEW` Added `--check_format=json|pretty` for use with `--check` to output diagnostics in a human readable format. ## 3.13.5 `2024-12-20` diff --git a/locale/en-us/script.lua b/locale/en-us/script.lua index 9c9163ae4..ee96900c7 100644 --- a/locale/en-us/script.lua +++ b/locale/en-us/script.lua @@ -648,8 +648,10 @@ CLI_CHECK_SUCCESS = 'Diagnosis completed, no problems found' CLI_CHECK_PROGRESS = 'Found {} problems in {} files' -CLI_CHECK_RESULTS = +CLI_CHECK_RESULTS_OUTPATH = 'Diagnosis complete, {} problems found, see {}' +CLI_CHECK_RESULTS_PRETTY = +'Diagnosis complete, {} problems found' CLI_CHECK_MULTIPLE_WORKERS = 'Starting {} worker tasks, progress output will be disabled. This may take a few minutes.' CLI_DOC_INITING = diff --git a/locale/ja-jp/script.lua b/locale/ja-jp/script.lua index 3c0cd48d6..6e1819d98 100644 --- a/locale/ja-jp/script.lua +++ b/locale/ja-jp/script.lua @@ -649,8 +649,10 @@ CLI_CHECK_SUCCESS = '診断が完了しました。問題は見つかりませんでした' CLI_CHECK_PROGRESS = '{} ファイルに渡り、{} 個の問題が発見されました' -CLI_CHECK_RESULTS = +CLI_CHECK_RESULTS_OUTPATH = '診断が完了しました。{} 個の問題が発見されました。詳しくは {} をご確認ください' +CLI_CHECK_RESULTS_PRETTY = +'診断が完了しました。{} 個の問題が発見されました' CLI_CHECK_MULTIPLE_WORKERS = '{} 個のワーカータスクを開始しているため、進行状況の出力が無効になります。完了まで数分かかることがあります。' CLI_DOC_INITING = diff --git a/locale/pt-br/script.lua b/locale/pt-br/script.lua index e763fb6c4..73d459d75 100644 --- a/locale/pt-br/script.lua +++ b/locale/pt-br/script.lua @@ -648,8 +648,10 @@ CLI_CHECK_SUCCESS = 'Diagnóstico completo, nenhum problema encontrado' CLI_CHECK_PROGRESS = -- TODO: need translate! 'Found {} problems in {} files' -CLI_CHECK_RESULTS = +CLI_CHECK_RESULTS_OUTPATH = 'Diagnóstico completo, {} problemas encontrados, veja {}' +CLI_CHECK_RESULTS_PRETTY = +'Diagnóstico completo, {} problemas encontrados' CLI_CHECK_MULTIPLE_WORKERS = -- TODO: need translate! 'Starting {} worker tasks, progress output will be disabled. This may take a few minutes.' CLI_DOC_INITING = -- TODO: need translate! diff --git a/locale/zh-cn/script.lua b/locale/zh-cn/script.lua index 0b748d84f..22f16eaac 100644 --- a/locale/zh-cn/script.lua +++ b/locale/zh-cn/script.lua @@ -648,8 +648,10 @@ CLI_CHECK_SUCCESS = '诊断完成,没有发现问题' CLI_CHECK_PROGRESS = '检测到问题 {} 在文件 {} 中' -CLI_CHECK_RESULTS = +CLI_CHECK_RESULTS_OUTPATH = '诊断完成,共有 {} 个问题,请查看 {}' +CLI_CHECK_RESULTS_PRETTY = +'诊断完成,共有 {} 个问题' CLI_CHECK_MULTIPLE_WORKERS = '开启 {} 个工作任务,进度输出将会被禁用。这可能会花费几分钟。' CLI_DOC_INITING = diff --git a/locale/zh-tw/script.lua b/locale/zh-tw/script.lua index 31e885208..090fb7ff0 100644 --- a/locale/zh-tw/script.lua +++ b/locale/zh-tw/script.lua @@ -648,8 +648,10 @@ CLI_CHECK_SUCCESS = '診斷完成,沒有發現問題' CLI_CHECK_PROGRESS = -- TODO: need translate! 'Found {} problems in {} files' -CLI_CHECK_RESULTS = +CLI_CHECK_RESULTS_OUTPATH = '診斷完成,共有 {} 個問題,請查看 {}' +CLI_CHECK_RESULTS_PRETTY = +'診斷完成,共有 {} 個問題' CLI_CHECK_MULTIPLE_WORKERS = -- TODO: need translate! 'Starting {} worker tasks, progress output will be disabled. This may take a few minutes.' CLI_DOC_INITING = -- TODO: need translate! diff --git a/script/cli/check.lua b/script/cli/check.lua index 3a5000428..7f38dfea4 100644 --- a/script/cli/check.lua +++ b/script/cli/check.lua @@ -11,7 +11,7 @@ local function logFileForThread(threadId) return LOGPATH .. '/check-partial-' .. threadId .. '.json' end -local function buildArgs(exe, numThreads, threadId) +local function buildArgs(exe, numThreads, threadId, format, quiet) local args = {exe} local skipNext = false for i = 1, #arg do @@ -35,7 +35,12 @@ local function buildArgs(exe, numThreads, threadId) args[#args + 1] = '--thread_id' args[#args + 1] = tostring(threadId) if numThreads > 1 then - args[#args + 1] = '--quiet' + if quiet then + args[#args + 1] = '--quiet' + end + if format then + args[#args + 1] = '--check_format=' .. format + end args[#args + 1] = '--check_out_path' args[#args + 1] = logFileForThread(threadId) end @@ -62,7 +67,7 @@ function export.runCLI() local procs = {} for i = 1, numThreads do - local process, err = subprocess.spawn({buildArgs(exe, numThreads, i)}) + local process, err = subprocess.spawn({buildArgs(exe, numThreads, i, CHECK_FORMAT, QUIET)}) if err then print(err) end @@ -76,11 +81,6 @@ function export.runCLI() checkPassed = process:wait() == 0 and checkPassed end - local outpath = CHECK_OUT_PATH - if outpath == nil then - outpath = LOGPATH .. '/check.json' - end - if numThreads > 1 then local mergedResults = {} local count = 0 @@ -95,11 +95,22 @@ function export.runCLI() end end end - util.saveFile(outpath, jsonb.beautify(mergedResults)) - if count == 0 then - print(lang.script('CLI_CHECK_SUCCESS')) - else - print(lang.script('CLI_CHECK_RESULTS', count, outpath)) + + local outpath = nil + + if CHECK_FORMAT == 'json' or CHECK_OUT_PATH then + outpath = CHECK_OUT_PATH or LOGPATH .. '/check.json' + util.saveFile(outpath, jsonb.beautify(mergedResults)) + end + + if not QUIET then + if count == 0 then + print(lang.script('CLI_CHECK_SUCCESS')) + elseif outpath then + print(lang.script('CLI_CHECK_RESULTS_OUTPATH', count, outpath)) + else + print(lang.script('CLI_CHECK_RESULTS_PRETTY', count)) + end end end return checkPassed and 0 or 1 diff --git a/script/cli/check_worker.lua b/script/cli/check_worker.lua index a3139c3ba..353926395 100644 --- a/script/cli/check_worker.lua +++ b/script/cli/check_worker.lua @@ -17,6 +17,100 @@ require 'vm' local export = {} +local colors + +if not os.getenv('NO_COLOR') then + colors = { + red = '\27[31m', + green = '\27[32m', + yellow = '\27[33m', + blue = '\27[34m', + magenta = '\27[35m', + white = '\27[37m', + grey = '\27[90m', + reset = '\27[0m' + } +else + colors = { + red = '', + green = '', + yellow = '', + blue = '', + magenta = '', + white = '', + grey = '', + reset = '' + } +end + +--- @type table +local severity_colors = { + Error = colors.red, + Warning = colors.yellow, + Information = colors.white, + Hint = colors.white, +} + +local severity_str = {} --- @type table +for k, v in pairs(define.DiagnosticSeverity) do + severity_str[v] = k +end + +local pwd + +---@param path string +---@return string +local function relpath(path) + if not pwd then + pwd = furi.decode(furi.encode(fs.current_path():string())) + end + if pwd and path:sub(1, #pwd) == pwd then + path = path:sub(#pwd + 2) + end + return path +end + +local function report_pretty(uri, diags) + local path = relpath(furi.decode(uri)) + + local lines = {} --- @type string[] + pcall(function() + for line in io.lines(path) do + table.insert(lines, line) + end + end) + + for _, d in ipairs(diags) do + local rstart = d.range.start + local rend = d.range['end'] + local severity = severity_str[d.severity] + print( + ('%s%s:%s:%s%s [%s%s%s] %s %s(%s)%s'):format( + colors.blue, + path, + rstart.line + 1, -- Use 1-based indexing + rstart.character + 1, -- Use 1-based indexing + colors.reset, + severity_colors[severity], + severity, + colors.reset, + d.message, + colors.magenta, + d.code, + colors.reset + ) + ) + if #lines > 0 then + io.write(' ', lines[rstart.line + 1], '\n') + io.write(' ', colors.grey, (' '):rep(rstart.character), '^') + if rstart.line == rend.line then + io.write(('^'):rep(rend.character - rstart.character - 1)) + end + io.write(colors.reset, '\n') + end + end +end + local function clear_line() -- Write out empty space to ensure that the previous lien is cleared. io.write('\x0D', (' '):rep(80), '\x0D') @@ -86,6 +180,7 @@ function export.runCLI() local numThreads = tonumber(NUM_THREADS or 1) local threadId = tonumber(THREAD_ID or 1) + local quiet = QUIET or numThreads > 1 if type(CHECK_WORKER) ~= 'string' then print(lang.script('CLI_CHECK_ERROR_TYPE', type(CHECK_WORKER))) @@ -127,9 +222,13 @@ function export.runCLI() client:register('textDocument/publishDiagnostics', function (params) results[params.uri] = params.diagnostics + if not QUIET and (CHECK_FORMAT == nil or CHECK_FORMAT == 'pretty') then + clear_line() + report_pretty(params.uri, params.diagnostics) + end end) - if not QUIET then + if not quiet then io.write(lang.script('CLI_CHECK_INITING')) end @@ -153,15 +252,15 @@ function export.runCLI() diag.doDiagnostic(uri, true) -- Print regularly but always print the last entry to ensure -- that logs written to files don't look incomplete. - if not QUIET and (os.clock() - lastClock > 0.2 or i == #uris) then + if not quiet and (os.clock() - lastClock > 0.2 or i == #uris) then lastClock = os.clock() client:update() report_progress(i, max, results) end end end - if not QUIET then - io.write('\x0D') + if not quiet then + clear_line() end end) @@ -173,16 +272,21 @@ function export.runCLI() end end - local outpath = CHECK_OUT_PATH or LOGPATH .. '/check.json' + local outpath = nil - -- Always write result, even if it's empty to make sure no one accidentally looks at an old output after a successful run. - util.saveFile(outpath, jsonb.beautify(results)) + if CHECK_FORMAT == 'json' or CHECK_OUT_PATH then + outpath = CHECK_OUT_PATH or LOGPATH .. '/check.json' + -- Always write result, even if it's empty to make sure no one accidentally looks at an old output after a successful run. + util.saveFile(outpath, jsonb.beautify(results)) + end - if not QUIET then + if not quiet then if count == 0 then print(lang.script('CLI_CHECK_SUCCESS')) + elseif outpath then + print(lang.script('CLI_CHECK_RESULTS_OUTPATH', count, outpath)) else - print(lang.script('CLI_CHECK_RESULTS', count, outpath)) + print(lang.script('CLI_CHECK_RESULTS_PRETTY', count)) end end return count == 0 and 0 or 1 diff --git a/script/global.d.lua b/script/global.d.lua index daac5f6c8..f4e4b336b 100644 --- a/script/global.d.lua +++ b/script/global.d.lua @@ -69,6 +69,9 @@ CHECKLEVEL = 'Warning' ---@type string|nil CHECK_OUT_PATH = '' +---@type string | 'json' | 'pretty' +CHECK_FORMAT = 'pretty' + ---@type 'trace' | 'debug' | 'info' | 'warn' | 'error' LOGLEVEL = 'warn'