configure formatters and linters

This commit is contained in:
Ray Elliott 2025-12-07 22:33:00 +00:00
commit b045d7c627
5 changed files with 269 additions and 7 deletions

View file

@ -0,0 +1,24 @@
-- Mason tool installer for formatters and linters
-- Note: phpcs/phpcbf already installed globally, not included here
return {
"WhoIsSethDaniel/mason-tool-installer.nvim",
dependencies = { "williamboman/mason.nvim" },
config = function()
require("mason-tool-installer").setup({
ensure_installed = {
-- Formatters
"prettier", -- JS, TS, CSS, JSON, Markdown, HTML
"stylua", -- Lua
-- Note: phpcbf already installed globally
-- Linters
"eslint_d", -- JS, TS (daemon version for speed)
"markdownlint", -- Markdown
-- Note: phpcs already installed globally
},
auto_update = false,
run_on_start = true,
})
end,
}

118
lua/plugins/none-ls.lua Normal file
View file

@ -0,0 +1,118 @@
-- none-ls.nvim: Formatting only (linting moved to nvim-lint)
-- Philosophy: Formatters are authoritative. Project-local executables preferred.
-- Note: none-ls removed most linters from builtins, so we use nvim-lint for diagnostics
return {
"nvimtools/none-ls.nvim",
dependencies = { "nvim-lua/plenary.nvim" },
event = { "BufReadPre", "BufNewFile" },
config = function()
local null_ls = require("null-ls")
local augroup = vim.api.nvim_create_augroup("LspFormatting", {})
-- Helper: Find project-local executable, fallback to global
-- Searches node_modules/.bin/, vendor/bin/, and Mason bin first
local function find_executable(names)
local cwd = vim.fn.getcwd()
local mason_bin = vim.fn.stdpath("data") .. "/mason/bin/"
-- Try project-local paths first, then Mason, then global
local search_paths = {
cwd .. "/node_modules/.bin/",
cwd .. "/vendor/bin/",
mason_bin,
}
for _, name in ipairs(names) do
for _, path in ipairs(search_paths) do
local full_path = path .. name
if vim.fn.executable(full_path) == 1 then
return full_path
end
end
-- Fallback to system PATH
if vim.fn.executable(name) == 1 then
return name
end
end
return nil
end
-- Formatters
local formatting = null_ls.builtins.formatting
-- Note: Diagnostics (linters) moved to nvim-lint plugin
null_ls.setup({
sources = {
-- Prettier (JS, TS, CSS, SCSS, JSON, Markdown, HTML)
formatting.prettier.with({
command = find_executable({ "prettier" }),
prefer_local = "node_modules/.bin",
}),
-- PHP CodeSniffer (phpcbf) - WordPress standards
formatting.phpcbf.with({
command = find_executable({ "phpcbf" }),
prefer_local = "vendor/bin",
-- Respects phpcs.xml in project root or uses WordPress standard
extra_args = function()
local phpcs_xml = vim.fn.findfile("phpcs.xml", ".;")
if phpcs_xml == "" then
phpcs_xml = vim.fn.findfile("phpcs.xml.dist", ".;")
end
if phpcs_xml == "" then
return { "--standard=WordPress" }
end
return {}
end,
}),
-- stylua (Lua)
formatting.stylua.with({
command = find_executable({ "stylua" }),
}),
},
-- Format on save
on_attach = function(client, bufnr)
if client.supports_method("textDocument/formatting") then
vim.api.nvim_clear_autocmds({ group = augroup, buffer = bufnr })
vim.api.nvim_create_autocmd("BufWritePre", {
group = augroup,
buffer = bufnr,
callback = function()
-- Only format if format-on-save is enabled (global flag)
if vim.g.format_on_save ~= false then
vim.lsp.buf.format({ bufnr = bufnr })
end
end,
})
end
end,
})
-- Format-on-save is enabled by default
vim.g.format_on_save = true
-- Keymaps
-- Toggle format-on-save
vim.keymap.set("n", "<leader>lt", function()
vim.g.format_on_save = not vim.g.format_on_save
local status = vim.g.format_on_save and "enabled" or "disabled"
vim.notify("Format on save " .. status, vim.log.levels.INFO)
end, { desc = "Toggle format on save", silent = true, noremap = true })
-- Manual format (buffer)
vim.keymap.set("n", "<leader>lf", function()
vim.lsp.buf.format({ async = false })
end, { desc = "Format buffer", silent = true, noremap = true })
-- Manual format (visual range)
vim.keymap.set("v", "<leader>lf", function()
vim.lsp.buf.format({ async = false })
end, { desc = "Format selection", silent = true, noremap = true })
end,
}

80
lua/plugins/nvim-lint.lua Normal file
View file

@ -0,0 +1,80 @@
-- nvim-lint: Linting support (replaces none-ls diagnostics)
-- none-ls removed ESLint and many linters, so we use nvim-lint instead
return {
"mfussenegger/nvim-lint",
event = { "BufReadPre", "BufNewFile" },
config = function()
local lint = require("lint")
-- Configure linters per filetype
lint.linters_by_ft = {
javascript = { "eslint_d" },
javascriptreact = { "eslint_d" },
typescript = { "eslint_d" },
typescriptreact = { "eslint_d" },
markdown = { "markdownlint" },
php = { "phpcs" },
}
-- Helper: Find project-local executable, fallback to global
local function find_executable(names)
local cwd = vim.fn.getcwd()
local mason_bin = vim.fn.stdpath("data") .. "/mason/bin/"
local search_paths = {
cwd .. "/node_modules/.bin/",
cwd .. "/vendor/bin/",
mason_bin,
}
for _, name in ipairs(names) do
for _, path in ipairs(search_paths) do
local full_path = path .. name
if vim.fn.executable(full_path) == 1 then
return full_path
end
end
if vim.fn.executable(name) == 1 then
return name
end
end
return nil
end
-- Configure phpcs for WordPress standards
lint.linters.phpcs.cmd = find_executable({ "phpcs" }) or "phpcs"
lint.linters.phpcs.args = {
"-q",
"--report=json",
function()
local phpcs_xml = vim.fn.findfile("phpcs.xml", ".;")
if phpcs_xml == "" then
phpcs_xml = vim.fn.findfile("phpcs.xml.dist", ".;")
end
if phpcs_xml == "" then
return "--standard=WordPress"
end
return nil
end,
"-", -- stdin
}
-- Configure eslint_d to use project-local first
lint.linters.eslint_d.cmd = find_executable({ "eslint_d", "eslint" }) or "eslint_d"
-- Configure markdownlint
lint.linters.markdownlint.cmd = find_executable({ "markdownlint" }) or "markdownlint"
-- Auto-lint on these events
local lint_augroup = vim.api.nvim_create_augroup("lint", { clear = true })
vim.api.nvim_create_autocmd({ "BufEnter", "BufWritePost", "InsertLeave" }, {
group = lint_augroup,
callback = function()
lint.try_lint()
end,
})
end,
}