├── LICENSE ├── README.md ├── linter.lua ├── linter_ameba.lua ├── linter_demo.gif ├── linter_dscanner.lua ├── linter_eslint.lua ├── linter_flake8.lua ├── linter_gocompiler.lua ├── linter_golint.lua ├── linter_jshint.lua ├── linter_luacheck.lua ├── linter_nim.lua ├── linter_php.lua ├── linter_pylint.lua ├── linter_revive ├── linter_selene.lua ├── linter_shellcheck.lua ├── linter_standard.lua ├── linter_teal.lua └── linter_zig.lua /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Daniel Margarido 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # linters 2 | Linters for https://github.com/rxi/lite 3 | 4 | ## Available linters 5 | 6 | * linter\_ameba - Uses ameba for crystal files 7 | * linter\_dscanner - Uses dscanner for d files 8 | * linter\_eslint - Uses eslint for javascript files 9 | * linter\_flake8 - Uses flake8 for python files 10 | * linter\_gocompiler - Uses the go vet command to display compilation errors in golang files 11 | * linter\_golint - Uses golint for golang files 12 | * linter\_jshint - Uses jshint linter for javascript files 13 | * linter\_luacheck - Uses luacheck for lua files 14 | * linter\_teal - Uses tl check for teal files 15 | * linter\_nim - Uses nim check for nim files 16 | * linter\_php - Uses built-in php binary -l flag for php files 17 | * linter\_pylint - Uses pylint for python files 18 | * linter\_revive - Uses revive for golang files (golint modern alt) 19 | * linter\_selene - Uses selene for lua files 20 | * linter\_shellcheck - Uses shellcheck linter for shell script files 21 | * linter\_standard - Uses standard linter for javascript files 22 | * linter_zig - Uses `zig ast-check` for linting zig files 23 | 24 | ## Demo 25 | 26 | Example of linting a file. 27 | 28 | ![Linter demo](/linter_demo.gif) 29 | 30 | ## Instructions 31 | 32 | 1. To install any linter first copy the `linter.lua` file to the folder 33 | `data/plugins/` of the lite editor which has the needed foundation for the linters. 34 | 2. Copy the specific linters you want to use to the same folder. 35 | 3. Make sure you have the commands needed for each linter to use them. 36 | 4. If you want to configure options in some of the linters you can edit your `data/user/init.lua` 37 | 38 | ```lua 39 | local config = require "core.config" 40 | local insert = table.insert 41 | 42 | -- [[ Each linter will load the arguments from a table in config._args ]] 43 | 44 | -- Mark global as known to the linter 45 | insert(config.luacheck_args, "--new-globals=love") 46 | 47 | -- Add error reporting in fuctions which exceed cyclomatic complexity 48 | insert(config.luacheck_args, "--max-cyclomatic-complexity=4") 49 | 50 | -- Add warnings if lines pass a maximum length 51 | insert(config.luacheck_args, "--max-line-length=80") 52 | ``` 53 | 54 | ## Linter Fields 55 | 56 | **file\_patterns** {String} - List of patterns to match the files to which the 57 | linter is run. 58 | 59 | **warning\_pattern** String | Function - Matches the line, column and the 60 | description for each warning(In this order). If the matching is complex and 61 | cannot be addressed with a pattern directly a function can be used. 62 | 63 | **warning\_pattern\_order** {line=Int, col=Int, message=Int} [optional] - Allows us 64 | to change what is expected from the pattern when the order of the elements in 65 | the warning is not _line -> col -> message_. 66 | 67 | **column\_starts\_at\_zero** Bool [optional] - Is useful for some linters which have 68 | the column number starting at zero instead of starting at one. 69 | 70 | **command** String - Command that is run to execute the linter. It can receive a 71 | $FILENAME name in the middle of the command which will be replaced with the name 72 | of the file which is being edited when the linter is run. 73 | 74 | **deduplicate** Bool [optional] - Prevent duplicated warnings. Only needed in 75 | specific cases because some linters work with whole packages instead of a 76 | specific file and report the same warning for the same file multiple times. 77 | 78 | **args** {String} - Arguments to be passed to the linter in the command 79 | line call. Will replace the $ARGS in the command. Usually this table comes from 80 | _config.\\_args_. This is done so the user can add it's own specific 81 | configurations to the linter in his own user file. 82 | 83 | **expected\_exitcodes** {Int} - Exit codes expected when the linter doesn't crash 84 | so we can report when there is an unexpected crash. 85 | 86 | ## Configurations 87 | 88 | The base linter code also allows some configurations that apply to all linters. 89 | You can set them in you user file - `data/user/init.lua`. 90 | 91 | The available configurations are: 92 | * linter\_box\_line\_limit - Number of columns in the warning box. (default: 80) 93 | * linter\_scan\_interval - Seconds between checks for results in the linter file. (default: 0.1) 94 | * warning\_font - Font to be used inside the warning box. (default: style.font) 95 | * linter\_trigger - Select how the linter is activated. (default: "save", options: ["save", "keypress"]) 96 | 97 | Example configuration: 98 | ```lua 99 | config.warning_font = renderer.font.load(EXEDIR .. "/data/fonts/monospace.ttf", 13.5 * SCALE) 100 | config.linter_trigger = "keypress" 101 | ``` 102 | -------------------------------------------------------------------------------- /linter.lua: -------------------------------------------------------------------------------- 1 | local core = require "core" 2 | local style = require "core.style" 3 | local command = require "core.command" 4 | local config = require "core.config" 5 | local DocView = require "core.docview" 6 | local StatusView = require "core.statusview" 7 | local Doc = require "core.doc" 8 | 9 | config.linter_box_line_limit = 80 -- characters limit 10 | config.linter_scan_interval = 0.1 -- scan every 100 ms 11 | 12 | -- Available Triggers - "save" "keypress" 13 | -- 14 | -- Most linters will not work fast enought to use keypress as the event, use it 15 | -- only if you are sure of what you are doing. For keypress to work we enforce 16 | -- auto save on every keypress. 17 | config.linter_trigger = "save" 18 | config.warning_font = style.font 19 | 20 | 21 | -- environments 22 | local is_windows = PATHSEP == "\\" 23 | local command_sep = is_windows and "&" or ";" 24 | local exitcode_cmd = is_windows and "echo %errorlevel%" or "echo $?" 25 | 26 | local current_doc = nil 27 | local cache = setmetatable({}, { __mode = "k" }) 28 | local hover_boxes = setmetatable({}, { __mode = "k" }) 29 | local linter_queue = {} 30 | local linters = {} 31 | 32 | local function completed(proc) 33 | local current_time = os.time() 34 | local diff = os.difftime(proc.start, current_time) 35 | if diff > proc.timeout then 36 | proc.callback(nil, "Timeout reached") 37 | return true 38 | end 39 | 40 | if not proc.doc.ref then -- if the doc is destroyed, delete the item too 41 | proc.callback(nil, "Weak reference destroyed") 42 | return true 43 | end 44 | 45 | local fp = io.open(proc.status) 46 | if io.type(fp) == "file" then 47 | local output = "" 48 | local exitcode = fp:read("*n") 49 | fp:close() 50 | os.remove(proc.status) 51 | 52 | fp = io.open(proc.output, "r") 53 | if io.type(fp) == "file" then 54 | output = fp:read("*a") 55 | fp:close() 56 | os.remove(proc.output) 57 | end 58 | 59 | proc.callback({ output = output, exitcode = exitcode }) 60 | return true 61 | end 62 | return false 63 | end 64 | 65 | local function lint_completion_thread() 66 | while true do 67 | coroutine.yield(config.linter_scan_interval) 68 | 69 | local j, n = 1, #linter_queue 70 | for i = 1, n, 1 do 71 | if not completed(linter_queue[i]) then 72 | -- move i to j since we want to keep it 73 | if i ~= j then 74 | linter_queue[j] = linter_queue[i] 75 | linter_queue[i] = nil 76 | end 77 | j = j + 1 78 | else 79 | -- remove i 80 | linter_queue[i] = nil 81 | end 82 | end 83 | end 84 | end 85 | core.add_thread(lint_completion_thread) 86 | 87 | local function async_run_lint_cmd(doc, path, linter, callback, timeout) 88 | timeout = timeout or 10 89 | local cmd = linter.command:gsub("$FILENAME", string.format("%q", path)) 90 | local args = table.concat(linter.args or {}, " ") 91 | cmd = cmd:gsub("$ARGS", args) 92 | 93 | local output_file = core.temp_filename() 94 | local status_file = core.temp_filename() 95 | local start_time = os.time() 96 | cmd = string.format("%s > %q 2>&1 %s %s > %q", 97 | cmd, 98 | output_file, 99 | command_sep, 100 | exitcode_cmd, 101 | status_file) 102 | system.exec(cmd) 103 | 104 | table.insert(linter_queue, { 105 | output = output_file, 106 | status = status_file, 107 | start = start_time, 108 | timeout = timeout, 109 | callback = callback, 110 | doc = setmetatable({ ref = doc }, { __mode = 'v' }) 111 | }) 112 | end 113 | 114 | local function match_pattern(text, pattern, order, filename) 115 | if type(pattern) == "function" then 116 | return coroutine.wrap(function() 117 | pattern(text, filename) 118 | end) 119 | end 120 | 121 | if order == nil then 122 | return text:gmatch(pattern) 123 | end 124 | 125 | return coroutine.wrap(function() 126 | for one, two, three in text:gmatch(pattern) do 127 | local fields = {one, two, three} 128 | local ordered = {line = 1, col = 1, message = "syntax error"} 129 | for field,position in pairs(order) do 130 | ordered[field] = fields[position] or ordered[field] 131 | if 132 | field == "line" 133 | and current_doc ~= nil 134 | and tonumber(ordered[field]) > #current_doc.lines 135 | then 136 | ordered[field] = #current_doc.lines 137 | end 138 | end 139 | coroutine.yield(ordered.line, ordered.col, ordered.message) 140 | end 141 | end) 142 | end 143 | 144 | 145 | local function is_duplicate(line_warns, col, warn) 146 | for _, w in ipairs(line_warns) do 147 | if w.col == col and w.text == warn then 148 | return true 149 | end 150 | end 151 | return false 152 | end 153 | 154 | -- Escape string so it can be used in a lua pattern 155 | local to_escape = { 156 | ["%"] = true, 157 | ["("] = true, 158 | [")"] = true, 159 | ["."] = true, 160 | ["+"] = true, 161 | ["-"] = true, 162 | ["*"] = true, 163 | ["["] = true, 164 | ["]"] = true, 165 | ["?"] = true, 166 | ["^"] = true, 167 | ["$"] = true 168 | } 169 | local function escape_to_pattern(text, count) 170 | count = count or 1 171 | local escaped = {} 172 | for char in text:gmatch(".") do 173 | if to_escape[char] then 174 | for _=1,count do 175 | table.insert(escaped, "%") 176 | end 177 | end 178 | table.insert(escaped, char) 179 | end 180 | return table.concat(escaped, "") 181 | end 182 | 183 | local function async_get_file_warnings(doc, warnings, linter, callback) 184 | local path = system.absolute_path(doc.filename) 185 | local double_escaped = escape_to_pattern(path, 2) 186 | local pattern = linter.warning_pattern 187 | if type(pattern) == "string" then 188 | pattern = pattern:gsub("$FILENAME", double_escaped) 189 | end 190 | 191 | local function on_linter_completion(data, error) 192 | if data == nil then 193 | return callback(nil, error) 194 | end 195 | 196 | local text = data.output 197 | if linter.expected_exitcodes then 198 | local valid_code = false 199 | for _, exitcode in ipairs(linter.expected_exitcodes) do 200 | if data.exitcode == exitcode then 201 | valid_code = true 202 | end 203 | end 204 | 205 | if not valid_code then 206 | return callback(nil, text) 207 | end 208 | end 209 | 210 | local order = linter.warning_pattern_order 211 | for line, col, warn in match_pattern(text, pattern, order, path) do 212 | line = tonumber(line) 213 | col = tonumber(col) 214 | if linter.column_starts_at_zero then 215 | col = col + 1 216 | end 217 | if not warnings[line] then 218 | warnings[line] = {} 219 | end 220 | 221 | local deduplicate = linter.deduplicate or false 222 | local exists = deduplicate and is_duplicate(warnings[line], col, warn) 223 | if not exists then 224 | table.insert(warnings[line], {col=col, text=warn}) 225 | end 226 | end 227 | callback(true) 228 | end 229 | 230 | async_run_lint_cmd(doc, path, linter, on_linter_completion) 231 | end 232 | 233 | local function matches_any(filename, patterns) 234 | for _, ptn in ipairs(patterns) do 235 | if filename:find(ptn) then return true end 236 | end 237 | end 238 | 239 | 240 | local function matching_linters(filename) 241 | local matched = {} 242 | for _, l in ipairs(linters) do 243 | if matches_any(filename, l.file_patterns) then 244 | table.insert(matched, l) 245 | end 246 | end 247 | return matched 248 | end 249 | 250 | 251 | local function update_cache(doc) 252 | local lints = matching_linters(doc.filename or "") 253 | if not lints[1] then return end 254 | 255 | local d = {} 256 | for _, l in ipairs(lints) do 257 | local linter_name = l.command:match("%S+") 258 | core.log("Linting %s with %s...", doc.filename, linter_name) 259 | async_get_file_warnings(doc, d, l, function(success, error) 260 | if not success then 261 | core.log("Error linting %s with linter %s: %s", doc.filename, linter_name, error) 262 | print(error) 263 | return 264 | end 265 | 266 | local i = 0 267 | for idx, t in pairs(d) do 268 | t.line_text = doc.lines[idx] or "" 269 | i = i + 1 270 | end 271 | cache[doc] = d 272 | core.log("Done linting %s, found %d warning(s).", doc.filename, i) 273 | end) 274 | end 275 | end 276 | 277 | 278 | local function get_word_limits(v, line_text, x, col) 279 | if col == 0 then col = 1 end 280 | local _, e = line_text:sub(col):find(config.symbol_pattern) 281 | if not e or e <= 0 then e = 1 end 282 | e = e + col - 1 283 | 284 | local font = v:get_font() 285 | local x1 = x + font:get_width(line_text:sub(1, col - 1)) 286 | local x2 = x + font:get_width(line_text:sub(1, e)) 287 | return x1, x2 288 | end 289 | 290 | 291 | local clean = Doc.clean 292 | function Doc:clean(...) 293 | current_doc = self 294 | clean(self, ...) 295 | update_cache(self) 296 | end 297 | 298 | local new = Doc.new 299 | function Doc:new(...) 300 | current_doc = self 301 | new(self, ...) 302 | update_cache(self) 303 | end 304 | 305 | -- Document action overrides to make trigger on keypress work. 306 | -- Keypress trigger works by saving on every adition / removal. 307 | local text_input = Doc.text_input 308 | function Doc:text_input(...) 309 | text_input(self, ...) 310 | if config.linter_trigger == "keypress" then 311 | self:save() 312 | end 313 | end 314 | 315 | local delete_to = Doc.delete_to 316 | function Doc:delete_to(...) 317 | delete_to(self, ...) 318 | if config.linter_trigger == "keypress" then 319 | self:save() 320 | end 321 | end 322 | 323 | local undo = Doc.undo 324 | function Doc:undo(...) 325 | undo(self, ...) 326 | if config.linter_trigger == "keypress" then 327 | self:save() 328 | end 329 | end 330 | 331 | local redo = Doc.redo 332 | function Doc:redo(...) 333 | redo(self, ...) 334 | if config.linter_trigger == "keypress" then 335 | self:save() 336 | end 337 | end 338 | 339 | 340 | 341 | local on_mouse_wheel = DocView.on_mouse_wheel 342 | function DocView:on_mouse_wheel(...) 343 | on_mouse_wheel(self, ...) 344 | hover_boxes[self] = nil 345 | end 346 | 347 | 348 | local on_mouse_moved = DocView.on_mouse_moved 349 | function DocView:on_mouse_moved(px, py, ...) 350 | on_mouse_moved(self, px, py, ...) 351 | 352 | local doc = self.doc 353 | local cached = cache[doc] 354 | if not cached then return end 355 | 356 | -- Check mouse is over this view 357 | local x, y, w, h = self.position.x, self.position.y, self.size.x, self.size.y 358 | if px < x or px > x + w or py < y or py > y + h then 359 | hover_boxes[self] = nil 360 | return 361 | end 362 | 363 | -- Detect if any warning is hovered 364 | local hovered = {} 365 | local hovered_w = {} 366 | for line, warnings in pairs(cached) do 367 | local text = doc.lines[line] 368 | if text == warnings.line_text then 369 | for _, warning in ipairs(warnings) do 370 | local x, y = self:get_line_screen_position(line) 371 | local x1, x2 = get_word_limits(self, text, x, warning.col) 372 | local h = self:get_line_height() 373 | if px > x1 and px <= x2 and py > y and py <= y + h then 374 | table.insert(hovered_w, warning.text) 375 | hovered.x = px 376 | hovered.y = y + h 377 | end 378 | end 379 | end 380 | end 381 | hovered.warnings = hovered_w 382 | hover_boxes[self] = hovered.warnings[1] and hovered 383 | end 384 | 385 | 386 | local draw_line_text = DocView.draw_line_text 387 | function DocView:draw_line_text(idx, x, y) 388 | draw_line_text(self, idx, x, y) 389 | 390 | local doc = self.doc 391 | local cached = cache[doc] 392 | if not cached then return end 393 | 394 | local line_warnings = cached[idx] 395 | if not line_warnings then return end 396 | 397 | -- Don't draw underlines if line text has changed 398 | if line_warnings.line_text ~= doc.lines[idx] then 399 | return 400 | end 401 | 402 | -- Draws lines in linted places 403 | local text = doc.lines[idx] 404 | for _, warning in ipairs(line_warnings) do 405 | local x1, x2 = get_word_limits(self, text, x, warning.col) 406 | local color = style.linter_warning or style.syntax.literal 407 | local h = style.divider_size 408 | local line_h = self:get_line_height() 409 | renderer.draw_rect(x1, y + line_h - h, x2 - x1, h, color) 410 | end 411 | end 412 | 413 | 414 | local function text_in_lines(text, max_len) 415 | local text_lines = {} 416 | local line = "" 417 | for word, seps in text:gmatch("([%S]+)([%c%s]*)") do 418 | if #line + #word > max_len then 419 | table.insert(text_lines, line) 420 | line = "" 421 | end 422 | line=line..word 423 | for sep in seps:gmatch(".") do 424 | if sep == "\n" then 425 | table.insert(text_lines, line) 426 | line = "" 427 | else 428 | line=line..sep 429 | end 430 | end 431 | end 432 | if #line > 0 then 433 | table.insert(text_lines, line) 434 | end 435 | return text_lines 436 | end 437 | 438 | 439 | local function draw_warning_box(hovered_item) 440 | local font = config.warning_font 441 | local th = font:get_height() 442 | local pad = style.padding 443 | 444 | local max_len = config.linter_box_line_limit 445 | local full_text = table.concat(hovered_item.warnings, "\n\n") 446 | local lines = text_in_lines(full_text, max_len) 447 | 448 | -- draw background rect 449 | local rx = hovered_item.x - pad.x 450 | local ry = hovered_item.y 451 | local text_width = 0 452 | for _, line in ipairs(lines) do 453 | local w = font:get_width(line) 454 | text_width = math.max(text_width, w) 455 | end 456 | local rw = text_width + pad.x * 2 457 | local rh = (th * #lines) + pad.y * 2 458 | renderer.draw_rect(rx, ry, rw, rh, style.background3) 459 | 460 | -- draw text 461 | local color = style.text 462 | local x = rx + pad.x 463 | for i, line in ipairs(lines) do 464 | local y = ry + pad.y + th * (i - 1) 465 | renderer.draw_text(font, line, x, y, color) 466 | end 467 | end 468 | 469 | 470 | local draw = DocView.draw 471 | function DocView:draw() 472 | draw(self) 473 | if hover_boxes[self] then 474 | core.root_view:defer_draw(draw_warning_box, hover_boxes[self]) 475 | end 476 | end 477 | 478 | 479 | local get_items = StatusView.get_items 480 | function StatusView:get_items() 481 | local left, right = get_items(self) 482 | 483 | local doc = core.active_view.doc 484 | local cached = cache[doc or ""] 485 | if cached then 486 | local count = 0 487 | for _, v in pairs(cached) do 488 | count = count + #v 489 | end 490 | table.insert(left, StatusView.separator) 491 | if not doc:is_dirty() and count > 0 then 492 | table.insert(left, style.text) 493 | else 494 | table.insert(left, style.dim) 495 | end 496 | table.insert(left, "warnings: " .. count) 497 | end 498 | 499 | return left, right 500 | end 501 | 502 | 503 | local function has_cached() 504 | return core.active_view.doc and cache[core.active_view.doc] 505 | end 506 | 507 | command.add(has_cached, { 508 | ["linter:move-to-next-warning"] = function() 509 | local doc = core.active_view.doc 510 | local line = doc:get_selection() 511 | local cached = cache[doc] 512 | local idx, min = math.huge, math.huge 513 | for k in pairs(cached) do 514 | if type(k) == "number" then 515 | min = math.min(k, min) 516 | if k < idx and k > line then idx = k end 517 | end 518 | end 519 | idx = (idx == math.huge) and min or idx 520 | if idx == math.huge then 521 | core.error("Document does not contain any warnings") 522 | return 523 | end 524 | if cached[idx] then 525 | doc:set_selection(idx, cached[idx][1].col) 526 | core.active_view:scroll_to_line(idx, true) 527 | end 528 | end, 529 | }) 530 | 531 | 532 | return { 533 | add_language = function(lang) 534 | table.insert(linters, lang) 535 | end, 536 | escape_to_pattern = escape_to_pattern 537 | } 538 | -------------------------------------------------------------------------------- /linter_ameba.lua: -------------------------------------------------------------------------------- 1 | local core = require "core" 2 | local config = require "core.config" 3 | local linter = require "plugins.linter" 4 | 5 | config.ameba_args = {} 6 | 7 | linter.add_language { 8 | file_patterns = {"%.cr$"}, 9 | warning_pattern = "[^:]:(%d+):(%d+)\n[%s]?([^\n]+)", 10 | command = core.project_dir .. "/bin/ameba $FILENAME $ARGS --no-color", 11 | args = config.ameba_args 12 | } 13 | -------------------------------------------------------------------------------- /linter_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drmargarido/linters/eb1611eaade6e5328df5172bd3f759d853c33a31/linter_demo.gif -------------------------------------------------------------------------------- /linter_dscanner.lua: -------------------------------------------------------------------------------- 1 | local config = require "core.config" 2 | local linter = require "plugins.linter" 3 | 4 | config.dscanner_args = {"-S"} 5 | 6 | linter.add_language { 7 | file_patterns = {"%.d$"}, 8 | warning_pattern = "[^:]%((%d+):(%d+)%)[%s]?([^\n]+)", 9 | command = "dscanner $FILENAME $ARGS", 10 | args = config.dscanner_args, 11 | expected_exitcodes = {0, 1} 12 | } 13 | -------------------------------------------------------------------------------- /linter_eslint.lua: -------------------------------------------------------------------------------- 1 | -- linter for eslint 2 | 3 | local config = require "core.config" 4 | local linter = require "plugins.linter" 5 | 6 | -- add --fix to your args for auto-fixing. 7 | config.eslint_args = {} 8 | 9 | linter.add_language { 10 | file_patterns = {"%.js$"}, 11 | warning_pattern = "[^:]:(%d+):(%d+): ([^\n]+)", 12 | command = "eslint --format unix $ARGS $FILENAME", 13 | args = config.eslint_args, 14 | expected_exitcodes = {0, 1} 15 | } 16 | -------------------------------------------------------------------------------- /linter_flake8.lua: -------------------------------------------------------------------------------- 1 | local config = require "core.config" 2 | local linter = require "plugins.linter" 3 | 4 | config.flake8_args = {} 5 | 6 | linter.add_language { 7 | file_patterns = {"%.py$"}, 8 | warning_pattern = "[^:]:(%d+):(%d+):%s[%w]+%s([^\n]*)", 9 | command = "flake8 $ARGS $FILENAME", 10 | args = config.flake8_args, 11 | expected_exitcodes = {0, 1} 12 | } 13 | -------------------------------------------------------------------------------- /linter_gocompiler.lua: -------------------------------------------------------------------------------- 1 | local linter = require "plugins.linter" 2 | local PATHSEP = package.config:sub(1, 1) 3 | 4 | local pattern = function(text, filename) 5 | local line, col, warn, path, l, c, w 6 | for line_text in text:gmatch("[^\n]+") do 7 | path, l, c, w = line_text:match("([^:]+):(%d+):(%d+):[%s]?([^\n]+)") 8 | local has_filename = path and filename:match(linter.escape_to_pattern(path)) 9 | if path then 10 | if warn then -- End of the last warning 11 | coroutine.yield(line, col, warn) 12 | warn = nil 13 | end 14 | if has_filename then line, col, warn = l, c, w end 15 | else 16 | if warn then warn = warn.."\n"..line_text end 17 | end 18 | end 19 | -- When we reach the end of the lines we didn't report the last warning 20 | if warn then coroutine.yield(line, col, warn) end 21 | end 22 | 23 | 24 | linter.add_language { 25 | file_patterns = {"%.go$"}, 26 | warning_pattern = pattern, 27 | command = "go vet -source $FILENAME"..PATHSEP..".."..PATHSEP.." 2>&1", 28 | args = {}, 29 | expected_exitcodes = {0, 1} 30 | } 31 | -------------------------------------------------------------------------------- /linter_golint.lua: -------------------------------------------------------------------------------- 1 | local config = require "core.config" 2 | local linter = require "plugins.linter" 3 | 4 | config.golint_args = {} 5 | 6 | linter.add_language { 7 | file_patterns = {"%.go$"}, 8 | warning_pattern = "[^:]:(%d+):(%d+):%s?([^\n]*)", 9 | command = "golint $ARGS $FILENAME 2>&1", 10 | args = config.golint_args, 11 | expected_exitcodes = {0} 12 | } 13 | -------------------------------------------------------------------------------- /linter_jshint.lua: -------------------------------------------------------------------------------- 1 | -- this is for the jshint linter 2 | 3 | local config = require "core.config" 4 | local linter = require "plugins.linter" 5 | 6 | -- if you want to specify any CLI arguments 7 | config.jshint_args = {} 8 | 9 | linter.add_language { 10 | file_patterns = {"%.js$"}, 11 | warning_pattern = "[^:]: line (%d+), col (%d+), ([^\n]+)", 12 | command = "jshint $ARGS $FILENAME", 13 | args = config.jshint_args, 14 | expected_exitcodes = {0, 1, 2} 15 | } 16 | -------------------------------------------------------------------------------- /linter_luacheck.lua: -------------------------------------------------------------------------------- 1 | local config = require "core.config" 2 | local linter = require "plugins.linter" 3 | 4 | config.luacheck_args = {} 5 | 6 | linter.add_language { 7 | file_patterns = {"%.lua$"}, 8 | warning_pattern = "[^:]:(%d+):(%d+):[%s]?([^\n]+)", 9 | command = "luacheck $FILENAME --formatter=plain $ARGS", 10 | args = config.luacheck_args, 11 | expected_exitcodes = {0, 1} 12 | } 13 | -------------------------------------------------------------------------------- /linter_nim.lua: -------------------------------------------------------------------------------- 1 | local linter = require "plugins.linter" 2 | 3 | local pattern 4 | if PLATFORM == "Windows" then 5 | pattern = function(text, filename) 6 | local line, col, warn 7 | for line_text in text:gmatch("[^\n]+") do 8 | local has_path = line_text:match("[A-Z]:\\") 9 | local has_filename = line_text:match(linter.escape_to_pattern(filename)) 10 | if has_path then 11 | if warn then coroutine.yield(line, col, warn) end 12 | if has_filename then 13 | line, col, warn = line_text:match("%((%d+), (%d+)%)([^\n]+)") 14 | else 15 | warn = nil -- New warning found but about another file 16 | end 17 | else 18 | if warn then warn = warn.."\n"..line_text end 19 | end 20 | end 21 | -- When we reach the end of the lines we didn't report the last warning 22 | if warn then coroutine.yield(line, col, warn) end 23 | end 24 | else 25 | pattern = [[$FILENAME%((%d+), (%d+)%)([^\n]+[^/]*)]] 26 | end 27 | 28 | linter.add_language { 29 | file_patterns = {"%.nim$", "%.nims$"}, 30 | warning_pattern = pattern, 31 | command = "nim --listfullpaths --stdout check $FILENAME", 32 | deduplicate = true, 33 | expected_exitcodes = {0, 1} 34 | } 35 | -------------------------------------------------------------------------------- /linter_php.lua: -------------------------------------------------------------------------------- 1 | local config = require "core.config" 2 | local linter = require "plugins.linter" 3 | 4 | config.phplint_args = {} 5 | 6 | linter.add_language { 7 | file_patterns = {"%.php$"}, 8 | warning_pattern = "[%a ]+:%s*(.*)%s+on%sline%s+(%d+)", 9 | warning_pattern_order = {line=2, col=nil, message=1}, 10 | command = "php -l $ARGS $FILENAME 2>/dev/null", 11 | args = config.phplint_args, 12 | expected_exitcodes = {0} 13 | } 14 | -------------------------------------------------------------------------------- /linter_pylint.lua: -------------------------------------------------------------------------------- 1 | local config = require "core.config" 2 | local linter = require "plugins.linter" 3 | 4 | config.pylint_args = {} 5 | 6 | -- 32 for usage error, lower than that for warnings or success 7 | local expected_exitcodes = {} 8 | for i=0,31 do 9 | table.insert(expected_exitcodes, i) 10 | end 11 | 12 | linter.add_language { 13 | file_patterns = {"%.py$"}, 14 | warning_pattern = "[^%d]+(%d+)[^%d]+(%d+):%s([^\n]*)", -- Default pylint format 15 | column_starts_at_zero = true, 16 | command = "pylint --score=n $ARGS $FILENAME", -- Disabled score output 17 | args = config.pylint_args, 18 | expected_exitcodes = expected_exitcodes 19 | } 20 | -------------------------------------------------------------------------------- /linter_revive: -------------------------------------------------------------------------------- 1 | local config = require "core.config" 2 | local linter = require "plugins.linter" 3 | 4 | config.revive_args = {} 5 | 6 | linter.add_language { 7 | file_patterns = {"%.go$"}, 8 | warning_pattern = "[^:]:(%d+):(%d+):%s?([^\n]*)", 9 | command = "revive $ARGS $FILENAME 2>&1", 10 | args = config.revive_args, 11 | expected_exitcodes = {0} 12 | } 13 | -------------------------------------------------------------------------------- /linter_selene.lua: -------------------------------------------------------------------------------- 1 | local config = require "core.config" 2 | local linter = require "plugins.linter" 3 | 4 | config.selene_args = {} 5 | 6 | linter.add_language { 7 | file_patterns = {"%.lua$", "%.luau$"}, 8 | warning_pattern = ".-:(%d+):(%d+): .-:([^\n]+)", 9 | command = "selene $FILENAME --display-style quiet $ARGS", 10 | args = config.selene_args, 11 | expected_exitcodes = {1} 12 | } 13 | -------------------------------------------------------------------------------- /linter_shellcheck.lua: -------------------------------------------------------------------------------- 1 | local config = require "core.config" 2 | local linter = require "plugins.linter" 3 | 4 | config.shellcheck_args = {} 5 | 6 | linter.add_language { 7 | file_patterns = {"%.sh$"}, 8 | warning_pattern = "[^:]:(%d+):(%d+):%s?([^\n]*)", 9 | command = "shellcheck -f gcc $ARGS $FILENAME 2>&1", 10 | args = config.shellcheck_args, 11 | expected_exitcodes = {0, 1} 12 | } 13 | -------------------------------------------------------------------------------- /linter_standard.lua: -------------------------------------------------------------------------------- 1 | -- this is for standardjs linting 2 | 3 | local config = require "core.config" 4 | local linter = require "plugins.linter" 5 | 6 | -- add auto-fixing by adding '--fix' to your options, add '--verbose' to get the offending eslint rule 7 | config.standard_args = {} 8 | 9 | linter.add_language { 10 | file_patterns = {"%.js$"}, 11 | warning_pattern = "[^:]:(%d+):(%d+): ([^\n]+)", 12 | command = "standard $ARGS $FILENAME", 13 | args = config.standard_args, 14 | expected_exitcodes = {0, 1} 15 | } 16 | -------------------------------------------------------------------------------- /linter_teal.lua: -------------------------------------------------------------------------------- 1 | local config = require "core.config" 2 | local linter = require "plugins.linter" 3 | 4 | config.tlcheck_args = {} 5 | 6 | linter.add_language { 7 | file_patterns = {"%.tl$"}, 8 | warning_pattern = "[^:]:(%d+):(%d+):[%s]?([^\n]+)", 9 | command = "tl check $FILENAME $ARGS", 10 | args = config.tlcheck_args, 11 | expected_exitcodes = {0} 12 | } 13 | -------------------------------------------------------------------------------- /linter_zig.lua: -------------------------------------------------------------------------------- 1 | -- mod-version:1 -- lite-xl 1.16 2 | local config = require "core.config" 3 | local linter = require "plugins.linter" 4 | 5 | config.zigcheck_args = {} 6 | 7 | linter.add_language { 8 | file_patterns = {"%.zig$"}, 9 | warning_pattern = "[^%s:]:(%d+):(%d+):[%s]?([^\n]+)", 10 | command = "zig ast-check $FILENAME $ARGS", 11 | deduplicate = true, 12 | args = config.zigcheck_args, 13 | expected_exitcodes = {0, 1} 14 | } 15 | --------------------------------------------------------------------------------