Skip to main content

Overview

nvim-lint provides asynchronous linting with support for multiple linters per filetype. Unlike automatic formatting, linting is triggered manually or on specific events. Configuration file: lua/plugins/linting.lua:7

Installation

All linters must be installed globally. See the installation documentation for platform-specific instructions.
Biome linting is handled via LSP (see lua/plugins/lsp/init.lua) and only activates when a biome.json file exists in the project.

Manual linting trigger

<leader>ll
keymap
Trigger linting - Manually runs linters for the current buffer’s filetypeShows notification with:
  • List of linters being run (if configured)
  • Warning if no linters are configured for the filetype
function()
  local lint = require("lint")
  lint.try_lint()
  local ft = vim.bo.filetype
  local linters = lint.linters_by_ft[ft] or {}
  if #linters > 0 then
    vim.notify("Linting triggered: " .. table.concat(linters, ", "), vim.log.levels.INFO)
  else
    vim.notify("No linters configured for filetype: " .. ft, vim.log.levels.WARN)
  end
end
Linting is manual by default. Press <leader>ll to run linters on the current file.

Linter configurations

Special configuration for golangci-lint with custom argument handling:
ignore_exitcode
boolean
default:"true"
Prevents treating non-zero exit codes as errors (some linters exit with 1 when issues are found).
Arguments:
  • run - Run linter
  • --output.json.path=stdout - Output JSON to stdout (v2 format)
  • --output.text.path= - Disable text output
  • --show-stats=false - Disable statistics
  • -c=<config> - Auto-detected config file path
  • Working directory - Set to current file’s directory
Config file detection:Searches for configuration files in this order:
  1. .golangci.yaml
  2. .golangci.yml
  3. .golangci.toml
  4. .golangci.json
Searches upward from the current file’s directory until $HOME.
function()
  local config_patterns = {
    ".golangci.yaml", ".golangci.yml",
    ".golangci.toml", ".golangci.json"
  }
  for _, pattern in ipairs(config_patterns) do
    local config_file = vim.fs.find(pattern, {
      upward = true,
      path = vim.fn.expand("%:p:h"),
      stop = vim.env.HOME,
    })[1]
    if config_file then
      return "-c=" .. config_file
    end
  end
  return nil
end

Linters by filetype

linters_by_ft.json
string[]
default:"[\"jsonlint\"]"
Uses jsonlint for JSON validation.
linters_by_ft.jsonc
string[]
default:"[\"jsonlint\"]"
Uses jsonlint for JSON with comments.

Linter features

eslint_d is a daemon version of ESLint that provides:
  • Significantly faster linting (daemon stays running)
  • Automatic project root detection
  • Respects .eslintrc, .eslintrc.json, eslint.config.js configurations
  • Works with all ESLint plugins
eslint_d automatically detects the project root by searching for ESLint config files.
shellcheck provides:
  • Syntax error detection
  • Semantic issues (quoting, deprecated syntax, etc.)
  • Best practices suggestions
  • POSIX compliance checking
  • Works with sh, bash, and zsh
hadolint checks for:
  • Dockerfile syntax errors
  • Best practices violations
  • Security issues
  • Performance problems
  • Deprecated instructions
golangci-lint runs multiple linters in parallel:
  • Supports 50+ different Go linters
  • Fast parallel execution
  • Configurable via .golangci.yaml
  • Caching for improved performance
  • Auto-detects Go module root
gopls has staticcheck disabled (see after/lsp/gopls.lua:49) to save CPU/memory. Use <leader>ll to run golangci-lint for comprehensive linting.
markdownlint-cli2 provides:
  • Markdown style consistency
  • Syntax validation
  • Configurable rules via .markdownlint.json
  • Automatic link checking
  • Heading hierarchy validation

Usage patterns

  1. Edit your file
  2. Press <leader>ll to run linters
  3. Review diagnostics in the diagnostic list
  4. Fix issues
  5. Repeat
Unlike formatting (which runs on save), linting is manual to avoid excessive CPU usage during active editing.
To enable automatic linting on events, add an autocmd:
vim.api.nvim_create_autocmd({ "BufWritePost", "BufEnter" }, {
  callback = function()
    require("lint").try_lint()
  end,
})
Automatic linting can increase CPU usage, especially with slow linters like golangci-lint.

Integration with LSP

The configuration uses both LSP diagnostics and external linters:LSP handles:
  • Real-time diagnostics during typing
  • Language-specific errors and warnings
  • Quick, incremental checks
External linters handle:
  • More comprehensive checks (e.g., golangci-lint runs 50+ linters)
  • Project-wide analysis
  • Style and best practice enforcement
  • Checks that are too slow for real-time feedback
For JavaScript/TypeScript, Biome LSP handles real-time linting when biome.json exists. eslint_d provides additional ESLint-specific checks.

Common commands

  • <leader>ll - Run linters for current filetype
  • :lua require('lint').try_lint() - Programmatically trigger linting
  • Use standard diagnostic navigation: [d, ]d, <leader>cd
Linter output is displayed as Neovim diagnostics, so all diagnostic commands and keymaps work with linter results.

Build docs developers (and LLMs) love