configure formatters and linters
This commit is contained in:
parent
c47afa2471
commit
b045d7c627
|
|
@ -260,13 +260,50 @@ Source of truth for the step-by-step rebuild. Keep this concise and up to date.
|
|||
## Phase 9 — Formatting & Linting
|
||||
|
||||
## Phase 9.1 — Confirm scope and priorities
|
||||
- [ ] Confirm scope and priorities for this phase
|
||||
- [x] Confirm scope and priorities for this phase
|
||||
- [x] Decision: Formatters - prettier (JS/TS/CSS/JSON/MD/HTML), phpcbf (PHP), stylua (Lua)
|
||||
- [x] Decision: Linters - eslint (JS/TS), phpcs (PHP), markdownlint (Markdown)
|
||||
- [x] Decision: Project-local executables preferred, fallback to global (node_modules/.bin/, vendor/bin/)
|
||||
- [x] Decision: phpcs/phpcbf already installed globally with WordPress coding standards - no installation needed
|
||||
- [x] Decision: Format-on-save enabled by default with toggle capability
|
||||
- [x] Decision: Manual format keymaps: `<leader>lf` (normal), `<leader>lf` (visual range)
|
||||
- [x] Decision: Respect project config files (.prettierrc, phpcs.xml, .eslintrc, etc.)
|
||||
- [x] Strategy: Helper function detects project-local executables first, then global
|
||||
- [x] WordPress: phpcs.xml in project root or --standard=WordPress flag for WordPress projects
|
||||
- [x] Philosophy: Formatters are authoritative source of truth; Neovim settings should match formatter rules per filetype
|
||||
- [x] Note: Phase 9.4 will align Neovim editor settings (tabstop, shiftwidth, expandtab) with formatter configurations
|
||||
|
||||
## Phase 9.2 — none-ls setup
|
||||
- [ ] Add `nvimtools/none-ls.nvim`
|
||||
- [ ] Configure formatters (to be determined in 9.1)
|
||||
- [ ] Configure linters (to be determined in 9.1)
|
||||
- [ ] Add format-on-save or manual format keymaps
|
||||
## Phase 9.2 — none-ls setup with project-aware executables
|
||||
- [x] Add `nvimtools/none-ls.nvim`
|
||||
- [x] Create helper function to detect project-local executables (node_modules/.bin/, vendor/bin/, Mason bin)
|
||||
- [x] Configure formatters:
|
||||
- [x] prettier (project-local first, then Mason, then global)
|
||||
- [x] phpcbf (project-local first, then global system install - already available)
|
||||
- [x] stylua (Mason installed)
|
||||
- [x] Add `mfussenegger/nvim-lint` for linting (none-ls removed most linters from builtins)
|
||||
- [x] Configure linters via nvim-lint:
|
||||
- [x] eslint_d (project-local first, then Mason, then global - daemon version for speed)
|
||||
- [x] phpcs (project-local first, then global system install - already available)
|
||||
- [x] markdownlint (Mason installed)
|
||||
- [x] Add format-on-save autocommand with toggle capability (`<leader>lt` to toggle)
|
||||
- [x] Add manual format keymaps: `<leader>lf` (buffer), `<leader>lf` (visual range)
|
||||
- [x] Ensure WordPress coding standards work via phpcs.xml or --standard flag
|
||||
- [x] Search order: project node_modules/.bin/ → project vendor/bin/ → Mason bin → system PATH
|
||||
- [x] Note: Linting runs on BufEnter, BufWritePost, InsertLeave events
|
||||
|
||||
## Phase 9.3 — Mason formatter/linter installation
|
||||
- [x] Add `WhoIsSethDaniel/mason-tool-installer.nvim` for automated installation
|
||||
- [x] Install via Mason: prettier, eslint_d, markdownlint, stylua
|
||||
- [x] Note: phpcs/phpcbf already installed globally, skip Mason installation
|
||||
- [x] Verify all tools available: Tools installed to ~/.local/share/nvim/mason/bin/
|
||||
|
||||
## Phase 9.4 — Align Neovim settings with formatter rules
|
||||
- [ ] Configure per-filetype settings to match formatter defaults
|
||||
- [ ] PHP: Match phpcs/WordPress standards (likely tabs, 4-width)
|
||||
- [ ] JS/TS/CSS/JSON: Match prettier defaults (likely 2 spaces)
|
||||
- [ ] Lua: Match stylua defaults (likely tabs or 4 spaces)
|
||||
- [ ] Use autocmds or after/ftplugin/ for filetype-specific tabstop, shiftwidth, expandtab
|
||||
- [ ] Goal: Manual editing feels natural and matches what formatter will produce on save
|
||||
|
||||
## Phase 10 — Migrate Kept Behaviours
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,12 @@
|
|||
"indent-blankline.nvim": { "branch": "master", "commit": "005b56001b2cb30bfa61b7986bc50657816ba4ba" },
|
||||
"lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
|
||||
"mason-lspconfig.nvim": { "branch": "main", "commit": "0b9bb925c000ae649ff7e7149c8cd00031f4b539" },
|
||||
"mason-tool-installer.nvim": { "branch": "main", "commit": "517ef5994ef9d6b738322664d5fdd948f0fdeb46" },
|
||||
"mason.nvim": { "branch": "main", "commit": "57e5a8addb8c71fb063ee4acda466c7cf6ad2800" },
|
||||
"none-ls.nvim": { "branch": "main", "commit": "5abf61927023ea83031753504adb19630ba80eef" },
|
||||
"nvim-autopairs": { "branch": "master", "commit": "7a2c97cccd60abc559344042fefb1d5a85b3e33b" },
|
||||
"nvim-cmp": { "branch": "main", "commit": "d97d85e01339f01b842e6ec1502f639b080cb0fc" },
|
||||
"nvim-lint": { "branch": "master", "commit": "ebe535956106c60405b02220246e135910f6853d" },
|
||||
"nvim-lspconfig": { "branch": "master", "commit": "9c923997123ff9071198ea3b594d4c1931fab169" },
|
||||
"nvim-surround": { "branch": "main", "commit": "fcfa7e02323d57bfacc3a141f8a74498e1522064" },
|
||||
"nvim-treesitter": { "branch": "master", "commit": "42fc28ba918343ebfd5565147a42a26580579482" },
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
Loading…
Reference in New Issue