Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code actions missing (?) in javascript project #847

Closed
minikN opened this issue Feb 25, 2022 · 24 comments
Closed

Code actions missing (?) in javascript project #847

minikN opened this issue Feb 25, 2022 · 24 comments

Comments

@minikN
Copy link

minikN commented Feb 25, 2022

Hello,

If this issue has been reported already I'm sorry, but I wasn't able to find any information on the issue tracker or Google in general.
I'd like to use eglot with react using the typescript-language-server. My setup is working, meaning I get diagnostics and can use code actions and so on.

For testing purposes, I created a new react app using create-react-app and ran npm install. Then I opened src/App.js (and modified it a bit):

import React, { useContext } from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  const [test, setTest] = useState(0);
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

There are two things I don't understand how to use:

  1. AddMissingImports: According to their doc, the typescript-language-server supports adding missing imports (this also works using VSCode for example). However I don't know how to trigger it. In the example above, useState needs to be imported. But the action is not available when doing eglot-code-actions. I tried placing the cursor on the u of useState, select the word and select the line. It always reports eglot--error: [eglot] No code actions here. Is it simply not implemented yet or am I missing something?

  2. OrganizeImports: In the example above, the useContext import should be cleaned up. When doing eglot-code-action-organize-imports it reports eglot--error: [eglot] No "source.organizeImports" code actions here. I tried selecting the whole buffer too. the only way I got this working is by placing the cursor on u of useContext and when doing eglot-code-actions it prompts with Remove unsused declaration for 'useContext'?. This works. However ideally I'd do this for all input at once.

Thanks in advance. Again if I missed something obvious then sorry. Really trying eglot for the first time, coming from lsp-mode.

Cheers,
Demis.

  • Server used: typescript-language-server
  • Emacs version: 29.0.50
  • Operating system: GNU/Guix
  • Eglot version: 1.8
  • Eglot installation method: Installed Guix package, then simply:
