Integrate reanalyze into the LSP so more editors can benefit out-of-the-box
Similar issue to: https://github.com/rescript-lang/rescript-vscode/issues/1108
My understanding from reading forums is that currently the excellent reanalyze tool is not integrated into the lsp, but is a separate tool that can either be invoked directly by the vscode plugin or from the rescript-tools cli.
If this could be integrated into the LSP, users like myself who don't use vscode could directly benefit from these tools without having to use vscode. I personally use Emacs, but lsp seems to be the best way to ensure cross-editor compatibility.
I could also work on parsing the output of the cli tool in the emacs mode but this could be a little cumbersome/error prone.
Happy to look into this myself is someone points me in the right direction.
This should definitely be done. At a glance it feels like it shouldn't be too hard. I'll have a quick look and get back with details soon.
has any progress been made on this? it's a huge missing piece of the puzzle for us non vsc users.
also open to pointers to start the work off
A small clarification + a concrete MVP path (starting from the compiler repo, per the ask):
Where reanalyze “lives” today (compiler repo):
reanalyzeis implemented in the ReScript compiler repo underanalysis/reanalyze/src/(OCaml).- It’s exposed via the existing
rescript-editor-analysisexecutable as a subcommand:rescript-editor-analysis reanalyze ...
- The
-jsonflag already produces a machine-friendly JSON array including file + 0-based range + severity + message (very close to LSP diagnostics).
How it’s shipped (so non-VSCode editors can use it):
rescript-editor-analysis.exeis already bundled in therescriptnpm install via@rescript/<platform>/bin/rescript-editor-analysis.exe.- So we don’t need a separate “reanalyze binary” distribution for the LSP. The LSP can reuse what the project already has installed.
Where the LSP lives / what’s missing:
- The editor-agnostic LSP is
@rescript/language-server(this repo’sserver/folder). - The LSP already has infra to locate and run
rescript-editor-analysisfor hover/defs/etc (it spawns the binary and parses JSON results). - What’s missing is an LSP entry point to trigger
reanalyze, plus “diagnostics merging” so reanalyze diagnostics don’t get overwritten by compiler/syntax publishes.
Minimal MVP (editor-agnostic):
- Add an LSP command via standard
workspace/executeCommand, e.g.:- command id:
rescript.reanalyze.run - args: optionally
{ projectRootUri }(or infer from active document/workspace folder)
- command id:
- Handler implementation:
- spawn:
rescript-editor-analysis reanalyze -config -json(cwd = project root) - parse JSON output items → map to
textDocument/publishDiagnostics:- severity: warning/error
- range: provided by reanalyze (already 0-based)
- message: provided
- source:
"reanalyze"
- spawn:
- Store per-file reanalyze diagnostics and merge them with existing compiler/syntax diagnostics before publishing, so they persist until the next reanalyze run (and can be cleared by re-running or by a
rescript.reanalyze.clearcommand).
That would make this usable from Emacs/Neovim immediately (any LSP client can call workspace/executeCommand), without adding VS Code–specific glue and without redesigning reanalyze itself.
Follow-on with concrete file/function touch points inside @rescript/language-server (this repo’s server/ dir), based on how it already spawns rescript-editor-analysis today:
Where to implement the new LSP command
1) Advertise the command in initialize capabilities
File: server/src/server.ts
- In the
initializehandler, add:executeCommandProvider: { commands: ["rescript.reanalyze.run", "rescript.reanalyze.clear"] }
This is the standard LSP way for clients (Emacs/Neovim/etc) to discover server commands.
2) Actually handle workspace/executeCommand
File: server/src/server.ts
- In the request dispatch inside
onMessage(the bigelse if (p.Message.isRequest(msg)) { ... }chain), add a branch for:p.ExecuteCommandRequest.method(workspace/executeCommand)
Implementation sketch:
- Parse params as
p.ExecuteCommandParams:commandstring- optional
arguments
- Support:
rescript.reanalyze.run→ run reanalyze and publish diagnosticsrescript.reanalyze.clear→ clear stored reanalyze diags and republish merged diags (compiler/syntax only)
How to run reanalyze (reuse existing binary discovery + exec infra)
File: server/src/utils.ts
- There is already
runAnalysisAfterSanityCheck(filePath, args, ...)which:- picks the right
rescript-editor-analysisbinary (project.editorAnalysisLocationvs legacybuiltinBinaryPath) - runs
childProcess.execFileSync(binaryPath, args, { cwd: projectRoot, env: ... }) - JSON-parses stdout
- picks the right
- However
reanalyzeis project-wide (not “per file”), so it’s better to add a dedicated helper that:- chooses
projectRootPath(from arg or active doc) - resolves
binaryPaththe same wayrunAnalysisAfterSanityCheckdoes - runs:
execFileSync(binaryPath, ["reanalyze", "-config", "-json"], { cwd: projectRootPath, ...env }) - parses the JSON array (reanalyze’s
-jsonoutput already containsfile+range+kind+message+name)
- chooses
I’d put this in utils.ts as e.g. runReanalyzeForProject(projectRootPath): Promise<ReanalyzeItem[]>.
Where to store and merge diagnostics (so they don’t get overwritten)
Key point: LSP textDocument/publishDiagnostics is per file and replaces the entire diagnostic set for that URI. Today the server publishes:
- compiler diagnostics in
sendUpdatedDiagnostics()(reads.compiler.log, publishes for many files) - syntax diagnostics for open files in
updateDiagnosticSyntax()(merges syntax + compiler for that file)
So if we publish reanalyze-only diags once, the next compiler-log publish will overwrite them unless we merge them.
3) Add storage for reanalyze diagnostics per project
File: server/src/projectFiles.ts
- Extend
projectFileswith:reanalyzeDiagnostics: filesDiagnostics(keyed by file URI)
4) Merge reanalyze diags in BOTH publishing paths
File: server/src/server.ts
A) In sendUpdatedDiagnostics():
- Today it sets
projectFile.filesDiagnostics = filesAndErrorsand then publishesfilesAndErrors[fileUri]. - Change it to publish:
merged = (filesAndErrors[fileUri] ?? []) + (projectFile.reanalyzeDiagnostics[fileUri] ?? [])
B) In updateDiagnosticSyntax(fileUri, fileContent):
- Today it merges:
syntaxDiagnosticsForFile + compilerDiagnosticsForFile - Change it to also add:
reanalyzeDiagnosticsForFilefrom the current project’sreanalyzeDiagnostics[fileUri].
Mapping details (reanalyze JSON → LSP Diagnostic)
item.kind("warning"/"error") →Diagnostic.severity(Warning/Error)item.range[sl, sc, el, ec] →Diagnostic.rangeitem.message→Diagnostic.messageitem.name→Diagnostic.code(optional but useful)Diagnostic.source = "reanalyze"