Skip to main content

Diagnostics and Linting

Neovim's diagnostics system displays errors, warnings, hints, and info from any source — including LSP servers, linters, and formatters. none-ls (formerly null-ls) bridges non-LSP tools into Neovim's LSP ecosystem.

Core Idea

LSP servers provide diagnostics for many languages. For linters that don't have LSP servers (ESLint, stylelint, phpcs, flake8), none-ls or nvim-lint wraps them as virtual LSP diagnostics.

Diagnostics Configuration

Configure how diagnostics appear in lsp/config.lua:

vim.diagnostic.config({
-- Inline virtual text
virtual_text = {
prefix = "●",
source = "if_many", -- show source name only if multiple clients
spacing = 4,
},

-- Floating window
float = {
border = "rounded",
source = "always",
header = "",
prefix = "",
},

-- Signs in the sign column
signs = {
severity = { min = vim.diagnostic.severity.HINT },
},

underline = true,
update_in_insert = false, -- don't show while typing
severity_sort = true, -- show errors before warnings
})

Diagnostic Commands and Navigation

:lua vim.diagnostic.goto_next()   " next diagnostic
:lua vim.diagnostic.goto_prev() " prev diagnostic
:lua vim.diagnostic.open_float() " show in float window

" Keymaps (configured in on_attach):
]dnext diagnostic
[dprev diagnostic
<leader>dlopen float with details
<leader>dL → Telescope diagnostics list

none-ls (null-ls) for Linters and Formatters

lua/plugins/lsp.lua (add to return table)
{
"nvimtools/none-ls.nvim",
dependencies = { "nvim-lua/plenary.nvim" },
event = { "BufReadPre", "BufNewFile" },
config = function()
local null_ls = require("null-ls")
null_ls.setup({
sources = {
-- Formatters
null_ls.builtins.formatting.prettier.with({
filetypes = { "javascript", "typescript", "css", "html", "json", "markdown" },
}),
null_ls.builtins.formatting.black, -- Python
null_ls.builtins.formatting.isort, -- Python imports
null_ls.builtins.formatting.stylua, -- Lua
null_ls.builtins.formatting.phpcsfixer, -- PHP

-- Linters
null_ls.builtins.diagnostics.eslint_d, -- JS/TS
null_ls.builtins.diagnostics.flake8, -- Python
null_ls.builtins.diagnostics.phpcs, -- PHP
null_ls.builtins.diagnostics.shellcheck, -- Bash

-- Code actions
null_ls.builtins.code_actions.eslint_d,
},
on_attach = function(client, bufnr)
if client.supports_method("textDocument/formatting") then
vim.api.nvim_create_autocmd("BufWritePre", {
buffer = bufnr,
callback = function()
vim.lsp.buf.format({
filter = function(c) return c.name == "null-ls" end,
bufnr = bufnr,
async = false,
})
end,
})
end
end,
})
end,
},

nvim-lint (Alternative to none-ls)

A simpler alternative focused only on linting:

{
"mfussenegger/nvim-lint",
event = { "BufReadPost", "BufWritePost" },
config = function()
local lint = require("lint")
lint.linters_by_ft = {
javascript = { "eslint_d" },
typescript = { "eslint_d" },
python = { "flake8" },
php = { "phpcs" },
bash = { "shellcheck" },
markdown = { "markdownlint" },
}
vim.api.nvim_create_autocmd({ "BufWritePost", "BufReadPost", "InsertLeave" }, {
callback = function()
lint.try_lint()
end,
})
end,
},

Diagnostic Signs Configuration

local signs = {
Error = " ", -- nf-fa-times_circle
Warn = " ", -- nf-fa-exclamation_triangle
Hint = "󰠠 ", -- nf-md-lightbulb
Info = " ", -- nf-fa-info_circle
}
for type, icon in pairs(signs) do
local hl = "DiagnosticSign" .. type
vim.fn.sign_define(hl, { text = icon, texthl = hl, numhl = "" })
end
Nerd Fonts Required

The diagnostic icons above require a Nerd Font in your terminal. Install one from nerdfonts.com or substitute plain ASCII: E, W, H, I.

What's Next