Skip to content

Commit

Permalink
Add a generic makeprg/errorformat compiler linter
Browse files Browse the repository at this point in the history
  • Loading branch information
mfussenegger committed Jun 3, 2021
1 parent 1dd697a commit 24926d7
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 22 deletions.
68 changes: 50 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@ implementation or where the diagnostics reporting of the language server is
inadequate and a better standalone linter exists.


## TODO

- [ ] Include more linters for languages with a lack of good language servers


## Installation

- Requires [Neovim HEAD/nightly][2]
Expand Down Expand Up @@ -59,22 +54,31 @@ example on the `InsertLeave` or `TextChanged` events.

## Available Linters

- [Languagetool][5]
- [Vale][8]
- [ShellCheck][10]
- [Mypy][11]
- [HTML Tidy][12]
- [Flake8][13]
- [Revive][14]
- [Pylint][15]
- [Golangci-lint][16]
- Ruby
- [Inko][17]
There is a generic linter called `compiler` that uses the `makeprg` and
`errorformat` options of the current buffer.

Other dedicated linters that are built-in are:


| Tool | Linter name |
| ---- | ----------- |
| Set via `makeprg` | `compiler` |
| [Languagetool][5] | `languagetool` |
| [Vale][8] | `vale` |
| [ShellCheck][10] | `shellcheck` |
| [Mypy][11] | `mypy` |
| [HTML Tidy][12] | `tidy` |
| [Flake8][13] | `mypy` |
| [Revive][14] | `revive` |
| [Pylint][15] | `pylint` |
| [Golangci-lint][16] | `golangci-lint` |
| Ruby | `ruby` |
| [Inko][17] | `inko` |


## Custom Linters

You could register custom linters by adding them to the `linters` table, but
You can register custom linters by adding them to the `linters` table, but
please consider contributing a linter if it is missing.


Expand All @@ -89,14 +93,42 @@ require('lint').linters.your_linter_name = {
}
```

Instead of declaring the linter as a table, you can also declare it as a
function which returns the linter table in case you want to dynamically
generate some of the properties.

`your_parse_function` can be a function which takes two arguments:

- `output`
- `bufnr`


The `output` is the output generated by the linter command.
The function must return a list of diagnostics as specified in the [language server protocol][9]:
The function must return a list of diagnostics as specified in the [language server protocol][9].

You can generate a parse function from a Lua pattern or from an `errorformat`
using the function in the `lint.parser` module:

### from_errorformat

```lua
parser = require('lint.parser').from_errorformat(errorformat)
```

The function takes a single argument which is the `errorformat`.


### from_pattern

```lua
parser = require('lint.parser').from_pattern(pattern, skeleton)
```

The function takes two arguments. The first is a lua pattern that must match
four groups in order: line number, offset, code and message.

The second argument is a skeleton used to create each diagnostic. This can be
used to provide default values - for example to set a `severity`.


<details>
Expand Down
24 changes: 20 additions & 4 deletions lua/lint.lua
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,21 @@ local function read_output(bufnr, parser, client_id)
end


function M.try_lint()
local linters = resolve_linters()
function M.try_lint(names)
if type(names) == "string" then
names = { names }
end
local linters
if names then
linters = vim.tbl_map(
function(name)
return assert(M.linters[name], 'Linter with name `' .. name .. '` not available')
end,
names
)
else
linters = resolve_linters()
end
for i, linter in pairs(linters) do
local ok, err = pcall(M.lint, linter, CLIENT_ID_OFFSET + i)
if not ok then
Expand Down Expand Up @@ -92,10 +105,13 @@ function M.lint(linter, client_id)
local pid_or_err
local args = {}
local bufnr = api.nvim_get_current_buf()
if type(linter) == "function" then
linter = linter()
end
if linter.args then
vim.list_extend(args, vim.tbl_map(eval_fn_or_id, linter.args))
end
if not linter.stdin then
if not linter.stdin and linter.append_fname ~= false then
table.insert(args, api.nvim_buf_get_name(bufnr))
end
local opts = {
Expand All @@ -110,7 +126,7 @@ function M.lint(linter, client_id)
stderr:close()
handle:close()
if code ~= 0 and not linter.ignore_exitcode then
print('Linter exited with code', code)
print('Linter command', linter.cmd, 'exited with code', code)
end
end)
assert(handle, 'Error running ' .. linter.cmd .. ': ' .. pid_or_err)
Expand Down
21 changes: 21 additions & 0 deletions lua/lint/linters/compiler.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
local api = vim.api


return function()
local errorformat = api.nvim_buf_get_option(0, 'errorformat')
local makeprg = api.nvim_buf_get_option(0, 'makeprg')
local bufname = api.nvim_buf_get_name(0)
local args = {
api.nvim_get_option('shellcmdflag'),
makeprg:gsub(' %%', ' ' .. bufname),
}
return {
cmd = vim.opt.shell:get(),
args = args,
stdin = false,
append_fname = false,
stream = 'stderr',
ignore_exitcode = true,
parser = require('lint.parser').from_errorformat(errorformat)
}
end
26 changes: 26 additions & 0 deletions lua/lint/parser.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,32 @@
local M = {}


-- Return a parse function that uses an errorformat to parse the output.
-- See `:help errorformat`
function M.from_errorformat(efm)
return function(output)
local lines = vim.split(output, '\n')
local qflist = vim.fn.getqflist({ efm = efm, lines = lines })
local result = {}
for _, item in pairs(qflist.items) do
if item.valid == 1 then
local col = item.col > 0 and item.col -1 or 0
local position = { line = item.lnum - 1, character = col }
table.insert(result, {
range = {
['start'] = position,
['end'] = position,
},
message = item.text,
severity = vim.lsp.protocol.DiagnosticSeverity.Error
})
end
end
return result
end
end


-- Return a parse function that uses a pattern to parse the output.
--
-- The first argument is a lua pattern.
Expand Down

0 comments on commit 24926d7

Please sign in to comment.