Linting
Blak uses mfussenegger/nvim-lint to run standalone linters — anything that isn’t already an LSP. It fires on the events in lint.events (default: BufWritePost, BufReadPost, InsertLeave).
Spec: lua/blak/plugins/formatting.lua (formatting and linting share a file). The runner itself lives in lua/blak/core/linting.lua.
Defaults
lint = { events = { "BufWritePost", "BufReadPost", "InsertLeave" }, linters_by_ft = {},}No linters are configured in core. Linters arrive via language extras:
| Extra | Adds |
|---|---|
lang.python | ruff for python |
lang.go | golangcilint for go |
lang.markdown | markdownlint for markdown |
lang.bash | shellcheck for sh/bash |
lang.docker | hadolint for dockerfile |
lang.terraform | tflint for terraform |
lang.python-pro uses Ruff’s native LSP diagnostics and code actions instead
of adding a separate nvim-lint entry, so it avoids duplicate Ruff diagnostics.
The TypeScript extras do the same with ESLint: they enable the eslint
language server rather than running eslint_d through nvim-lint. See
Using eslint_d anyway if you want the standalone
daemon back.
Adding a linter
-- ~/.config/blak/lua/blak/user.luareturn { lint = { linters_by_ft = { sh = { "shellcheck" }, dockerfile = { "hadolint" }, }, }, mason = { ensure_installed = { "shellcheck", "hadolint" }, },}Or wrap it in an extra so it’s reversible.
Tuning when it runs
return { lint = { -- Only on write, not on every InsertLeave or BufReadPost events = { "BufWritePost" }, },}Customizing a linter
nvim-lint exposes each linter as require("lint").linters.<name>. Tweak in user.lua:
vim.schedule(function() require("lint").linters.shellcheck.args = { "--severity=warning", "--shell=bash", "-", }end)Using eslint_d anyway
The TypeScript extras rely on the eslint language server, so nothing runs
eslint_d by default. To add it back, list it for the filetypes you want and
install it through Mason:
return { extras = { enabled = { "lang.typescript" }, }, lint = { linters_by_ft = { javascript = { "eslint_d" }, javascriptreact = { "eslint_d" }, typescript = { "eslint_d" }, typescriptreact = { "eslint_d" }, }, }, mason = { ensure_installed = { "eslint_d" }, },}Expect ESLint diagnostics from both sources unless you also disable the eslint
language server.
Failure handling
Blak resolves each linter’s command before running it. A linter that isn’t
installed (shellcheck missing from $PATH, say) is skipped — no Error running shellcheck: ENOENT on every save, and no diagnostics from that linter.
When a linter does run but writes something Blak can’t parse — eslint_d
printing a config resolution error to stdout is the common case — the message is
reported once as a warning instead of being attached to line 1 of your buffer as
a fake diagnostic.
To see what’s actually wired up and what’s missing:
:checkhealth blakThe Linters section lists every configured linter and whether its binary was
found. Run :BlakToolsInstall after adding a Mason-installable linter, or
install it yourself.
Inspecting
:lua print(vim.inspect(require("lint").linters_by_ft)):lua require("lint").try_lint() " trigger now:lua require("lint").get_running() " what's running