Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

Diagnostics use a stale parse_result when the request is read before its didChange is applied

未关闭
#4,247 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

@efegokdemir 已经在做这个了。

开始于 2026年10月23日。

  • #4249 来自 @efegokdemir —— 未关闭

评估

难度
4/5
预计耗时
3-5 天
新手友好度
52/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
neovim, ruby
领域
backend, devtools

调研方向

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:

  1. Client sends didChange and immediately textDocument/diagnostic (Neovim pulls diagnostics on every didChange).
  2. The main thread (BaseServer#start) only calls document.parse! for non-did* messages and queues didChange without applying it. When the diagnostic request arrives, the edit isn't applied yet, needs_parsing is false, so nothing is parsed.
  3. The worker applies the edits (push_edits sets needs_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")
  1. Open app.rb and wait for diagnostics.
  2. Insert a new line y = "abc" after def foo. I typed it via nvim_buf_set_text one character at a time, 40ms apart; the client sends it as a single didChange.
  3. 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

环境准备

在 Codespaces 中打开

在浏览器里用你自己的 GitHub 账号启动这个项目的开发容器。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

Shopify/ruby-lsp 的其他 Issue

查看 Shopify/ruby-lsp 的全部 Issue

相似的 Issue

更多 Ruby Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。