Diagnostics use a stale parse_result when the request is read before its didChange is applied
维护者通常 1 天内回复
评估
调研方向
Start with BaseServer#start, the worker's push_edits path, and the diagnostic request handling; compare when needs_parsing is set with when document.parse_result is refreshed. Check the RuboCop add-on's RuntimeAdapter#prism_result and Requests::Diagnostics for both reported effects. Done means diagnostics and syntax warnings use a parse result matching the updated document source in the reproduction.
由索引模型根据 Issue 内容生成。
描述
Ruby LSP Information
- ruby-lsp 0.26.11 (code in question is unchanged on
main) - rubocop 1.87.0 (also checked 1.88.0
RuntimeAdapter#prism_result, unchanged), prism 1.9.0 - Ruby 4.0.6 via rbenv, Linux
- Neovim 0.11.6, built-in LSP client (pull diagnostics)
initializationOptions: { linters: ["rubocop"] }(RuboCop's own add-on)
Description
Diagnostics are intermittently computed with a parse_result from the previous document version while document.source is already the new version. With RuboCop's add-on, which reuses document.parse_result as prism_result, this produces wrong offenses that stay until the next edit. Syntax warnings from Requests::Diagnostics read the same stale tree.
Order of events, as I read it:
- Client sends
didChangeand immediatelytextDocument/diagnostic(Neovim pulls diagnostics on everydidChange). - The main thread (
BaseServer#start) only callsdocument.parse!for non-did*messages and queuesdidChangewithout applying it. When the diagnostic request arrives, the edit isn't applied yet,needs_parsingisfalse, so nothing is parsed. - The worker applies the edits (
push_editssetsneeds_parsing = true), then handles the diagnostic request without parsing.
Server-side log from a stale run (a small workspace add-on that logs at enqueue, worker start and in RuntimeAdapter#run_diagnostic):
reader enqueue textDocument/didChange doc v0 needs_parsing=false tree=current
worker start textDocument/didChange doc v0 needs_parsing=false tree=current
reader enqueue textDocument/diagnostic doc v0 needs_parsing=false tree=current
worker push_edits v15 start
worker push_edits v15 done src="# frozen_string_literal: true\n\ndef foo\n y = \"abc\"\n x = 1\n x\nend\n"
worker start textDocument/diagnostic doc v15 needs_parsing=true tree=STALE
adapter run_diagnostic v15 source==parsed_source? false
source: "# frozen_string_literal: true\n\ndef foo\n y = \"abc\"\n x = 1\n x\nend\n"
parse_result: "# frozen_string_literal: true\n\ndef foo\n x = 1\n x\nend\n"
Resulting diagnostics vs. rubocop --stdin on the same text:
client: 4:3 Style/RedundantAssignment | 4:9 Layout/ExtraSpacing
rubocop: 4:3 Lint/UselessAssignment | 4:7 Style/StringLiterals | 5:3 Style/RedundantAssignment
In a later stale run the parser warning assigned but unused variable - y remained after y had been deleted, so this isn't specific to the RuboCop add-on.
Reproduction steps
Project: Gemfile with gem "rubocop", a .rubocop.yml with AllCops: { NewCops: enable }, and app.rb:
# frozen_string_literal: true
def foo
x = 1
x
end
Minimal Neovim config (nvim --clean -u init.lua app.rb):
vim.lsp.config("ruby_lsp", {
cmd = { "rbenv", "exec", "ruby-lsp" },
filetypes = { "ruby" },
root_markers = { "Gemfile" },
init_options = { linters = { "rubocop" } },
})
vim.lsp.enable("ruby_lsp")
- Open
app.rband wait for diagnostics. - Insert a new line
y = "abc"afterdef foo. I typed it vianvim_buf_set_textone character at a time, 40ms apart; the client sends it as a singledidChange. - See diagnostics that don't match the buffer.
Results (headless, 6 runs each):
| setting | stale runs |
|---|---|
linters: ["rubocop"] |
6/6 |
linters: ["rubocop_internal"] |
0/6 |
no linters (detects rubocop_internal) |
0/6 |
["rubocop"] in my full config, with a 300ms delay before sending the diagnostic request |
0/5 (vs 2/5 without the delay in that batch) |
rubocop_internal is unaffected because RuboCopRunner#run doesn't pass prism_result through (the assignment is commented out) and RuboCop parses contents itself.
Possible fix direction: call document.parse! in the worker before handling a request, or parse after push_edits, so a request never sees a tree older than its source. I haven't checked whether VS Code hits this; its pull timing may hide it.
- 主要语言
- Ruby
- 星标
- 2k
- 派生
- 286
- 平均合并
- 8 小时 37 分钟
- 30 天内合并 PR
- 12
环境准备
在浏览器里用你自己的 GitHub 账号启动这个项目的开发容器。
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
Shopify/ruby-lsp 的其他 Issue
-
Dependencies view: `getParent` loops forever on untitled documents, extension host runs out of memory可能已有人在做 @efegokdemir 于 13 天前认领。 未关闭bug
难度 2/5 1-3 小时 新手友好度 70/100
维护者通常 1 天内回复
-
Linux file system with square brackets in the directory/filename are failing to be indexed可能已有人在做 @andriytyurnikov 于 218 天前认领。 未关闭bug help-wanted pinned
难度 2/5 1-3 小时 新手友好度 68/100
Shopify/ruby-lsp#3503 · 8 条评论 · 1 个 reaction ·
维护者通常 1 天内回复
-
rbenv activation leaks RBENV_VERSION and RBENV_DIR into process.env of the extension host可能已有人在做 @efegokdemir 于 8 天前认领。 未关闭bug
难度 3/5 半天 新手友好度 45/100
维护者通常 1 天内回复
-
Rename of a class moves its file and leaves require_relative on the old path可能已有人在做 @efegokdemir 于 15 天前认领。 未关闭
难度 4/5 3-5 天 新手友好度 22/100
维护者通常 1 天内回复
-
Paths need to be escaped可能已有人在做 @Vraiment 于 18 天前认领。 未关闭bug
难度 2/5 1-3 小时 新手友好度 25/100
维护者通常 1 天内回复
相似的 Issue
-
[Bug] Chatwoot v4.19.0 – Super Admin returns 500 due to missing superadmin.scss Vite manifest entry未关闭Bug
难度 2/5 1-3 小时 新手友好度 76/100
维护者通常 2 天内回复
-
难度 1/5 1 小时以内 新手友好度 84/100
-
难度 2/5 1-3 小时 新手友好度 65/100
-
internal
难度 2/5 1-3 小时 新手友好度 70/100
Kong/developer.konghq.com#7716 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 67/100