eglot
eglot copied to clipboard
vscode-eslint-language-server not working
Hello.
This is a 2-in-1 issue report, but since there have been open issue regarding the first one, I thought it's unnecessary to create a new one again.
- I'm trying to run the
typescript-language-server
(link) alongside thevscode-eslint-language-server
(link). There already are issues regarding this:
- #611
- #423
- #90
- more?
After reading through the issues, I was under the impression that this could be done using eglot-alternatives
, so I added this to my configuration:
(add-to-list
'eglot-server-programs
`((js-mode typescript-mode typescript-tsx-mode) .
,(eglot-alternatives
'(("/gnu/store/q2fz3ccwf6ax8wd9ln011i1vln0wh1h4-node-typescript-language-server-0.11.0/bin/typescript-language-server"
"--tsserver-path"
"/gnu/store/s4smmc11wizcv2lzlgn9a0q7sxa7yvg3-node-typescript-4.7.3/lib/"
"--stdio")
("/gnu/store/q050rzv1wqliqk7jvqfx872xa2b7cfxa-node-vscode-langservers-extracted-4.2.1/bin/vscode-eslint-language-server"
"--stdio")))))
But I think I may have misunderstood it. It doesn't establish connections to all language servers, but only the first one it can execute. Is it therefore not possible to run multiple language servers at once for a given mode? In the javascript eco system this is kind of needed as typescript-language-server
only reports syntax / type errors and so one whereas vscode-eslint-language-server
reports errors in regards to the local/global eslint configuration. We need both language servers at once.
- After that I tried using the
vscode-eslint-language-server
alone to test it. So I added
(add-to-list 'eglot-server-programs '(typescript-tsx-mode . ("vscode-eslint-language-server" "--stdio")))
to my config.
I have the eslint
binary itself installed globally. So I init a test project with create-react-app, then cd
into it and run eslint --init
. I answer the question and end up with basic .eslint.js
config file.
When loading the test project in emacs, I see two warnings in the Messages buffer:
[eglot] Connected! Server `EGLOT (ts-app/typescript-tsx-mode)' now managing `typescript-tsx-mode' buffers in project `ts-app'.
[eglot] (warning) Server tried to register unsupported capability `workspace/didChangeConfiguration'
[eglot] (warning) Server tried to register unsupported capability `workspace/didChangeWorkspaceFolders'
The connection seem to be established, but nothing is working as I expect it. If I try to execute eglot-code-actions
I get
jsonrpc-error: "request id=2 failed:", (jsonrpc-error-code . -32603), (jsonrpc-error-message . "Request textDocument/codeAction failed with message: Cannot read property 'workingDirectory' of null"), (jsonrpc-error-data)
Looking through eglot's event log, I see (full log below):
[stderr] (node:1262) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'workingDirectory' of null
[stderr] at /home/db/test/npm/lib/node_modules/vscode-langservers-extracted/lib/eslint-language-server/eslint.js:1089:60
This is the line it fails. So I'm not quite sure what to make of this as I'm not experienced with language servers. It seems like connection.workspace.getConfiguration
on line 117 is not returning a proper configuration.
Now is this an internal problem with the eslint language server or something that eglot should provide but doesn't?
I'd love to get some input on these two issues. Thanks in advance!
- Server used:
typescript-language-server
,vscode-eslint-language-server
- Emacs version:
29.0.50
- Operating system: GNU/Guix
- Eglot version:
1.8
- Eglot installation method: System package manager (https://guix.gnu.org/packages/emacs-eglot-1.8/)
- Using Doom: No
LSP transcript - M-x eglot-events-buffer (mandatory unless Emacs inoperable)
[client-request] (id:1) Thu Jun 16 15:35:41 2022:
(:jsonrpc "2.0" :id 1 :method "initialize" :params
(:processId 1008 :rootPath "/home/db/projects/ts-app/" :rootUri "file:///home/db/projects/ts-app" :initializationOptions #s(hash-table size 65 test eql rehash-size 1.5 rehash-threshold 0.8125 data
())
:capabilities
(:workspace
(:applyEdit t :executeCommand
(:dynamicRegistration :json-false)
:workspaceEdit
(:documentChanges :json-false)
:didChangeWatchedFiles
(:dynamicRegistration t)
:symbol
(:dynamicRegistration :json-false)
:configuration t)
:textDocument
(:synchronization
(:dynamicRegistration :json-false :willSave t :willSaveWaitUntil t :didSave t)
:completion
(:dynamicRegistration :json-false :completionItem
(:snippetSupport :json-false)
:contextSupport t)
:hover
(:dynamicRegistration :json-false :contentFormat
["markdown" "plaintext"])
:signatureHelp
(:dynamicRegistration :json-false :signatureInformation
(:parameterInformation
(:labelOffsetSupport t)
:activeParameterSupport t))
:references
(:dynamicRegistration :json-false)
:definition
(:dynamicRegistration :json-false :linkSupport t)
:declaration
(:dynamicRegistration :json-false :linkSupport t)
:implementation
(:dynamicRegistration :json-false :linkSupport t)
:typeDefinition
(:dynamicRegistration :json-false :linkSupport t)
:documentSymbol
(:dynamicRegistration :json-false :hierarchicalDocumentSymbolSupport t :symbolKind
(:valueSet
[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26]))
:documentHighlight
(:dynamicRegistration :json-false)
:codeAction
(:dynamicRegistration :json-false :codeActionLiteralSupport
(:codeActionKind
(:valueSet
["quickfix" "refactor" "refactor.extract" "refactor.inline" "refactor.rewrite" "source" "source.organizeImports"]))
:isPreferredSupport t)
:formatting
(:dynamicRegistration :json-false)
:rangeFormatting
(:dynamicRegistration :json-false)
:rename
(:dynamicRegistration :json-false)
:publishDiagnostics
(:relatedInformation :json-false :codeDescriptionSupport :json-false))
:experimental #s(hash-table size 65 test eql rehash-size 1.5 rehash-threshold 0.8125 data
()))))
[server-notification] Thu Jun 16 15:35:41 2022:
(:jsonrpc "2.0" :method "window/logMessage" :params
(:type 3 :message "ESLint server running in node v14.18.3"))
[server-reply] (id:1) Thu Jun 16 15:35:41 2022:
(:jsonrpc "2.0" :id 1 :result
(:capabilities
(:textDocumentSync
(:openClose t :change 2 :willSaveWaitUntil :json-false :save
(:includeText :json-false))
:workspace
(:workspaceFolders
(:supported t))
:codeActionProvider
(:codeActionKinds
["quickfix" "source.fixAll.eslint"])
:executeCommandProvider
(:commands
["eslint.applySingleFix" "eslint.applySuggestion" "eslint.applySameFixes" "eslint.applyAllFixes" "eslint.applyDisableLine" "eslint.applyDisableFile" "eslint.openRuleDoc"]))))
[client-notification] Thu Jun 16 15:35:41 2022:
(:jsonrpc "2.0" :method "initialized" :params #s(hash-table size 65 test eql rehash-size 1.5 rehash-threshold 0.8125 data
()))
[client-notification] Thu Jun 16 15:35:41 2022:
(:jsonrpc "2.0" :method "textDocument/didOpen" :params
(:textDocument
(:uri "file:///home/db/projects/ts-app/src/App.tsx" :version 0 :languageId "typescript-tsx" :text "import React from 'react';\nimport logo from './logo.svg';\nimport './App.css';\n\nfunction App() {\n return (\n <div className=\"App\">\n <header className=\"App-header\">\n <img src={logo} className=\"App-logo\" alt=\"logo\" />\n <p>\n Edit <code>src/App.tsx</code> and save to reload.\n </p>\n <a\n className=\"App-link\"\n href=\"https://reactjs.org\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Learn React\n </a>\n </header>\n </div>\n );\n}\n\nexport default App;\n")))
[client-notification] Thu Jun 16 15:35:41 2022:
(:jsonrpc "2.0" :method "workspace/didChangeConfiguration" :params
(:settings nil))
[server-request] (id:0) Thu Jun 16 15:35:41 2022:
(:jsonrpc "2.0" :id 0 :method "client/registerCapability" :params
(:registrations
[(:id "dcfa99a0-12bc-4d8d-b2da-14ed6d106182" :method "workspace/didChangeConfiguration" :registerOptions nil)]))
[client-reply] (id:0) Thu Jun 16 15:35:41 2022:
(:jsonrpc "2.0" :id 0 :result nil)
[server-request] (id:1) Thu Jun 16 15:35:41 2022:
(:jsonrpc "2.0" :id 1 :method "client/registerCapability" :params
(:registrations
[(:id "234a266a-0363-4598-ba68-392b41dd2801" :method "workspace/didChangeWorkspaceFolders" :registerOptions nil)]))
[client-reply] (id:1) Thu Jun 16 15:35:41 2022:
(:jsonrpc "2.0" :id 1 :result nil)
[server-request] (id:2) Thu Jun 16 15:35:41 2022:
(:jsonrpc "2.0" :id 2 :method "workspace/configuration" :params
(:items
[(:scopeUri "file:///home/db/projects/ts-app/src/App.tsx" :section "")]))
[client-reply] (id:2) Thu Jun 16 15:35:41 2022:
(:jsonrpc "2.0" :id 2 :result
[nil])
[server-request] (id:3) Thu Jun 16 15:35:41 2022:
(:jsonrpc "2.0" :id 3 :method "workspace/configuration" :params
(:items
[(:scopeUri "file:///home/db/projects/ts-app/src/App.tsx" :section "")]))
[client-reply] (id:3) Thu Jun 16 15:35:41 2022:
(:jsonrpc "2.0" :id 3 :result
[nil])
[stderr] (node:1262) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'workingDirectory' of null
[stderr] at /home/db/test/npm/lib/node_modules/vscode-langservers-extracted/lib/eslint-language-server/eslint.js:1089:60
[stderr] (Use `node --trace-warnings ...` to show where the warning was created)
[stderr] (node:1262) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
[stderr] (node:1262) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
[stderr] (node:1262) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'workingDirectory' of null
[stderr] at /home/db/test/npm/lib/node_modules/vscode-langservers-extracted/lib/eslint-language-server/eslint.js:1089:60
[stderr] (node:1262) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)
[stderr] (node:1262) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'workingDirectory' of null
[stderr] at /home/db/test/npm/lib/node_modules/vscode-langservers-extracted/lib/eslint-language-server/eslint.js:1089:60
[stderr] (node:1262) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 3)
Minimum Reproducible Example (mandatory)
- Install
vscode-eslint-language-server
:npm i -g vscode-langservers-extracted
- Make sure npm bin path is part of
PATH
- Test if lsp server is available with
which vscode-eslint-language-server
- Start emacs and evaluate
(add-to-list 'eglot-server-programs '(js-mode . ("vscode-eslint-language-server" "--stdio")))
- Setup test js/ts project, for example create-react-app:
npx create-react-app my-app
- Open
/path/to/my-app/src/App.js
in Emacs - Start eglot
-
vscode-eslint-language-server
connection should be established
Is it therefore not possible to run multiple language servers at once for a given mode?
That's correct. It's currently impossible in Eglot.
In the javascript eco system this is kind of needed as
For what it's worth, I used to program a lot of JS/TS until recently and I used a language server and a separate eslint Flymake backend. https://github.com/orzechowskid/flymake-eslint To manually configure other Flymake backends alongside Eglot's LSP-based backend, refer to the Flymake documentation and make sure you add flymake
to eglot-stay-out-of
As to the rest of your issue, it could be the server has a bug, which is it doesn't respond well to null "workspace configurations". I can't confirm right now, but I don't know if responding with [nil]
if actually wrong on Eglot's side. If you know it to be wrong, point me to some reference and I'll fix it.
[server-request] (id:3) Thu Jun 16 15:35:41 2022:
(:jsonrpc "2.0" :id 3 :method "workspace/configuration" :params
(:items
[(:scopeUri "file:///home/db/projects/ts-app/src/App.tsx" :section "")]))
[client-reply] (id:3) Thu Jun 16 15:35:41 2022:
(:jsonrpc "2.0" :id 3 :result
[nil])
Regardless, Eglot just doesn't have anything to provide to the server. So if you want it to, please read up on eglot-workspace-configuration
(look in the README/issue/discussion tracker) and try to set it up just as vscode-eslint-language-server
expects it.
On a side note, I don't understand why all these language servers don't assume sensible defaults when presented with no information. That's what "defaults" are for. I guess the VScode folks like to WETWET (write everything twice, waste everyone's time) A probable sensible default for workingDirectory
of a given language server is the rootPath
and rootUri
given by Eglot to the server in
(:processId 1008 :rootPath "/home/db/projects/ts-app/" :rootUri "file:///home/db/projects/ts-app" :initializationOptions #s(hash-table size 65 test eql rehash-size 1.5 rehash-threshold 0.8125 data
First of all, thanks for the fast response!
For what it's worth, I used to program a lot of JS/TS until recently and I used a language server and a separate eslint Flymake backend. https://github.com/orzechowskid/flymake-eslint To manually configure other Flymake backends alongside Eglot's LSP-based backend, refer to the Flymake documentation and make sure you add flymake to eglot-stay-out-of
I actually didn't know about this at all. On first sight, this seems like a suitable replacement. I'll check it out! In case it doesn't work or doesn't do what I want I'll look into workspace configuration for the lsp server. Actually, looking into it may be worth anyway.
I'll report back. Thanks.
flymake-eslint
works very well together with eglot. I really like it and it's what I'm going to use, at least for now. Thanks for pointing it out.
However, I think it's still worth setting up vscode-eslint-language-server
properly. At the moment, I use eslint-fix to fix the errors reported by eslint. It works, but it's pretty rudimentary. With vscode-eslint-language-server
, one (to just name one example) has the option to add a disable rule for the error at point. This is not possible using the flymake approach.
Let's see how far I can get configuring vscode-eslint-language-server
.
P.S.: But then again, this only really makes sense once it's possible to run multiple language servers. This is feature planned for the foreseeable future @joaotavora ?
Hi @joaotavora , I'm not sure if this is the best place to start the discussion again.
I think allowing multiple language servers in the same buffer is essential for the "frontend" development flow. The vscode-eslint-language-server
is a great example here, and there is the tailwindcss-intellisense
I want to use in jsx
, tsx
, or leex
files.
For comparison, vscode supports this out of the box, lsp-mode
can achieve this by setting the server to "add-on" mode, I haven't tried neovim, but a quick search shows that it can also be achieved.
I was hoping that I can switch to eglot since it is now in the emacs core, and I really hope that this feature I use every day can be implemented in eglot, thanks :pray:
I'd like to second @merrickluo's comment above. I'm really excited that eglot is in Emacs core now, and it seems really fast and integrates well with built-in emacs features.
But I just can't use it because I need both the typescript and eslint servers. The eslint server isn't just for highlighting errors; it's also for fixing those issues from a menu of options. By running both servers it all works seamlessly, with a consistent UX for identifying and fixing issues wherever the source (typescript or eslint).
My concern is that if this is not addressed in eglot, and since eglot is in emacs core now, Emacs will be perceived as a second class LSP citizen for a huge swath of LSP users.
I just want to say here that multi-server support is indeed number one on my Eglot/LSP/OSS todo list right now. But life keeps getting in the way, unfortunately. Let's see maybe over the holidays i can get something rolling.
was trying to solve the same problem and saw ts lang server supports plug-ins. Configured the eslint and gql plugins in my tsconfig, but this makes no difference? https://www.typescriptlang.org/tsconfig#plugins
hey thanks for the flymake-eslint tip. It works good.
edit: i was calling eglot multiple times on accident, creating chaos.
@gdanov
was trying to solve the same problem and saw ts lang server supports plug-ins. Configured the eslint and gql plugins in my tsconfig, but this makes no difference? https://www.typescriptlang.org/tsconfig#plugins
I had slightly better luck using this approach:
https://notes.alexkehayias.com/setting-up-typescript-and-eslint-with-eglot/
Pretty invasive though, and eslint code actions / fixes weren't working.
Anyone else have any luck with typescript language server plugins?
HAHAHA this is unexpectedly informative reply, thanks. By the way...
- running TS + eglot has been extremely simple and performant for me, one of the reasons I switched from the more feature-rich LSP mode.
- wasn't aware I have to hint the path to my tsconfig, I'm in multi-module project so I should've figured this out...
- so happy to find out another soul that wasted tons of time trying to move their language server into docker container, LOL, thought it's just me
Yeah it's getting closer. The ability to auto-fix lint errors is pretty important to me though for FE dev. For some reason, when using the eslint plugin as described above, eglot will show the errors but eglot-code-actions
won't show the option to fix it. It about more than just fixing niggling issues - eslint can catch and fix React bugs for example. It's a big productivity boost.
According to this page you can configure vscode to use a plugin without having to modify your project's tsconfig.json
:
VS Code has the ability for a extension to automatically include language service plugins, and so you may have some running in your editor without needing to define them in your tsconfig.json.
Maybe there's a way to get eglot to support that as well. Still need auto-fix though.
followed the steps and I think I got lang server with the eslint plugin working. I see now the code action "remove unused imports" which I'm pretty sure is eslint feature.
I'm struggling with the same problem here, specifically Eslint code actions not available in Eglot.
I'm gonna come with a slightly different idea:
I don't know too much about flymake, but is there any reason Flymake couldn't report code actions to Eglot?
Unless there's anything other than code actions that we're after here, I think it makes sense if Eglot could ask Flymake for code actions, since Flymake already handles the syntax checking and such and is the recommended checker with Eglot.
This way, flymake-eslint
would report available code actions to Eglot, and any of Flymake's multiple backends could report code actions and Eglot would combine them into a single list.
Thoughts?
Until I find time to get some multi-server support going, the best way I know of is to use a language server that does this coalescing of various sources itself. clangd
is a fine eaxmple, where clang-tidy
actions are easily available, along with some other non-clang-tidy ones. pylsp
is another example of a server who joins many sources into a single one.
To make a generic server joiner for any language, but I must admit it's harder than I thought. Every server combination must be clearly understood, what notifications go where, how to communicate with this beast, what to do when one of the subservers times out, etc, etc. Where to send requests? Both servers and merge responses? What about specific requests like rename
? Who renames? Where is are all these decisions configured?
It's easier when these decisions are taken by a single server already existing (like the clangd
example above). How hard can it be to make typescript-language server call into eslint
libraries and join its diagnostics and code actions with theirs? I know, "famous last words", but is there any technical impediment?
Alternatively, maybe someone make this server joiner just for the typescript-language-server
and vscode-eslint-language-server
with opinionated decisions on what things each server is responsible for.
Thoughts?
That doesn't work, can never work cleanly. Flymake works independently with Eglot as one of its backends. Inverting that relation for something Flymake wasn't designed is madness. Flymake diagnostics can however have clickable buttons that run arbitrary functions, but you'd be repeating a lot of the logic Eglot has for code actions, and bumping into the same problems, and they would never be available through M-x eglot-code-actions
and friends.
Thanks for the quick response @joaotavora!
Perhaps I'll take a look at the specific case of TypeScript + Eslint as some kind of server joiner. I haven't had any success using typescript-eslint-language-service
plugin, but perhaps it contains some useful code which could be ported as is into a server joined.
Makes sense about the flymake stuff, it was a weird idea I thought perhaps could be easier than running eslint multiple times (once for language server, once for flymake eslint). As I said, I have not read much about the internals of eglot and flymake. The small code I've read of Eglot seems to indicate that Eglot does call certain things from flymake, so I thought it was already communicating specifically with flymake about some things.
It's easier when these decisions are taken by a single server already existing (like the clangd example above). How hard can it be to make typescript-language server call into eslint libraries and join its diagnostics and code actions with theirs? I know, "famous last words", but is there any technical impediment?
This is keeping me from fully switching from lsp-mode
at the moment, so I just want to try and add some more context to this from a "Javscript" (in the broadest sense) perspective. Unfortunately, it's not as simple as "joining" the Typescript and ESLint language servers. This may seem a bit mad (and maybe rightly so) from the perspective of other ecosystems, but the following combination of language servers is not unusual at all for an Angular project:
-
.ts
: Typescript + ESLint + Angular -
.html
: ESLint + Angular + Tailwind
With Svelte, as another example, you could easily have a combo of Svelte + ESLint + Tailwind language servers for a .svelte
file. I'm sure there's many other combinations people use.
Being able to "compose" language servers in an arbitrary way seems very useful in this ecosystem. Unfortunately, I see the complexity in your questions and the need for configurability, @joaotavora. And lsp-mode
doesn't necessarily get this right at the moment: for example, both the Angular and the Typescript server have something to say about renaming a symbol, and last I checked, it does not work out of the box.
Just came here to say that sure eslint integration for typescript would be nice but my current blocker is with tailwindcss lsp server running in the same buffer as typescript lsp