Prettier&ESLint と Biome を競合させないための Neovim 設定
はじめに
TypeScript の開発環境として、長らく Prettier と ESLint の組み合わせが定番でしたが、最近では Biome という選択肢も登場しています。実際の開発では、既存プロジェクトでは PrettierとESLintの組み合わせを、新規プロジェクトでは Biome を使うといったケースも多いのではないでしょうか。
そうなると困るのが Neovim の設定です。両方のツールを有効にしていると、以下のような問題が発生します。
この記事では、プロジェクトごとに自動的にツールを切り替える Neovim 設定を紹介します。手動での切り替え操作は不要で、プロジェクトルートの設定ファイルを検知して自動で判断する仕組みです。
前提条件・環境
以下の環境を前提としています:
プラグインのインストールと基本的な設定は完了している前提で進めます。
各設定の詳細解説
起動するかどうかを動的に判定する
まずは none-ls 側の設定で Biome の設定ファイルが存在する場合に Prettier を無効化する仕組みを追加します。none-ls のオプションの一つである condition でnone-lsで提供されている root_has_file() を使い、プロジェクトルートに biome.json または biome.jsonc が存在するかチェックしています。これらのファイルが存在する場合、Prettier を無効にします。
local formatting = require("null-ls").builtins.formatting.prettier
formatting.with({
-- ... 他の設定 ...
-- Biome の設定ファイルがある場合は Prettier を無効化
condition = function(utils)
return not utils.root_has_file({ "biome.json", "biome.jsonc" })
end,
-- ... 他の設定 ...
})次に、ESLint も同様に Biome が存在する場合は無効化します。こちらはLSPの設定に乗せているので、root_dir を動的に設定することで Biome プロジェクトでは ESLint を起動しないようにしています。root_dir が nil を返すと、そのバッファでは LSP クライアントがアタッチされません。
---@type vim.lsp.Config
return {
root_dir = function(bufnr, cb)
local exclud_dir = vim.fs.root(bufnr, { "biome.json", "biome.jsonc" })
if exclud_dir then
cb(nil)
return
end
local root_dir = vim.fs.root(bufnr, {
".eslintrc",
".eslintrc.js",
".eslintrc.json",
"eslint.config.js",
-- ...
})
if root_dir then
cb(root_dir)
else
cb(nil)
end
end,
}これだけで目的は果たせるのですが、Biome が提供する自動修正を保存時に自動で実行する設定も加えておきます。
local function exec_code_action_sync(client, bufnr, action_kind)
local params = {
textDocument = vim.lsp.util.make_text_document_params(bufnr),
range = { start = { line = 0, character = 0 }, ["end"] = { line = vim.fn.line("$"), character = 0 } },
context = { diagnostics = {}, only = { action_kind } },
}
local result = vim.lsp.buf_request_sync(bufnr, "textDocument/codeAction", params, 5000)
if not result or vim.tbl_isempty(result) then
return
end
for _, res in pairs(result) do
if res.result then
for _, action in ipairs(res.result) do
if action.edit then
vim.lsp.util.apply_workspace_edit(action.edit, client.offset_encoding)
end
end
end
end
end
require("lspconfig").biome.setup({
on_attach = function(client, bufnr)
-- 保存時に Biome の全機能を実行
vim.api.nvim_create_autocmd("BufWritePre", {
buffer = bufnr,
callback = function()
if client.supports_method("textDocument/codeAction") then
-- lintルールの自動修正
exec_code_action_sync(client, bufnr, "source.fixAll")
-- インポート文の整理
exec_code_action_sync(client, bufnr, "source.organizeImports")
end
if client.supports_method("textDocument/formatting") then
-- コードフォーマット
vim.lsp.buf.format({
bufnr = bufnr,
async = false,
timeout_ms = 5000,
})
end
end,
})
end,
-- ... 他の設定 ...
})
この設定では保存時に以下の処理を順番に実行しています。
source.fixAll:リンティングエラーの自動修正source.organizeImports:インポート文の並び替えと未使用インポートの削除textDocument/formatting:コードフォーマット重要なのは async = false で同期実行している点です。これにより、他のフォーマッターが同時に動作することを防ぎます。
これはおまけですが、プロジェクトで使用しているパッケージマネージャーに応じて Biome の起動コマンドを切り替えることもしています。pnpm を使っているプロジェクトでは pnpm で起動し、それ以外では npx を使います。
-- pnpm を使っているかチェック
local pnpm_root = vim.fs.root(0, { "pnpm-lock.yaml", "pnpm-workspace.yaml" })
require("lspconfig").biome.setup({
-- pnpm プロジェクトなら pnpm 経由で、そうでなければ npx で実行
cmd = pnpm_root and { "pnpm", "biome", "lsp-proxy" }
or { "npx", "biome", "lsp-proxy" },
-- ... 他の設定 ...
})さいごに
Prettier/ESLint と Biome の両方を使い分ける必要がある場合は、こうした条件分岐を設定に組み込むことでスムーズなプロジェクト間の移動が可能になります。設定ファイルの検知による自動切り替えは、他のツール(例えば Deno と Node.js の切り替えなど)にも応用できる手法なので、覚えておくと便利です。