(eval-when-compile (require 'eglot))
(with-eval-after-load
'eglot
  (add-to-list 'eglot-server-programs
    '(typescript-tsx-mode . ("typescript-language-server" "--stdio"))))
(add-hook 'js-mode-hook 'eglot-ensure)
(add-hook 'typescript-mode-hook 'eglot-ensure)
(add-hook 'typescript-tsx-mode-hook 'eglot-ensure)
  • Using Doom: No

LSP transcript - M-x eglot-events-buffer (mandatory unless Emacs inoperable)

[client-request] (id:159) Fri Feb 25 19:48:06 2022:
(:jsonrpc "2.0" :id 159 :method "textDocument/signatureHelp" :params
          (:textDocument
           (:uri "file:///home/db/testprj/test-app/src/index.js")
           :position
           (:line 8 :character 11)))
[client-request] (id:160) Fri Feb 25 19:48:06 2022:
(:jsonrpc "2.0" :id 160 :method "textDocument/hover" :params
          (:textDocument
           (:uri "file:///home/db/testprj/test-app/src/index.js")
           :position
           (:line 8 :character 11)))
[client-request] (id:161) Fri Feb 25 19:48:06 2022:
(:jsonrpc "2.0" :id 161 :method "textDocument/documentHighlight" :params
          (:textDocument
           (:uri "file:///home/db/testprj/test-app/src/index.js")
           :position
           (:line 8 :character 11)))
[server-reply] (id:159) Fri Feb 25 19:48:06 2022:
(:jsonrpc "2.0" :id 159 :result
          (:activeSignature 0 :activeParameter 0 :signatures
                            [(:label "App(): JSX.Element" :parameters
                                     [])]))
[server-reply] (id:161) Fri Feb 25 19:48:06 2022:
(:jsonrpc "2.0" :id 161 :result
          [])
[server-reply] (id:160) Fri Feb 25 19:48:06 2022:
(:jsonrpc "2.0" :id 160 :result
          (:contents
           []))
[client-notification] Fri Feb 25 19:48:14 2022:
(:jsonrpc "2.0" :method "textDocument/didClose" :params
          (:textDocument
           (:uri "file:///home/db/testprj/test-app/src/index.js")))
[server-notification] Fri Feb 25 19:48:14 2022:
(:jsonrpc "2.0" :method "textDocument/publishDiagnostics" :params
          (:uri "file:///home/db/testprj/test-app/src/index.js" :diagnostics
                []))
[internal] Fri Feb 25 19:48:14 2022:
(:message "Diagnostics received for unvisited file:///home/db/testprj/test-app/src/index.js")
[client-notification] Fri Feb 25 20:04:00 2022:
(:jsonrpc "2.0" :method "textDocument/didOpen" :params
          (:textDocument
           (:uri "file:///home/db/testprj/test-app/src/App.js" :version 0 :languageId "js" :text "import React, { useContext } from 'react';\nimport logo from './logo.svg';\nimport './App.css';\n\nfunction App() {\n    const [state, useState] = React.useState(0);\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.js</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")))
[server-notification] Fri Feb 25 20:04:00 2022:
(:jsonrpc "2.0" :method "textDocument/publishDiagnostics" :params
          (:uri "file:///home/db/testprj/test-app/src/App.js" :diagnostics
                [(:range
                  (:start
                   (:line 0 :character 16)
                   :end
                   (:line 0 :character 26))
                  :message "'useContext' is declared but its value is never read." :severity 4 :code 6133 :source "typescript")
                 (:range
                  (:start
                   (:line 5 :character 11)
                   :end
                   (:line 5 :character 16))
                  :message "'state' is declared but its value is never read." :severity 4 :code 6133 :source "typescript")
                 (:range
                  (:start
                   (:line 5 :character 18)
                   :end
                   (:line 5 :character 26))
                  :message "'useState' is declared but its value is never read." :severity 4 :code 6133 :source "typescript")]))
[server-notification] Fri Feb 25 20:04:00 2022:
(:jsonrpc "2.0" :method "textDocument/publishDiagnostics" :params
          (:uri "file:///home/db/testprj/test-app/src/App.js" :diagnostics
                [(:range
                  (:start
                   (:line 0 :character 16)
                   :end
                   (:line 0 :character 26))
                  :message "'useContext' is declared but its value is never read." :severity 4 :code 6133 :source "typescript")
                 (:range
                  (:start
                   (:line 5 :character 11)
                   :end
                   (:line 5 :character 16))
                  :message "'state' is declared but its value is never read." :severity 4 :code 6133 :source "typescript")
                 (:range
                  (:start
                   (:line 5 :character 18)
                   :end
                   (:line 5 :character 26))
                  :message "'useState' is declared but its value is never read." :severity 4 :code 6133 :source "typescript")]))
[client-request] (id:162) Fri Feb 25 20:04:01 2022:
(:jsonrpc "2.0" :id 162 :method "textDocument/signatureHelp" :params
          (:textDocument
           (:uri "file:///home/db/testprj/test-app/src/App.js")
           :position
           (:line 25 :character 0)))
[client-request] (id:163) Fri Feb 25 20:04:01 2022:
(:jsonrpc "2.0" :id 163 :method "textDocument/hover" :params
          (:textDocument
           (:uri "file:///home/db/testprj/test-app/src/App.js")
           :position
           (:line 25 :character 0)))
[client-request] (id:164) Fri Feb 25 20:04:01 2022:
(:jsonrpc "2.0" :id 164 :method "textDocument/documentHighlight" :params
          (:textDocument
           (:uri "file:///home/db/testprj/test-app/src/App.js")
           :position
           (:line 25 :character 0)))
[server-reply] (id:162) Fri Feb 25 20:04:01 2022:
(:jsonrpc "2.0" :id 162 :result nil)
[server-reply] (id:164) Fri Feb 25 20:04:01 2022:
(:jsonrpc "2.0" :id 164 :result
          [])
[server-reply] (id:163) Fri Feb 25 20:04:01 2022:
(:jsonrpc "2.0" :id 163 :result
          (:contents
           []))
[client-request] (id:165) Fri Feb 25 20:04:06 2022:
(:jsonrpc "2.0" :id 165 :method "shutdown" :params nil)
[server-reply] (id:165) Fri Feb 25 20:04:06 2022:
(:jsonrpc "2.0" :id 165 :result nil)
[client-notification] Fri Feb 25 20:04:06 2022:
(:jsonrpc "2.0" :method "exit" :params nil)
[internal] Fri Feb 25 20:04:06 2022:
(:message "Connection state changed" :change "killed\n")

----------b---y---e---b---y---e----------
[stderr] 
[stderr] 
[stderr] nil
[stderr] nil
[stderr] Process EGLOT (test-app/js-mode) stderr finished
[client-request] (id:1) Fri Feb 25 20:04:06 2022:
(:jsonrpc "2.0" :id 1 :method "initialize" :params
          (:processId 31745 :rootPath "/home/db/testprj/test-app/" :rootUri "file:///home/db/testprj/test-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] Fri Feb 25 20:04:06 2022:
(:jsonrpc "2.0" :method "window/logMessage" :params
          (:type 3 :message "[lspserver] Using Typescript version (bundled) 4.5.5 from path \"/home/db/testprj/global_node_modules/lib/node_modules/typescript/lib/tsserver.js\""))
[server-reply] (id:1) Fri Feb 25 20:04:06 2022:
(:jsonrpc "2.0" :id 1 :result
          (:capabilities
           (:textDocumentSync 2 :completionProvider
                              (:triggerCharacters
                               ["." "\"" "'" "/" "@" "<"]
                               :resolveProvider t)
                              :codeActionProvider
                              (:codeActionKinds
                               ["source.fixAll.ts" "source.removeUnused.ts" "source.addMissingImports.ts" "source.organizeImports.ts"])
                              :definitionProvider t :documentFormattingProvider t :documentRangeFormattingProvider t :documentHighlightProvider t :documentSymbolProvider t :executeCommandProvider
                              (:commands
                               ["_typescript.applyWorkspaceEdit" "_typescript.applyCodeAction" "_typescript.applyRefactoring" "_typescript.organizeImports" "_typescript.applyRenameFile"])
                              :hoverProvider t :renameProvider t :referencesProvider t :signatureHelpProvider
                              (:triggerCharacters
                               ["(" "," "<"])
                              :workspaceSymbolProvider t :implementationProvider t :typeDefinitionProvider t :foldingRangeProvider t :semanticTokensProvider
                              (:documentSelector nil :legend
                                                 (:tokenTypes
                                                  ["class" "enum" "interface" "namespace" "typeParameter" "type" "parameter" "variable" "enumMember" "property" "function" "member"]
                                                  :tokenModifiers
                                                  ["declaration" "static" "async" "readonly" "defaultLibrary" "local"])
                                                 :full t :range t)
                              :callsProvider t)))
[client-notification] Fri Feb 25 20:04:06 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] Fri Feb 25 20:04:06 2022:
(:jsonrpc "2.0" :method "textDocument/didOpen" :params
          (:textDocument
           (:uri "file:///home/db/testprj/test-app/src/App.js" :version 0 :languageId "js" :text "import React, { useContext } from 'react';\nimport logo from './logo.svg';\nimport './App.css';\n\nfunction App() {\n    const [state, useState] = React.useState(0);\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.js</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] Fri Feb 25 20:04:06 2022:
(:jsonrpc "2.0" :method "workspace/didChangeConfiguration" :params
          (:settings nil))
[server-notification] Fri Feb 25 20:04:09 2022:
(:jsonrpc "2.0" :method "textDocument/publishDiagnostics" :params
          (:uri "file:///home/db/testprj/test-app/src/App.js" :diagnostics
                []))
[server-notification] Fri Feb 25 20:04:09 2022:
(:jsonrpc "2.0" :method "textDocument/publishDiagnostics" :params
          (:uri "file:///home/db/testprj/test-app/src/App.js" :diagnostics
                [(:range
                  (:start
                   (:line 0 :character 16)
                   :end
                   (:line 0 :character 26))
                  :message "'useContext' is declared but its value is never read." :severity 4 :code 6133 :source "typescript")
                 (:range
                  (:start
                   (:line 5 :character 11)
                   :end
                   (:line 5 :character 16))
                  :message "'state' is declared but its value is never read." :severity 4 :code 6133 :source "typescript")
                 (:range
                  (:start
                   (:line 5 :character 18)
                   :end
                   (:line 5 :character 26))
                  :message "'useState' is declared but its value is never read." :severity 4 :code 6133 :source "typescript")]))

Minimal configuration (mandatory)

# Type this in a shell to start an Emacs with Eglot configured
$ emacs -Q -f package-initialize -L $EMACSLOADPATH/eglot-1.8 -l eglot.el
;; Example of a minimal configuration
;;
;; No special config. Just open a react project (App.js) and `M-x eglot'
@minikN
Copy link
Author

minikN commented Mar 5, 2022

After digging a bit further, I fixed half of my problem. I looked at eglots code and then evaluated the following:

(eglot--code-action eglot-code-action-organize-imports-ts "source.organizeImports.ts")

After that, I can execute eglot-code-action-organize-imports-ts and it will indeed successfully removed all unused imports. Logically I tried the same with missing imports:

(eglot--code-action eglot-code-action-add-missing-imports-ts "source.addMissingImports.ts")

Source: https://github.com/typescript-language-server/typescript-language-server#code-actions-on-save

But this doesn't work. It says [eglot] No "source.addMissingImports.ts" code actions here I tried selecting the whole buffer, the symbol, everything I cloud think of.

@minikN
Copy link
Author

minikN commented Mar 5, 2022

One more update. I was testing this in a javascript application. I just did the same thing in a typescript application using npx create-react-app test-app --template typescript. Here the addMissingImports.ts code action from my last comment works flawlessly. Before executing it, I also get a flymake error with the useState symbol: typescript: Cannot find name 'useState'. The same error doesn't occur in the javascript project and therefore the code action is not available. I guess at this point, this is something to tackle with the lsp server itself?

@nemethf
Copy link
Collaborator

nemethf commented Mar 5, 2022

I have hard time understanding this part of the specification. The client declares what kinds of codeActions it supports in its CodeActionClientCapabilities. However, the server replies with CodeActionKinds that list the actions the server may return. What's the relationship between two lists is unclear. Also, if the client doesn't specify an only argument in the context part of the textDocument/codeAction request, then the server should return all possible actions for the given context. At least that's my reading of the specification.

The event log of the original bug report does not include a textDocument/codeAction request, so it's impossible to say how the server reacted to a 'give-me-all' request (M-x eglot-code-actions RET).

@minikN
Copy link
Author

minikN commented Mar 5, 2022

I have hard time understanding this part of the specification. The client declares what kinds of codeActions it supports in its CodeActionClientCapabilities. However, the server replies with CodeActionKinds that list the actions the server may return. What's the relationship between two lists is unclear. Also, if the client doesn't specify an only argument in the context part of the textDocument/codeAction request, then the server should return all possible actions for the given context. At least that's my reading of the specification.

The event log of the original bug report does not include a textDocument/codeAction request, so it's impossible to say how the server reacted to a 'give-me-all' request (M-x eglot-code-actions RET).

In the typescript project, in App.tsx, placing cursor over u of useState then M-x eglot-code-actions RET:

[client-request] (id:79) Sat Mar  5 19:07:51 2022:
(:jsonrpc "2.0" :id 79 :method "textDocument/codeAction" :params
          (:textDocument
           (:uri "file:///home/db/testprj-ts/test-app/src/App.tsx")
           :range
           (:start
            (:line 5 :character 30)
            :end
            (:line 5 :character 30))
           :context
           (:diagnostics
            [(:range
              (:start
               (:line 5 :character 30)
               :end
               (:line 5 :character 38))
              :message "Cannot find name 'useState'." :severity 1 :code 2304 :source "typescript")])))
[server-reply] (id:79) Sat Mar  5 19:07:51 2022:
(:jsonrpc "2.0" :id 79 :result
          [(:title "Update import from \"react\"" :command
                   (:title "Update import from \"react\"" :command "_typescript.applyWorkspaceEdit" :arguments
                           [(:documentChanges
                             [(:textDocument
                               (:uri "file:///home/db/testprj-ts/test-app/src/App.tsx" :version 50)
                               :edits
                               [(:range
                                 (:start
                                  (:line 0 :character 12)
                                  :end
                                  (:line 0 :character 12))
                                 :newText ", { useState }")])])])
                   :kind "quickfix")
           (:title "Add missing function declaration 'useState'" :command
                   (:title "Add missing function declaration 'useState'" :command "_typescript.applyWorkspaceEdit" :arguments
                           [(:documentChanges
                             [(:textDocument
                               (:uri "file:///home/db/testprj-ts/test-app/src/App.tsx" :version 50)
                               :edits
                               [(:range
                                 (:start
                                  (:line 26 :character 20)
                                  :end
                                  (:line 26 :character 20))
                                 :newText "\nfunction useState(): [any, any] {\n    throw new Error('Function not implemented.');\n}\n")])])])
                   :kind "quickfix")])

Works as expected.

Doing exactly the same thing in javascript project (App.js):

[client-request] (id:169) Sat Mar  5 19:12:37 2022:
(:jsonrpc "2.0" :id 169 :method "textDocument/codeAction" :params
          (:textDocument
           (:uri "file:///home/db/testprj/test-app/src/App.js")
           :range
           (:start
            (:line 5 :character 28)
            :end
            (:line 5 :character 28))
           :context
           (:diagnostics
            [])))
[server-reply] (id:169) Sat Mar  5 19:12:37 2022:
(:jsonrpc "2.0" :id 169 :result
          [])

@minikN
Copy link
Author

minikN commented Mar 5, 2022

Okay I got it fixed: One needs a jsconfig.json in the project root. I just copied the tsconfig.json from the typescript project, the two important options that need to be enabled are allowJs and checkJs. After that, it works perfectly:

{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "checkJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": [
    "src"
  ]
}

@minikN minikN closed this as completed Mar 5, 2022
nemethf added a commit that referenced this issue Mar 12, 2022
I thought the server must reply with a subset of the client's list of
codeActionKind during the initialization.  But it's clearly not the case.
The typescript-language-server returns these [1]:

  ["source.fixAll.ts" "source.removeUnused.ts" "source.addMissingImports.ts" ...]

The clangd server returns these [2]:

  ["quickfix" "refactor" "info"]

(Additionally, in the current code there is a mismatch between what
Eglot initially sent and the completing read arguemnt of
eglot-code-actions.  "source" and "refactor" is missing from
`eglot-code-actions'. Was this intentional?)

Now, Eglot plays safe, and offers the union of the two lists as
possible completions in `eglot-code-actions'.

[1]: #847 (comment)
[2]: #860
@minikN
Copy link
Author

minikN commented Jan 1, 2024

Hello,

Unfortunately I need to reopen this issue. I just revisited this as I haven't touched it in a while. I am not able to execute the organizeImports code action. When I do so in a JSX file, eglot reports No 'source.organizeImports' code action here.

The same thing works with VSCode, this is what vscode sends to the server:

    {
      "seq": 34,
      "type": "request",
      "command": "organizeImports",
      "arguments": {
        "scope": {
          "type": "file",
          "args": {
            "file": "/home/db/.local/share/git/gaia/client/platform/web/src/ui/Component/File/Hotspot.jsx"
          }
        },
        "skipDestructiveCodeActions": false,
        "mode": "All"
      }
    }

I'm not quite sure how to trigger this in eglot. Unfortunately, I can't post the output of my eglot events buffer, because it's empty for some reason. However in general, the connection to the lsp works (I get ts warning, errors, actions like rename etc work fine). No idea why it's empty.

Also, the hack I mentioned here is not working anymore, if I do this, I get the same message saying that the action is not available like above.

In addition, when executing eglot-code-actions, I sometimes get

jsonrpc-request: jsonrpc-error: "request id=9 failed:", (jsonrpc-error-code . 1), (jsonrpc-error-message . "<semantic> TypeScript Server Error (5.3.3)
BADCLIENT: Bad error code, 7044 not found in range 350..350 (found: false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false); could have caused this error:
Debug Failure. Invalid cast. The supplied value [object Object] did not pass the test 'isCallExpression'.
Error: BADCLIENT: Bad error code, 7044 not found in range 350..350 (found: false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false); could have caused this error:
Debug Failure. Invalid cast. The supplied value [object Object] did not pass the test 'isCallExpression'.
    at cast (/nix/store/bdfnpa2jhkfw1vljp9xifr75n11smnix-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:3481:16)
    at addMissingNewOperator (/nix/store/bdfnpa2jhkfw1vljp9xifr75n11smnix-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:152939:16)
    at /nix/store/bdfnpa2jhkfw1vljp9xifr75n11smnix-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:152932:79
    at _ChangeTracker.with (/nix/store/bdfnpa2jhkfw1vljp9xifr75n11smnix-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:168580:5)
    at Object.getCodeActions (/nix/store/bdfnpa2jhkfw1vljp9xifr75n11smnix-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:152932:62)
    at /nix/store/bdfnpa2jhkfw1vljp9xifr75n11smnix-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:147100:46
    at flatMap (/nix/store/bdfnpa2jhkfw1vljp9xifr75n11smnix-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:2597:17)
    at Object.getFixes (/nix/store/bdfnpa2jhkfw1vljp9xifr75n11smnix-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:147100:10)
    at /nix/store/bdfnpa2jhkfw1vljp9xifr75n11smnix-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:145385:33
    at flatMap (/nix/store/bdfnpa2jhkfw1vljp9xifr75n11smnix-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:2597:17)
    at Object.getCodeFixesAtPosition (/nix/store/bdfnpa2jhkfw1vljp9xifr75n11smnix-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:145383:12)
    at IpcIOSession.getCodeFixes (/nix/store/bdfnpa2jhkfw1vljp9xifr75n11smnix-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:185071:50)
    at getCodeFixes (/nix/store/bdfnpa2jhkfw1vljp9xifr75n11smnix-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:183183:43)
    at /nix/store/bdfnpa2jhkfw1vljp9xifr75n11smnix-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:185375:69
    at IpcIOSession.executeWithRequestId (/nix/store/bdfnpa2jhkfw1vljp9xifr75n11smnix-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:185367:14)
    at IpcIOSession.executeCommand (/nix/store/bdfnpa2jhkfw1vljp9xifr75n11smnix-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:185375:29)
    at IpcIOSession.onMessage (/nix/store/bdfnpa2jhkfw1vljp9xifr75n11smnix-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:185417:51)
    at process.<anonymous> (/nix/store/bdfnpa2jhkfw1vljp9xifr75n11smnix-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:186999:14)
    at process.emit (node:events:514:28)
    at emit (node:internal/child_process:951:14)
    at process.processTicksAndRejections (node:internal/process/task_queues:83:21)"), (jsonrpc-error-data)

It seems to only happen when the cursor is not on a symbol (but maybe an empty line).

Another thing I noticed is, that in order to get the correct available code actions, I need to select (mark) the symbol the cursor is on:

  const theme = useTheme()

Say I have not imported useTheme yet, if the cursor is on that symbol and I execute eglot-code-actions, I don't get the Add import for ... code action. If I mark the word useTheme and execute eglot-code-actions again, I get said action. Is that the normal behaviour?

Again, unfortunately my events buffer remains empty, so I can't post its output.

My eglot related config is here and here.

In essence, these are the questions I have:

  • How can I trigger the organizeImports action?
  • Why do I have to mark the symbol in order to get proper code actions?
  • Is there still no way I can execute LSP-specific actions like these?

M-x version:

GNU Emacs 30.0.50 (build 1, x86_64-pc-linux-gnu, GTK+ Version 3.24.38, cairo version 1.18.0)

I am on eglot version 1.16 from ELPA.

Thanks in advance!

@minikN minikN reopened this Jan 1, 2024
@joaotavora
Copy link
Owner

Unfortunately, I can't post the output of my eglot events buffer, because it's empty for some reason

Well, mine isn't, so the likely culprit is something in your configuration.

Please understand that very little can be done without the eglot events buffer or without an -Q reproduction recipe as described in the manual. Sorry if I sound bitter, but I'm running out of ways to pass this message.

I got lucky this time and managed to find a way to study the problem quickly with a single file.

How can I trigger the organizeImports action?

The previous technique you uses in 2022 should work. #847 (comment)

If it doesn't, the only way to debug this is to have that recipe I ask for.

Why do I have to mark the symbol in order to get proper code actions?

You don't. LSP dictates that LSP code actions are requested for a range. As explained in eglot-code-actions, if the region
is empty, actions "at point" are requested. What is returned in each situation is entirely up to the server.

Find LSP code actions of type ACTION-KIND between BEG and END.
Interactively, offer to execute them.
If ACTION-KIND is nil, consider all kinds of actions.
Interactively, default BEG and END to region's bounds else BEG is
point and END is nil, which results in a request for code actions
at point.  With prefix argument, prompt for ACTION-KIND.

The last sentence is relevant, we can use it to invoke exactly the action the server is supplying.

Is there still no way I can execute LSP-specific actions like these?

I can execute some actions just fine. Notice how C-u is used to be able to control the shape of the request of eglot-code-actions.

$have typescript-language-server 4.2.0 installed globally 
/path/to/recent/master/emacs -Q ~/tmp/thingy.mjs

;; paste in the contents of the first snippet in https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
M-x eglot
C-u M-x eglot-code-actions RET source.organizeImports.ts RET
;; imports are "organized"

This works today.

I may of course also ask the server to :only return source.organizeImports (no "ts" suffix) actions, which is a standard LSP name. This is what happens with the bundled M-x eglot-code-actions-organize-imports command.

Here, the TS servers still returns actions, but says they're of kind source.organizeImports.ts. Eglot will filter them out.

At first I thought this was a server bug, but it isn't. The names are hierarchical. I've pushed a fix to master so eventually Eglot 1.17 will have it and have some understanding of this hierarchy.

If you want, you can get Emacs master right now or get bleeding edge Eglot from GNU-devel ELPA in a day or so.

dickmao pushed a commit to commercial-emacs/commercial-emacs that referenced this issue Jan 1, 2024
Github-reference: joaotavora/eglot#847

Servers like typescript-language-server, when asked for {"only" :
"source.organizeImports"}, return actions with the
"source.organizeImports.ts" kind.  Eglot rejected these actions, but
according to the spec:

  Kinds are a hierarchical list of identifiers separated by `.` [...]
  The set of kinds is open.

So I guess we can use string-prefix-p

* lisp/progmodes/eglot.el (eglot-code-actions): Use string-prefix-p.
@minikN
Copy link
Author

minikN commented Jan 2, 2024

Please understand that very little can be done without the eglot events buffer or without an -Q reproduction recipe as described in the manual. Sorry if I sound bitter, but I'm running out of ways to pass this message.

You don't sound bitter. I can completely understand that it's frustrating reading comments not providing the information needed. Sorry.

I was able to get the events buffer back, I had misconfigured something. I also updated to typescript-language-server 4.2.0 and typescript 5.3.3. However, to minimize interference, I start emacs with:

emacs -Q --eval "(with-eval-after-load 'eglot (add-to-list 'eglot-server-programs '((js-mode js-ts-mode typescript-ts-mode tsx-ts-mode) . (\"/nix/store/1ib64cn3b55dv44w795rnca6qbhjh07b-typescript-language-server-4.2.0/bin/typescript-language-server\" \"--stdio\" :initializationOptions (:tsserver (:path \"/nix/store/w7ky32k69jdlp8v3w0nd33llj9zpn77k-typescript-5.3.3/lib/node_modules/typescript/lib\"))))))"

Emacs start, I open a JSX file in my work project, I go to an empty line, M-x eglot-code-actions:

jsonrpc-request: jsonrpc-error: "request id=40 failed:", (jsonrpc-error-code . 1), (jsonrpc-error-message . "<semantic> TypeScript Server Error (5.3.3)
BADCLIENT: Bad error code, 7044 not found in range 4465..4465 (found: false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false); could have caused this error:
Debug Failure. Invalid cast. The supplied value [object Object] did not pass the test 'isCallExpression'.
Error: BADCLIENT: Bad error code, 7044 not found in range 4465..4465 (found: false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false); could have caused this error:
Debug Failure. Invalid cast. The supplied value [object Object] did not pass the test 'isCallExpression'.
    at cast (/nix/store/w7ky32k69jdlp8v3w0nd33llj9zpn77k-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:3481:16)
    at addMissingNewOperator (/nix/store/w7ky32k69jdlp8v3w0nd33llj9zpn77k-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:152939:16)
    at /nix/store/w7ky32k69jdlp8v3w0nd33llj9zpn77k-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:152932:79
    at _ChangeTracker.with (/nix/store/w7ky32k69jdlp8v3w0nd33llj9zpn77k-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:168580:5)
    at Object.getCodeActions (/nix/store/w7ky32k69jdlp8v3w0nd33llj9zpn77k-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:152932:62)
    at /nix/store/w7ky32k69jdlp8v3w0nd33llj9zpn77k-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:147100:46
    at flatMap (/nix/store/w7ky32k69jdlp8v3w0nd33llj9zpn77k-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:2597:17)
    at Object.getFixes (/nix/store/w7ky32k69jdlp8v3w0nd33llj9zpn77k-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:147100:10)
    at /nix/store/w7ky32k69jdlp8v3w0nd33llj9zpn77k-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:145385:33
    at flatMap (/nix/store/w7ky32k69jdlp8v3w0nd33llj9zpn77k-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:2597:17)
    at Object.getCodeFixesAtPosition (/nix/store/w7ky32k69jdlp8v3w0nd33llj9zpn77k-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:145383:12)
    at IpcIOSession.getCodeFixes (/nix/store/w7ky32k69jdlp8v3w0nd33llj9zpn77k-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:185071:50)
    at getCodeFixes (/nix/store/w7ky32k69jdlp8v3w0nd33llj9zpn77k-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:183183:43)
    at /nix/store/w7ky32k69jdlp8v3w0nd33llj9zpn77k-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:185375:69
    at IpcIOSession.executeWithRequestId (/nix/store/w7ky32k69jdlp8v3w0nd33llj9zpn77k-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:185367:14)
    at IpcIOSession.executeCommand (/nix/store/w7ky32k69jdlp8v3w0nd33llj9zpn77k-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:185375:29)
    at IpcIOSession.onMessage (/nix/store/w7ky32k69jdlp8v3w0nd33llj9zpn77k-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:185417:51)
    at process.<anonymous> (/nix/store/w7ky32k69jdlp8v3w0nd33llj9zpn77k-typescript-5.3.3/lib/node_modules/typescript/lib/tsserver.js:186999:14)
    at process.emit (node:events:517:28)
    at emit (node:internal/child_process:944:14)
    at process.processTicksAndRejections (node:internal/process/task_queues:83:21)"), (jsonrpc-error-data)

I can execute some actions just fine. Notice how C-u is used to be able to control the shape of the request of eglot-code-actions.

I also tested this. Except I used my project file instead of the snippet you posted. I get

eglot--error: [eglot] No "source.organizeImports" code actions here

Full log: https://pastebin.com/EHY9GUWV

Thanks!

@joaotavora
Copy link
Owner

I also tested this.

And what was the outcome?

Except I used my project file instead of the snippet you posted. I get

OK, but first try it with the .mjs file-making technique I supplied earlier. That's the only way we can establish some boundaries: if my recipe works for you then we know the problem is somewhere in the middle, and knowing that is good.

I can't try your recipe because I don't have your jsx file (is it a react? file). That big backtrace is coming from Node, in Microsoft TS land, not from Eglot. Also I don't have 'nix'. Also I wonder if you need that big trailer of things in eglot-server-programs. Why can't you just use the default?

@minikN
Copy link
Author

minikN commented Jan 5, 2024

OK, but first try it with the .mjs file-making technique I supplied earlier. That's the only way we can establish some boundaries: if my recipe works for you then we know the problem is somewhere in the middle, and knowing that is good.

I actually have a hard time getting this to work. I have typescript-language-server installed globally. I created ~/test.mjs, pasted code-snippet you mentioned and saved it. Then emacs -Q ~/test.mjs. The mode is still set to fundamental-mode, so I do M-x js-mode, then M-x eglot. I get

eglot--error: [eglot] -32603: Request initialize failed with message: Could not find a valid TypeScript installation. Please ensure that the "typescript" dependency is installed in the workspace or that a valid `tsserver.path` is specified. Exiting.

On NixOS, It doesn't work to have typescript installed globally:

lib > which tsserver
/etc/profiles/per-user/db/bin/tsserver

So I start emacs with this:

emacs -Q --eval "(progn (require 'eglot) (add-to-list 'eglot-server-programs '((js-mode js-ts-mode typescript-ts-mode tsx-ts-mode) . (\"typescript-language-server\" \"--stdio\" :initializationOptions (:tsserver (:path \"/nix/store/w7ky32k69jdlp8v3w0nd33llj9zpn77k-typescript-5.3.3/lib/node_modules/typescript/lib\"))))))" ~/tmp/test.mjs

Then M-x js-mode, M-x eglot, it says:

[eglot] Connected! Server `EGLOT (tmp/(js-mode js-ts-mode typescript-ts-mode tsx-ts-mode))' now managing `(js-mode js-ts-mode typescript-ts-mode tsx-ts-mode)' buffers in project `tmp'.
[eglot] Server reports (type=1): Inlay Hints request failed. File not opened in the editor.

I do M-x eglot again and tell it to reconnect, then the errors goes away. Now i can try C-u M-x eglot-code-actions RET source.organizeImports.ts RET (or with just source.organizeImports) I always get No [...] code action here.

Log: https://pastebin.com/f1mxXZ4T

Also I don't have 'nix'. Also I wonder if you need that big trailer of things in eglot-server-programs. Why can't you just use the default?

Apart from what I already explained above, I like to specifically set paths for typescript-language-server and tsserver.path in eglot. I don't write out the actual path (like nix/store/...). But rather in my nix config, I specify a particular version of the application, and use it in the emacs config, like so:

(add-to-list
   'eglot-server-programs
   '((js-ts-mode
      typescript-ts-mode
      tsx-ts-mode) . ("${customNodePackages.typescript-language-server}/bin/typescript-language-server" "--stdio"
      :initializationOptions
      (:tsserver (:path "${customNodePackages.typescript}/lib/node_modules/typescript/lib"))))))

(Source)

That nix configuration will eventually be used to create my emacs config, and the package (${customNodePackages.typescript-language-server}) will be replaced with the real path (/nix/store/...). I do this because this makes my environment reproducible. The version of typescript-language-server I used is fixed, unless I manually change it to point the another/newer version.

Anyway, I just explained this to give you some background information. Doing it like this is common practice is the nix-world. But regardless, it should of course work even if I want to directly specify those paths.

And I don't think nix is a problem here. As I mentioned before, eglot generally works (flymake errors, code actions like rename seem to work just fine).

@joaotavora
Copy link
Owner

Whatever Nix does is not a problem unless you're trying to communicate a bug report to someone who doesn't use it. I you should just omit irrelevant s stuff like that if it is indeed irrelevant. I'll double check my recipe

@minikN
Copy link
Author

minikN commented Jan 5, 2024

Whatever Nix does is not a problem unless you're trying to communicate a bug report to someone who doesn't use it. I you should just omit irrelevant s stuff like that if it is indeed irrelevant. I'll double check my recipe

There is no bug with NixOS and I don't intend to report a bug. You asked

Also I don't have 'nix'. Also I wonder if you need that big trailer of things in eglot-server-programs. Why can't you just use the default?

and I was trying to provide some background information for clarity.

Regardless, in an effort to

omit irrelevant s stuff like that if it is indeed irrelevant

I did the following:

  • Removed any typescript-language-server / typescript npm packages installed via nix
  • npm set prefix ~/.npm-global (this is needed on NixOS to be able to install packages via npm i -g)
  • Added ~/.npm-global to $PATH
  • npm i -g typescript-language-server typescript
db > which typescript-language-server                                                                                                                                
/home/db/.npm-global/bin/typescript-language-server
db > which tsserver
/home/db/.npm-global/bin/tsserver
db >
  • followed your recipe again (with emacs -Q ~/tmp/test.mjs)

Result is same as before. Eglot connects fine and seems to work (I get flymake errors), but I can't execute organizeImports code action: No ... code action here.

Log: https://pastebin.com/UdXd8FrK

EDIT: source.organizeImports produces No ... code action here. But source.organizeImports.ts does work!

@joaotavora
Copy link
Owner

joaotavora commented Jan 5, 2024

But source.organizeImports.ts does work!

Aha! OK, so sanity finally achieved.

This is consistent to what I found in the log excerpt, too. When you ask for source.organizeImports ,the server gets you a lot of actions, only not exactly under that name.

[jsonrpc] e[16:10:00.721] --> textDocument/codeAction[6]   .... "only":["source.organizeImports"]}}}
                                                                                                             ^^^^^^^^^^^^^^
[jsonrpc] e[16:10:00.794]   <-- textDocument/codeAction[6] ... "kind":"source.organizeImports.ts"}]}

The bleeding edge Eglot should fix this, until then, you should use C-u M-x..., or use your previous hack.

So what you should probalby do now, is to check in your more elaborated project and see if you can find a similar mismatch between the requested kind and the actual kind returned.

@minikN
Copy link
Author

minikN commented Jan 5, 2024

So what you should probalby do now, is to check in your more elaborated project and see if you can find a similar mismatch between the requested kind and the actual kind returned.

I just opened the same js project/file that I worked on before and that was producing the errors. Everything seems to work with this setup now. Also these code actions worked fine, using C-u M-x eglot-code-actions.

I now try to figure out why it breaks when I manually specify the paths to typescript-language-server / tsserver.

Thanks for your help so far.

@minikN
Copy link
Author

minikN commented Jan 5, 2024

Findings so far:

I'm still running into this error, even when using emacs -Q:

jsonrpc-request: jsonrpc-error: "request id=14 failed:", (jsonrpc-error-code . 1), (jsonrpc-error-message . "<semantic> TypeScript Server Error (5.3.3)
BADCLIENT: Bad error code, 7044 not found in range 592..592 (found: false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false); could have caused this error:
Debug Failure. Invalid cast. The supplied value [object Object] did not pass the test 'isCallExpression'.
Error: BADCLIENT: Bad error code, 7044 not found in range 592..592 (found: false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false); could have caused this error:
Debug Failure. Invalid cast. The supplied value [object Object] did not pass the test 'isCallExpression'.
    at cast (/home/db/.npm-global/lib/node_modules/typescript/lib/tsserver.js:3481:16)
    at addMissingNewOperator (/home/db/.npm-global/lib/node_modules/typescript/lib/tsserver.js:152939:16)
    at /home/db/.npm-global/lib/node_modules/typescript/lib/tsserver.js:152932:79
    at _ChangeTracker.with (/home/db/.npm-global/lib/node_modules/typescript/lib/tsserver.js:168580:5)
    at Object.getCodeActions (/home/db/.npm-global/lib/node_modules/typescript/lib/tsserver.js:152932:62)
    at /home/db/.npm-global/lib/node_modules/typescript/lib/tsserver.js:147100:46
    at flatMap (/home/db/.npm-global/lib/node_modules/typescript/lib/tsserver.js:2597:17)
    at Object.getFixes (/home/db/.npm-global/lib/node_modules/typescript/lib/tsserver.js:147100:10)
    at /home/db/.npm-global/lib/node_modules/typescript/lib/tsserver.js:145385:33
    at flatMap (/home/db/.npm-global/lib/node_modules/typescript/lib/tsserver.js:2597:17)
    at Object.getCodeFixesAtPosition (/home/db/.npm-global/lib/node_modules/typescript/lib/tsserver.js:145383:12)
    at IpcIOSession.getCodeFixes (/home/db/.npm-global/lib/node_modules/typescript/lib/tsserver.js:185071:50)
    at getCodeFixes (/home/db/.npm-global/lib/node_modules/typescript/lib/tsserver.js:183183:43)
    at /home/db/.npm-global/lib/node_modules/typescript/lib/tsserver.js:185375:69
    at IpcIOSession.executeWithRequestId (/home/db/.npm-global/lib/node_modules/typescript/lib/tsserver.js:185367:14)
    at IpcIOSession.executeCommand (/home/db/.npm-global/lib/node_modules/typescript/lib/tsserver.js:185375:29)
    at IpcIOSession.onMessage (/home/db/.npm-global/lib/node_modules/typescript/lib/tsserver.js:185417:51)
    at process.<anonymous> (/home/db/.npm-global/lib/node_modules/typescript/lib/tsserver.js:186999:14)
    at process.emit (node:events:514:28)
    at emit (node:internal/child_process:951:14)
    at process.processTicksAndRejections (node:internal/process/task_queues:83:21)"), (jsonrpc-error-data)

It happens if I move the cursor to an empty line or a line with a comment.

Log: https://pastebin.com/kwVWdTuf

@joaotavora Can you tell me an email to send the JSX file to? I can't share that publicly.

@joaotavora
Copy link
Owner

I now try to figure out why it breaks when I manually specify the paths to typescript-language-server / tsserver.

Ah, good. So you confirm that bare .mjs recipe starts failing under these conditions where Nix and a longer invocation is used? Or are you also trying with your more complex recipe?

I'm still running into this error, even when using emacs -Q:

This is a pure server error. I don't think it has anything to do with Eglot. Unless Eglot somehow provided the server with bad data before, which I doubt. I suggest you report this error to the server devs!

@joaotavora Can you tell me an email to send the JSX file to? I can't share that publicly.

My email is in the source. if I can help you I will, But I only have an Archlinux and Node installation, and installing more toolchains wasn't in my plans :-)

@minikN
Copy link
Author

minikN commented Jan 6, 2024

Ah, good. So you confirm that bare .mjs recipe starts failing under these conditions where Nix and a longer invocation is used? Or are you also trying with your more complex recipe?

It has nothing to do with nix paths. In my latest test I installed typescript-language-server globally, using npm, not nix. Also not using any specific eglot configuration but the default it comes with. The error still persists.

This is a pure server error. I don't think it has anything to do with Eglot. Unless Eglot somehow provided the server with bad data before, which I doubt. I suggest you report this error to the server devs!

I created an issue here. Still seems unlikely to me that this is an internal tsserver bug.
I will send you the JSX file via email, maybe you can reproduce it.

@minikN
Copy link
Author

minikN commented Jan 6, 2024

@joaotavora I talked to the typescript-language-server maintainer in the issue link above and they were able to reproduce by bug, but told me that it's caused by a wrong request by eglot. Maybe you should give your input, you can read it here. Thanks.

@joaotavora
Copy link
Owner

Still seems unlikely to me that this is an internal tsserver bug.

Let's get one thing straight. Eglot does not have Javascript code, it's written in Elisp. So if a server is spewing out a Javascript
backtrace in response to an LSP request that reads like this:

Error: BADCLIENT: Bad error code, 7044 not found in range 592..592 (found: false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false); could have caused this error:
Debug Failure. Invalid cast. The supplied value [object Object] did not pass the test 'isCallExpression'.

Wherever this comes from, whatever this is, this tells me there's something to fix on the server side.

but told me that it's caused by a wrong request by eglo

Wrong request? OK. Nonsensical requests do happen. It's impossible to tell from that message, but I will have a look. We've seen how Eglot does this request perfectly for other kinds of projects with this server and, what's more, for literally tens of other servers.

@minikN
Copy link
Author

minikN commented Jan 6, 2024

Let's get one thing straight. Eglot does not have Javascript code, it's written in Elisp.

I'm aware of that, there is no need to point that out. I'm just trying to fix this bug.

Wherever this comes from, whatever this is, this tells me there's something to fix on the server side.

Yes and no. An exception has been thrown because something unexpected happened. This "unexcepted" thing could be something internal, could be something coming from eglot. Should the server react differently and not just error out an throw an exception? Sure it should, they should probably fix that. But that doesn't mean that it's impossible the client caused this.

Wrong request? OK. Nonsensical requests do happen. It's impossible to tell from that message, but I will have a look. We've seen how Eglot does this request perfectly for other kinds of projects with this server and, what's more, for literally tens of other servers.

You come across quite hostile and I don't really know why. I didn't say "It's eglot's fault". The maintainer of the LSP server did. Whether that is true or not, I don't know. I'm just forwarding to you what they told me. Nothing more.

@joaotavora
Copy link
Owner

I tried your tsx file and the server replies with no diagnostics at all. And no code actions at all. Probably just a file is not enough.

I could also confirm the incorrect request on behalf of Emacs. But was it Eglot who was reponsible? I can only judge by what I can experiment with and in a much simpler setup like the one I gave you earlier with thingy.mjs (and which you confirmed worked), this is Eglot's full request:


[jsonrpc] e[16:44:28.737] --> textDocument/codeAction[31] 
(:jsonrpc "2.0" :id 31 :method "textDocument/codeAction" :params
          (:textDocument
           (:uri "file:///home/capitaomorte/tmp/thingy.mjs") :range
           (:start (:line 0 :character 0) :end (:line 0 :character 40))
           :context
           (:diagnostics
            [(:range
              (:start (:line 0 :character 0) :end
                      (:line 0 :character 40))
              :message
              "'defaultExport' is declared but its value is never read."
              :severity 4 :code 6133 :source "typescript" :tags [1])]
            :only ["source.organizeImports"])))

As you can see only the diagnostic that is overlapping the range for which code actions are requested is included in the request.

I suggest you try this on your end with the thingy.mjs file and report back the same section of the events log (perhaps in another format, depending on your setting of eglot-events-buffer-config -- any format will do)

You come across quite hostile and I don't really know why. I didn't say "It's eglot's fault". The maintainer of the LSP server did. > Whether that is true or not, I don't know. I'm just forwarding to you what they told me. Nothing more.

I wrote "Wrong request? OK. Nonsensical requests do happen. It's impossible to tell from that message, but I will have a look."

This is what I'm doing, at 11 PM in the evening. If that is hostile to you, I don't know what to say.

@joaotavora
Copy link
Owner

joaotavora commented Jan 7, 2024

I've put on my JS kid hat and done this recipe exactly

npx create-react-app my-app --template typescript
cd my-app
path/to/emacs -Q src/App.tsx -f tsx-ts-mode -f eglot
;;; delete use line that uses the "logo" import, whatever that does
;;; Go back to start of buffer
M-x eglot-code-action-organize-imports

Everything works as expected (I suppose, since organizing imports just changed the order of two imports on lines 2 and 3).

Then check the events log:

[jsonrpc] e[17:59:22.715] --> textDocument/codeAction[11] {"jsonrpc":"2.0","id":11,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"file:///home/capitaomorte/Source/Javascript/my-app/src/App.tsx"},"range":{"start":{"line":0,"character":0},"end":{"line":0,"character":26}},"context":{"diagnostics":[{"range":{"start":{"line":0,"character":0},"end":{"line":0,"character":26}},"message":"'React' is declared but its value is never read.","severity":4,"code":6133,"source":"typescript","tags":[1]}],"only":["source.organizeImports"]}}}
[jsonrpc] e[17:59:22.742]   <-- textDocument/codeAction[11] {"jsonrpc":"2.0","id":11,"result":[{"title":"Organize Imports","edit":{"documentChanges":[{"textDocument":{"uri":"file:///home/capitaomorte/Source/Javascript/my-app/src/App.tsx","version":0},"edits":[{"range":{"start":{"line":0,"character":0},"end":{"line":1,"character":0}},"newText":"import React from 'react';\nimport './App.css';\nimport logo from './logo.svg';\n"},{"range":{"start":{"line":1,"character":0},"end":{"line":2,"character":0}},"newText":""},{"range":{"start":{"line":2,"character":0},"end":{"line":3,"character":0}},"newText":""}]}]},"kind":"source.organizeImports.ts"}]}

Everything looks OK. I.e. no extra diagnostcs reported.

jollaitbot pushed a commit to sailfishos-mirror/emacs that referenced this issue Jan 7, 2024
Invoking code actions without a marked region or over a symbol
will trip certain servers up since BEG and END in eglot-code-actions
will be nil, causing 'eglot--pos-to-lsp-position' to assume point (which
is OK) but the 'flymake-diagnostics' call to return all diagnostics.

This causes an absolutely undecipherable JavaScript backtrace to be
sent back to Eglot from typescript-language-server.

Github-reference: joaotavora/eglot#847

* lisp/progmodes/eglot.el (eglot--code-action-bounds): Avoid returning
  (list nil nil)
@joaotavora
Copy link
Owner

HOWEVER...

If we do a little innocent tweak to that recipe:

npx create-react-app my-app --template typescript
cd my-app
path/to/emacs -Q src/App.tsx -f tsx-ts-mode -f eglot
;;; delete use line that uses the "logo" import, whatever that does
;;; GO TO AN EMPTY LINE (like the fourth one)
M-x eglot-code-action-organize-imports

Then everything still works but now the events log shows this

[jsonrpc] e[18:04:40.996] --> textDocument/codeAction[24] {"jsonrpc":"2.0","id":24,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"file:///home/capitaomorte/Source/Javascript/my-app/src/App.tsx"},"range":{"start":{"line":3,"character":0},"end":{"line":3,"character":0}},"context":{"diagnostics":[{"range":{"start":{"line":0,"character":0},"end":{"line":0,"character":26}},"message":"'React' is declared but its value is never read.","severity":4,"code":6133,"source":"typescript","tags":[1]},{"range":{"start":{"line":2,"character":0},"end":{"line":2,"character":30}},"message":"'logo' is declared but its value is never read.","severity":4,"code":6133,"source":"typescript","tags":[1]}],"only":["source.organizeImports"]}}}
[jsonrpc] e[18:04:41.001]   <-- textDocument/codeAction[24] {"jsonrpc":"2.0","id":24,"result":[{"title":"Organize Imports","edit":{"documentChanges":[{"textDocument":{"uri":"file:///home/capitaomorte/Source/Javascript/my-app/src/App.tsx","version":3},"edits":[{"range":{"start":{"line":0,"character":0},"end":{"line":1,"character":0}},"newText":"import React from 'react';\nimport './App.css';\nimport logo from './logo.svg';\n"},{"range":{"start":{"line":1,"character":0},"end":{"line":2,"character":0}},"newText":""},{"range":{"start":{"line":2,"character":0},"end":{"line":3,"character":0}},"newText":""}]}]},"kind":"source.organizeImports.ts"}]}

And if you bother reading through that first line, you'll see that Eglot is reporting too many diagnostics.

Here, it didn't trip up the server to that undecipherable JS crash, but I suppose it may.

This is very easy to fix so I've pushed a fix.

As before, the bleeding edge Eglot should fix this, until then, you should make sure to invoke eglot-code-actions-organize-imports on either a marked region, a diagnostic, or at least one non empty symbolic expression.

Now to address some earlier words.

I never said this couldn't be a problem in Eglot. I wrote I doubt it.

. I don't think it has anything to do with Eglot. Unless Eglot somehow provided the server with bad data before, which I doubt. I suggest you report this error to the server devs!

And I had good reasons to doubt it, given all the examples I had seen before and the undecipherable nature of a JS backtrace.

But that doesn't mean that it's impossible the client caused this.

Never said anything to the contrary either. Hardly anything is impossible in this world. Legend goes the term bug was coined after an actual insect.

Here, the combination of:

  1. missing complete (and I do mean complete, like the one I baked myself) error recipe
  2. conflation of a two new errors with something completely different reported nearly 2 years ago
  3. exceptionally picky -- though no doubt correctly behaving -- server. note the server doesn't always error
  4. exceptionally hard to decipher error message when it does error
  5. me not knowing anything about react or tsx

...led to a very hard investigation. But hopefully to a fix.

@minikN
Copy link
Author

minikN commented Jan 7, 2024

Hello,

thank you for taking the time and fixing this issue. I will get latest Emacs and test your changes.

Now to address some earlier words.

Regarding our conversation as a whole, I apologize for the turn it took. Communication via chat can sometimes be misleading. I understand this was a frustrating issue to debug on your end, especially since you normally don't work in JS. I was expressing the impression I got from our conversation, I never meant to allege that you were actually being hostile or that you said it couldn't be a fault on eglot's side.

Given how hard it was for you to finally come up with a recipe that is reproducible, I now know that I should have taken more time to investigate this myself and come up with that recipe for you since I have a background in JS. If we ever end up in the same situation I will try to do so.


One more thing, and maybe a new issue is the right place for this, I don't know. Something the maintainer said caught my eye:

Note that the jsx files should use javascriptreact language ID in didOpen notification. Typescript has JSX (and TSX) specific logic in some cases so it needs to know the correct language ID for proper functioning.

I noticed that eglot is using javascript as the language id for both JS and JSX, since js-ts-mode covers both JS and JSX.

I fixed this on my end by defining a derived mode jsx-ts-mode from js-ts-mode and is only used for jsx files. I then added it to eglot-server-programs:

(add-to-list
   'eglot-server-programs
   '(((jsx-ts-mode :language-id "javascriptreact") ;; needs to come before js-ts-mode
      (js-ts-mode :language-id "javascript")
      (tsx-ts-mode :language-id "typescriptreact") ;; needs to come before typescrip-ts-mode
      (typescript-ts-mode :language-id "typescript")) . ("${pkgs.nodePackages.typescript-language-server}/bin/typescript-language-server" "--stdio"
      :initializationOptions
      (:tsserver (:path "${pkgs.nodePackages.typescript}/lib/node_modules/typescript/lib")))))

The only caveat is that it needs to come before js-ts-mode (and same for ts/tsx). I looked at eglot's code and if I understand it correctly, it will loop through the list of modes/lsps, and if the current mode is a derived mode from the current item looked at in the list, it will take its language-id. This is true if js-ts-mode comes before my own derived mode, so I had to change the order.

Long story short, is this something we should fix in eglot?

dickmao pushed a commit to commercial-emacs/commercial-emacs that referenced this issue Jan 7, 2024
Invoking code actions without a marked region or over a symbol
will trip certain servers up since BEG and END in eglot-code-actions
will be nil, causing 'eglot--pos-to-lsp-position' to assume point (which
is OK) but the 'flymake-diagnostics' call to return all diagnostics.

This causes an absolutely undecipherable JavaScript backtrace to be
sent back to Eglot from typescript-language-server.

Github-reference: joaotavora/eglot#847

* lisp/progmodes/eglot.el (eglot--code-action-bounds): Avoid returning
  (list nil nil)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants