local null_ls = require("null-ls") -- phpstan: use the project's own phpstan.neon when there is one, otherwise fall -- back to the generic config in dotfiles. local phpstan_global_config = vim.fn.expand("~/repos/dotfiles/php/phpstan-global.neon") -- returns the config path to pass via -c, or nil when phpstan can discover a -- local config itself (that also picks up its `includes:`, e.g. a baseline) local function phpstan_config_arg(root) for _, name in ipairs({ "phpstan.neon", "phpstan.neon.dist" }) do if vim.uv.fs_stat(root .. "/" .. name) then return nil end end return phpstan_global_config end null_ls.setup({ sources = { null_ls.builtins.formatting.phpcbf.with({ command = "phpcbf", -- or full path if not in PATH extra_args = { "--standard=PSR12", "--standard=phpcs.xml" }, }), null_ls.builtins.diagnostics.phpstan.with({ -- prefer the project's own phpstan (extensions and baselines are -- version bound), fall back to the mason binary dynamic_command = function(params, done) local local_bin = params.root .. "/vendor/bin/phpstan" if vim.fn.executable(local_bin) == 1 then return done(local_bin) end return done("phpstan") end, args = function(params) local args = { "analyse", "--error-format", "json", "--no-progress" } local config = phpstan_config_arg(params.root) if config then vim.list_extend(args, { "-c", config }) end table.insert(args, "$FILENAME") return args end, -- analyse the file on disk, not a copy in /tmp: phpstan needs the -- project's autoloader and its configured paths/scanDirectories method = null_ls.methods.DIAGNOSTICS_ON_SAVE, to_temp_file = false, timeout = 60000, -- a cold run easily exceeds null-ls' 5s default condition = function(utils) return utils.root_has_file({ "phpstan.neon", "phpstan.neon.dist" }) or vim.uv.fs_stat(phpstan_global_config) ~= nil end, }), null_ls.builtins.diagnostics.phpcs.with({ command = "phpcs", -- or "vendor/bin/phpcs" if local args = { "--standard=PSR12", -- or "phpcs.xml", "phpcs.xml.dist", etc. "-s", -- show sniff codes "-q", -- suppress warnings "--report=emacs", "--stdin-path", "$FILENAME", "-" }, condition = function(utils) return utils.root_has_file({ "phpcs.xml", "phpcs.xml.dist" }) or true end, }), null_ls.builtins.formatting.phpcsfixer.with({ command = "php-cs-fixer", -- no extra_args needed if config file is correctly named and in root }), }, })