Skip to content

DiagnosticTag.Unnecessary diagnostics not persisted #41935

@smpanaro

Description

@smpanaro

Summary

Diagnostics with DiagnosticTag.Unnecessary are not persisted across closing and reopening a file if a different buffer is viewed in between.

Description

Steps to reproduce:

  1. Install the FlatBuffers extension.
  2. Create and save a FlatBuffers file foo.fbs:
table Foo {
    f: int (deprecated);
}
  1. Observe a "Deprecated" diagnostic on the second line.
  2. Close foo.fbs.
  3. Open a different buffer (e.g. Zed log).
  4. Reopen foo.fbs.

Expected Behavior: The "Deprecated" diagnostic should still be present despite the language server not republishing the unchanged diagnostics. This behavior would be consistent with how Warning diagnostics are persisted (e.g. if you delete the semicolon, close, and reopen).
Actual Behavior: It is not present until the file's diagnostics change and are republished.

video example
unnecessary-diagnostic-compress.mp4
language server logs from the above video

The main methods of interest are the two textDocument/publishDiagnostics. They never pass an empty diagnostics array.

// Send:
{"jsonrpc":"2.0","id":3,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"file:///Users/stephen/Downloads/foo/foo.fbs"},"range":{"start":{"line":3,"character":0},"end":{"line":3,"character":0}},"context":{"diagnostics":[],"only":["quickfix"]}}}

// Receive:
{"jsonrpc":"2.0","result":[],"id":3}

// Send:
{"jsonrpc":"2.0","method":"textDocument/didClose","params":{"textDocument":{"uri":"file:///Users/stephen/Downloads/foo/foo.fbs"}}}

// Receive:
{"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"[flatbuffers_language_server::handlers::lifecycle] closed: /Users/stephen/Downloads/foo/foo.fbs","type":4}}

// Send:
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///Users/stephen/Downloads/foo/foo.fbs","languageId":"flatbuffers","version":0,"text":"table Foo {\n    f: int (deprecated);\n}\n"}}}

// Receive:
{"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"[flatbuffers_language_server::handlers::lifecycle] opened: /Users/stephen/Downloads/foo/foo.fbs","type":4}}

// Receive:
{"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"[flatbuffers_language_server::workspace] parsing: /Users/stephen/Downloads/foo/foo.fbs","type":3}}

// Send:
{"jsonrpc":"2.0","id":4,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"file:///Users/stephen/Downloads/foo/foo.fbs"},"range":{"start":{"line":3,"character":0},"end":{"line":3,"character":0}},"context":{"diagnostics":[],"only":["quickfix"]}}}

// Receive:
{"jsonrpc":"2.0","result":[],"id":4}

// Send:
{"jsonrpc":"2.0","id":5,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"file:///Users/stephen/Downloads/foo/foo.fbs"},"range":{"start":{"line":1,"character":23},"end":{"line":1,"character":23}},"context":{"diagnostics":[],"only":["quickfix"]}}}

// Receive:
{"jsonrpc":"2.0","result":[],"id":5}

// Send:
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///Users/stephen/Downloads/foo/foo.fbs","version":1},"contentChanges":[{"text":"table Foo {\n    f: int (deprecated)\n}\n"}]}}

