nvim-lint
An asynchronous linter plugin for Neovim (>= 0.6.0) complementary to the built-in Language Server Protocol support.
Motivation & Goals
With ale we already got an asynchronous linter, why write yet another one?
Because ale reports diagnostics with its own home grown solution and even includes its own language server client.
nvim-lint
instead uses the vim.diagnostic
module to present diagnostics in
the same way the language client built into neovim does.
nvim-lint
is meant to fill the gaps for languages where either no language
server exists, or where standalone linters provide better results than the
available language server do.
Installation
- Requires Neovim >= 0.6.0
nvim-lint
is a plugin. Install it like any other Neovim plugin.- If using vim-plug:
Plug 'mfussenegger/nvim-lint'
- If using packer.nvim:
use 'mfussenegger/nvim-lint'
- If using vim-plug:
Usage
Configure the linters you want to run per file type. For example:
require('lint').linters_by_ft = {
markdown = {'vale',}
}
Then setup a autocmd to trigger linting. For example:
au BufWritePost lua require('lint').try_lint()
or with Lua autocmds (requires 0.7):
vim.api.nvim_create_autocmd({ "BufWritePost" }, {
callback = function()
require("lint").try_lint()
end,
})
Some linters require a file to be saved to disk, others support linting stdin
input. For such linters you could also define a more aggressive autocmd, for
example on the InsertLeave
or TextChanged
events.
If you want to customize how the diagnostics are displayed, read :help vim.diagnostic.config
.
Available Linters
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 |
ansible-lint | ansible_lint |
bandit | bandit |
cfn-lint | cfn_lint |
cfn_nag | cfn_nag |
checkstyle | checkstyle |
chktex | chktex |
clang-tidy | clangtidy |
clazy | clazy |
clj-kondo | clj-kondo |
cmakelint | cmakelint |
codespell | codespell |
cppcheck | cppcheck |
cpplint | cpplint |
credo | credo |
cspell | cspell |
curlylint | curlylint |
ESLint | eslint |
eslint_d | eslint_d |
fennel | fennel |
Flake8 | flake8 |
flawfinder | flawfinder |
Golangci-lint | golangcilint |
glslc | glslc |
DirectX Shader Compiler | dxc |
hadolint | hadolint |
hlint | hlint |
HTML Tidy | tidy |
Inko | inko |
janet | janet |
jshint | jshint |
jsonlint | jsonlint |
ktlint | ktlint |
lacheck | lacheck |
Languagetool | languagetool |
luacheck | luacheck |
markdownlint | markdownlint |
mlint | mlint |
Mypy | mypy |
Nix | nix |
npm-groovy-lint | npm-groovy-lint |
oelint-adv | oelint-adv |
phpcs | phpcs |
proselint | proselint |
psalm | psalm |
pycodestyle | pycodestyle |
pydocstyle | pydocstyle |
Pylint | pylint |
Revive | revive |
rflint | rflint |
robocop | robocop |
rstcheck | rstcheck |
rstlint | rstlint |
Ruby | ruby |
RuboCop | rubocop |
Ruff | ruff |
Selene | selene |
ShellCheck | shellcheck |
StandardRB | standardrb |
statix check | statix |
stylelint | stylelint |
Nagelfar | nagelfar |
Vale | vale |
vint | vint |
vulture | vulture |
yamllint | yamllint |
Custom Linters
You can register custom linters by adding them to the linters
table, but
please consider contributing a linter if it is missing.
require('lint').linters.your_linter_name = {
cmd = 'linter_cmd',
stdin = true, -- or false if it doesn't support content input via stdin. In that case the filename is automatically added to the arguments.
append_fname = true, -- Automatically append the file name to `args` if `stdin = false` (default: true)
args = {}, -- list of arguments. Can contain functions with zero arguments that will be evaluated once the linter is used.
stream = nil, -- ('stdout' | 'stderr' | 'both') configure the stream to which the linter outputs the linting result.
ignore_exitcode = false, -- set this to true if the linter exits with a code != 0 and that's considered normal.
env = nil, -- custom environment table to use with the external process. Note that this replaces the *entire* environment, it is not additive.
parser = your_parse_function
}
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 :help diagnostic-structure
.
You can override the environment that the linting process runs in by setting
the env
key, e.g.
env = { ["FOO"] = "bar" }
Note that this completely overrides the environment, it does not add new
environment variables. The one exception is that the PATH
variable will be
preserved if it is not explicitly set.
You can generate a parse function from a Lua pattern or from an errorformat
using the function in the lint.parser
module:
from_errorformat
parser = require('lint.parser').from_errorformat(errorformat)
The function takes a single argument which is the errorformat
.
from_pattern
parser = require('lint.parser').from_pattern(pattern, groups, severity_map, defaults, opts)
The function allows to parse the linter's output using a Lua regular expression pattern.
- pattern: The regular expression pattern applied on each line of the output
- groups: The groups specified by the pattern
Available groups:
lnum
end_lnum
col
end_col
message
file
severity
code
The order of the groups must match the order of the captures within the pattern. An example:
local pattern = '[^:]+:(%d+):(%d+):(%w+):(.+)'
local groups = { 'lnum', 'col', 'code', 'message' }
- severity: A mapping from severity codes to diagnostic codes
default_severity = {
['error'] = vim.diagnostic.severity.ERROR,
['warning'] = vim.diagnostic.severity.WARN,
['information'] = vim.diagnostic.severity.INFO,
['hint'] = vim.diagnostic.severity.HINT,
}
- defaults: The defaults diagnostic values
defaults = {["source"] = "mylint-name"}
-
opts: Additional options
end_col_offset
: offset added toend_col
. Defaults to-1
, assuming that the end-column position is exclusive.
Customize built-in linter parameters
You can import a linter and modify its properties. An example:
local phpcs = require('lint').linters.phpcs
phpcs.args = {
'-q',
-- <- Add a new parameter here
'--report=json',
'-'
}
Alternatives
Development ☢️
Run tests
Running tests requires plenary.nvim to be checked out in the parent directory of this repository, as well as python
resolvable via PATH.
You can then run:
nvim --headless --noplugin -u tests/minimal.vim -c "PlenaryBustedDirectory tests/ {minimal_init = 'tests/minimal.vim'}"
Or if you want to run a single test file:
nvim --headless --noplugin -u tests/minimal.vim -c "PlenaryBustedDirectory tests/vale_spec.lua {minimal_init = 'tests/minimal.vim'}"