Configuring eslint to work with Neovim LSP
The way we’ll get this to work is by using a generic Language Server called
efm-langserver
, which is written in
Go.
These Language Servers are generic in that they were made to be powered by command-line tools and for any programming language.
Making eslint faster with eslint_d
To reduce latency when invoking eslint
, I’m gonna use
eslint_d
, which runs eslint
as a
daemon process.
Configuring eslint_d in efm-langserver
It’s possible to configure it with a YAML file, by following their README. I did this initially I found that it’s more powerful to configure it with Lua.
Here’s the configuration, which more explanation below.
local lspconfig = require"lspconfig"
local eslint = {
lintCommand = "eslint_d -f unix --stdin --stdin-filename ${INPUT}",
lintStdin = true,
lintFormats = {"%f:%l:%c: %m"},
lintIgnoreExitCode = true,
formatCommand = "eslint_d --fix-to-stdout --stdin --stdin-filename=${INPUT}",
formatStdin = true
}
lspconfig.tsserver.setup {
on_attach = function(client)
if client.config.flags then
client.config.flags.allow_incremental_sync = true
end
client.resolved_capabilities.document_formatting = false
set_lsp_config(client)
end
}
lspconfig.efm.setup {
on_attach = function(client)
client.resolved_capabilities.document_formatting = true
client.resolved_capabilities.goto_definition = false
set_lsp_config(client)
end,
root_dir = function()
if not eslint_config_exists() then
return nil
end
return vim.fn.getcwd()
end,
settings = {
languages = {
javascript = {eslint},
javascriptreact = {eslint},
["javascript.jsx"] = {eslint},
typescript = {eslint},
["typescript.tsx"] = {eslint},
typescriptreact = {eslint}
}
},
filetypes = {
"javascript",
"javascriptreact",
"javascript.jsx",
"typescript",
"typescript.tsx",
"typescriptreact"
},
}
I defined a table to configure efm-langserver
with eslint_d
by giving the
necessary commands for linting and formatting.
I customize the on_attach
function of both efm
and tsserver
so that only
one has documentFormatting
capability, otherwise they would conflict with each
other.
The root_dir
function was also customized so that eslint_d
is spawned just
for the current working directory and not for every directory it encounters a
.eslintrc
(or similar).
But I also want this to happen only if the directory has some sort of eslint
configuration. So I created a function to do this:
local function eslint_config_exists()
local eslintrc = vim.fn.glob(".eslintrc*", 0, 1)
if not vim.tbl_isempty(eslintrc) then
return true
end
if vim.fn.filereadable("package.json") then
if vim.fn.json_decode(vim.fn.readfile("package.json"))["eslintConfig"] then
return true
end
end
return false
end
Conclusion
This is kind like a poor replacement for the VS Code eslint extension, which
does a similar thing as eslint_d
. And it works ok, it’s pretty fast, much
faster than typescript-language-server
, so it’s definitely an improvement.
It would be nice if there was a way to shut down the server if eslint
is
broken for example, but I didn’t manage to do it just yet.