// Receive:
{"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"[flatbuffers_language_server::handlers::lifecycle] changed: /Users/stephen/Downloads/foo/foo.fbs","type":4}}

// Receive:
{"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"[flatbuffers_language_server::workspace] parsing: /Users/stephen/Downloads/foo/foo.fbs","type":3}}

// Receive:
{"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"[flatbuffers_language_server::parser] flatc error parsing /Users/stephen/Downloads/foo/foo.fbs: /Users/stephen/Downloads/foo/foo.fbs:3: 1: error: expecting: ; instead got: }","type":4}}

// Receive:
{"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"diagnostics":[{"code":"expecting-token","data":{"eol":true,"expected":";"},"message":"expected `;`, found `}`","range":{"end":{"character":24,"line":1},"start":{"character":23,"line":1}},"relatedInformation":[{"location":{"range":{"end":{"character":1,"line":2},"start":{"character":0,"line":2}},"uri":"file:///Users/stephen/Downloads/foo/foo.fbs"},"message":"unexpected token"},{"location":{"range":{"end":{"character":24,"line":1},"start":{"character":23,"line":1}},"uri":"file:///Users/stephen/Downloads/foo/foo.fbs"},"message":"add `;` here"}],"severity":1}],"uri":"file:///Users/stephen/Downloads/foo/foo.fbs"}}

// Send:
{"jsonrpc":"2.0","id":6,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"file:///Users/stephen/Downloads/foo/foo.fbs"},"range":{"start":{"line":1,"character":22},"end":{"line":1,"character":22}},"context":{"diagnostics":[{"range":{"start":{"line":1,"character":22},"end":{"line":1,"character":23}},"severity":1,"code":"expecting-token","message":"expected `;`, found `}`","data":{"eol":true,"expected":";"}},{"range":{"start":{"line":1,"character":22},"end":{"line":1,"character":23}},"severity":3,"code":"expecting-token","message":"add `;` here","data":{"eol":true,"expected":";"}}],"only":["quickfix"]}}}

// Receive:
{"jsonrpc":"2.0","result":[{"diagnostics":[{"code":"expecting-token","data":{"eol":true,"expected":";"},"message":"expected `;`, found `}`","range":{"end":{"character":23,"line":1},"start":{"character":22,"line":1}},"severity":1}],"edit":{"changes":{"file:///Users/stephen/Downloads/foo/foo.fbs":[{"newText":";","range":{"end":{"character":23,"line":1},"start":{"character":23,"line":1}}}]}},"kind":"quickfix","title":"Add missing `;`"}],"id":6}

// Send:
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///Users/stephen/Downloads/foo/foo.fbs","version":2},"contentChanges":[{"text":"table Foo {\n    f: int (deprecated);\n}\n"}]}}

// Receive:
{"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"[flatbuffers_language_server::handlers::lifecycle] changed: /Users/stephen/Downloads/foo/foo.fbs","type":4}}

// Receive:
{"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"[flatbuffers_language_server::workspace] parsing: /Users/stephen/Downloads/foo/foo.fbs","type":3}}

// Receive:
{"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"diagnostics":[{"message":"Deprecated. Excluded from generated code.","range":{"end":{"character":4294967295,"line":1},"start":{"character":4,"line":1}},"severity":4,"tags":[1]}],"uri":"file:///Users/stephen/Downloads/foo/foo.fbs"}}

// Send:
{"jsonrpc":"2.0","id":7,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"file:///Users/stephen/Downloads/foo/foo.fbs"},"range":{"start":{"line":1,"character":24},"end":{"line":1,"character":24}},"context":{"diagnostics":[{"range":{"start":{"line":1,"character":4},"end":{"line":1,"character":24}},"severity":4,"message":"Deprecated. Excluded from generated code."}],"only":["quickfix"]}}}

// Receive:
{"jsonrpc":"2.0","result":[],"id":7}

// Send:
{"jsonrpc":"2.0","method":"workspace/didChangeWatchedFiles","params":{"changes":[{"uri":"file:///Users/stephen/Downloads/foo/foo.fbs","type":2}]}}

// Receive:
{"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"[flatbuffers_language_server::workspace] parsing: /Users/stephen/Downloads/foo/foo.fbs","type":3}}

// Send:
{"jsonrpc":"2.0","method":"textDocument/didSave","params":{"textDocument":{"uri":"file:///Users/stephen/Downloads/foo/foo.fbs"}}}

// Receive:
{"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"[flatbuffers_language_server::handlers::lifecycle] saved: /Users/stephen/Downloads/foo/foo.fbs","type":4}}

// Receive:
{"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"[flatbuffers_language_server::workspace] parsing: /Users/stephen/Downloads/foo/foo.fbs","type":3}}

// Send:
{"jsonrpc":"2.0","method":"textDocument/didClose","params":{"textDocument":{"uri":"file:///Users/stephen/Downloads/foo/foo.fbs"}}}

// Receive:
{"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"[flatbuffers_language_server::handlers::lifecycle] closed: /Users/stephen/Downloads/foo/foo.fbs","type":4}}

// Send:
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///Users/stephen/Downloads/foo/foo.fbs","languageId":"flatbuffers","version":0,"text":"table Foo {\n    f: int (deprecated);\n}\n"}}}

// Receive:
{"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"[flatbuffers_language_server::handlers::lifecycle] opened: /Users/stephen/Downloads/foo/foo.fbs","type":4}}

// Receive:
{"jsonrpc":"2.0","method":"window/logMessage","params":{"message":"[flatbuffers_language_server::workspace] parsing: /Users/stephen/Downloads/foo/foo.fbs","type":3}}

// Send:
{"jsonrpc":"2.0","id":8,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"file:///Users/stephen/Downloads/foo/foo.fbs"},"range":{"start":{"line":1,"character":24},"end":{"line":1,"character":24}},"context":{"diagnostics":[],"only":["quickfix"]}}}

// Receive:
{"jsonrpc":"2.0","result":[],"id":8}

I maintain the FlatBuffers extension/LS, but I am reporting it since I think this behavior might affect other extensions. My interpretation of the following section of the LSP spec is that the client should cache diagnostics until the server clears them:

When a file changes it is the server’s responsibility to re-compute diagnostics and push them to the client. If the computed set is empty it has to push the empty array to clear former diagnostics.

I tried to reproduce this with other extensions (lua, basedpyright) but they either use pull diagnostics or republish diagnostics on textDocument/didOpen which masks the problem.

VSCode does persist unnecessary diagnostics across file closure.

Zed Version and System Specs

Zed: v0.211.3 (Zed Preview)
OS: macOS 14.7.1
Memory: 64 GiB
Architecture: aarch64

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions