github github
neovim-0.5 plugin
star 210
alert-circle 3
open issues
users 4
git-branch 36



2 days ago


  use 'mfussenegger/nvim-lint'


require "paq" { 


An asynchronous linter plugin for Neovim (>= 0.5) complementary to the built-in Language Server Protocol support.

It uses the same API to report diagnostics as the language server client built-in to neovim would do. Any customizations you did for vim.lsp.diagnostic apply for this plugin as well.

Motivation & Goals

With ale we already got an asynchronous linter, why write yet another one?

Because ale is a full blown linter including a language server client with its own commands and functions.

nvim-lint is for cases where you use the language server protocol client built into neovim for 90% of the cases, but you want something to fill the remaining gaps for languages where there is no good language server implementation or where the diagnostics reporting of the language server is inadequate and a better standalone linter exists.


  • Requires Neovim >= 0.5
  • 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'


Configure the linters you want to run per filetype. For example:

require('lint').linters_by_ft = {
  markdown = {'vale',}

Then setup a autocmd to trigger linting. For example:

au BufWritePost <buffer> lua require('lint').try_lint()

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.

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
checkstyle checkstyle
chktex chktex
clang-tidy clangtidy
clazy clazy
clj-kondo clj-kondo
codespell codespell
cppcheck cppcheck
cspell cspell
eslint eslint
fennel fennel
Flake8 flake8
flawfinder flawfinder
Golangci-lint golangcilint
hadolint hadolint
hlint hlint
HTML Tidy tidy
Inko inko
jshint jshint
Languagetool languagetool
luacheck luacheck
markdownlint markdownlint
mlint mlint
Mypy mypy
nix nix
pycodestyle pycodestyle
pydocstyle pydocstyle
Pylint pylint
Revive revive
rflint rflint
robocop robocop
Ruby ruby
Selene selene
ShellCheck shellcheck
StandardRB standardrb
statix check statix
stylelint stylelint
Vale vale
vint vint

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.
  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 the language server protocol.

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:


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

The function takes a single argument which is the errorformat.


parser = require('lint.parser').from_pattern(pattern, groups, severity_map, defaults)

The function allows to parse the linter's output using a lua regex pattern.

  • pattern: The regex pattern applied on each line of the output
  • groups: The groups specified by the pattern
groups = {"line", "message", "start_col", ["end_col"], ["code"], ["code_desc"], ["file"], ["severity"]}
  • severity: A mapping from severity codes to diagnostic codes
default_severity = {
['error'] = vim.lsp.protocol.DiagnosticSeverity.Error,
['warning'] = vim.lsp.protocol.DiagnosticSeverity.Warning,
['information'] = vim.lsp.protocol.DiagnosticSeverity.Information,
['hint'] = vim.lsp.protocol.DiagnosticSeverity.Hint,
  • defaults: The defaults diagnostic values
defaults = {["source"] = "mylint-name"}
export interface Diagnostic {
      * The range at which the message applies.
    range: Range;

      * The diagnostic's severity. Can be omitted. If omitted it is up to the
      * client to interpret diagnostics as error, warning, info or hint.
    severity?: DiagnosticSeverity;

      * The diagnostic's code, which might appear in the user interface.
    code?: integer | string;

      * An optional property to describe the error code.
      * @since 3.16.0
    codeDescription?: CodeDescription;

      * A human-readable string describing the source of this
      * diagnostic, e.g. 'typescript' or 'super lint'.
    source?: string;

      * The diagnostic's message.
    message: string;

      * Additional metadata about the diagnostic.
      * @since 3.15.0
    tags?: DiagnosticTag[];

      * An array of related diagnostic information, e.g. when symbol-names within
      * a scope collide all definitions can be marked via this property.
    relatedInformation?: DiagnosticRelatedInformation[];

      * A data entry field that is preserved between a
      * `textDocument/publishDiagnostics` notification and
      * `textDocument/codeAction` request.
      * @since 3.16.0
    data?: unknown;


Development ☢️

Run tests

Running tests requires plenary.nvim to be checked out in the parent directory of this repository. 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'}"