Experimental ReScript Language Server in OCaml#8425
Draft
aspeddro wants to merge 139 commits into
Draft
Conversation
- Move Io, Chan, and listen infrastructure to server.ml - Simplify on_request to take a packed request directly - Add basic hover response with markdown content - Rename public executable to rescript-language-server
- Add `parse_implementation_from_source` to parsing/print engine types and all engine implementations, enabling parsing from a string source rather than a filename - Use `parse_implementation_from_source` in CompletionFrontEnd - Rename package from `rescript-lsp` to `rescript-language-server` - Refactor LSP server with typed state, document store, and diagnostics - Add hover support via completion backend integration
Move LSP modules under lsp/src/ with a thin lsp/bin/ entry point, add a configuration module, and introduce a tests/lsp_tests workspace exercising hover end-to-end.
Splits `Commands.ml` into a pure layer that returns OCaml values (option, list, typed records like Protocol.hover, Protocol.signatureHelp, Protocol.completionItem) and a new `analysis/src/Cli.ml` that does the stringify-and-print step. `analysis/bin/main.ml` now dispatches to `Cli.*`, while the LSP server consumes `Commands.*` directly. Makes the parsers accept source strings: `res_driver` gains `parse_interface_from_source` alongside the existing `parse_implementation_from_source`
Welcome to Codecov 🎉Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests. Thanks for integrating Codecov - We've got you covered ☂️ |
8 tasks
- Rename Helper module to Client - Move expected output files into workspace directories - Improve request log format to show file path:line:col - Simplify hover.ml by removing intermediate result binding - Disable in-source compilation for basic-workspace
`file://name.res#LX,Y` Works in VSCode and Zed!!
Merge all diagnostic sources when serving requests and serializing state. Ignore partial compiler logs until #Done, then publish the completed snapshot and refresh pull diagnostics so clients clear resolved warnings without flickering.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This branch introduces a standalone ReScript LSP server
(
rescript lspsubcommand) built on top of the existinganalysislibrary.It's a separate, OCaml-side exploration alongside the Rust/rewatch-based
experiment in #8243, the two share the same goal (a LSP server for ReScript) but
approach it from different ends of the toolchain.
Why rewriting the server in OCaml is a good fit
The editor features are already implemented in the OCaml analysis library.
Hover, completion, references, rename, document symbols, code actions, and
diagnostics all depend on compiler data structures such as
.cmt/.cmti,typed trees, locations, package metadata, and compiler diagnostics. Running the
LSP server in OCaml lets the server call those APIs directly instead of shelling
out to
rescript-editor-analysis.exeor passing JSON through stdin. Thatremoves a large amount of process orchestration and serialization glue, and
keeps the LSP closer to the compiler types it needs to understand. This also
makes the server easier to evolve with the compiler. When typedtree, diagnostic
formats, package discovery, or
.cmthandling changes, the LSP can be updatedin the same language and build system as the analysis code. That reduces version
skew and makes bugs easier to reproduce with Dune/expect tests.
Why ship the language server with the compiler
The language server is tightly coupled to the compiler version used by a
project. It reads
.cmtand.cmtifiles containing marshalled compilerinternals such as
Typedtree,Types, andEnv. These files are not a stable,schema-aware protocol: changing one of those OCaml types can make an analysis
binary incompatible with artifacts produced by another compiler revision.
This is not only a theoretical compatibility concern. In
#8475, adding typedtree
constructors for
for...ofwithout changing the CMT magic number allowed anewer analysis binary to read incompatible ReScript 12 artifacts and
segmentation fault. The same class of failure appeared after record-rest
changes altered compiler data stored in CMT files. Bumping the CMT magic number
makes these mismatches rejectable, but the project must still be rebuilt with
the matching compiler.
Shipping the server in the same platform package as the compiler, and exposing
it through
rescript lsp --stdio, gives clients one project-local entrypoint.The selected ReScript dependency then supplies
bsc, the analysis code, and thelanguage server from the same release. This has several practical benefits:
rescript-language-server.exepath remains a packaging detail.Bundling does not make stale artifacts safe. Compiler changes that alter
marshalled CMT data must still bump the magic number, and the server should turn
version mismatches into an actionable request to rebuild. A separately
versioned server becomes practical only if analysis moves to a stable,
explicitly versioned artifact format or maintains decoders for multiple
compiler-internal formats. Until then, promising one server binary that supports
multiple compiler versions would hide a compatibility boundary that already
exists.
What's here
lsp/package split intolsp/bin(entrypoint) andlsp/src(library),with its own opam file (
rescript-language-server.opam). Depends onlsp(>=1.22.0),
eio/eio_main,ppx_deriving_yojson,ppx_expectand the in-treeanalysislibrary.rescript lsp. The language server is shipped with thecompiler.
Status / what's not here yet
The main objective is to first maintain feature parity with the current server
(server.ts) as much as possible. Below is a list of some requests and
notifications. Some are out of scope because they don't make sense.
initialize- client requestinitialized- client notificationshutdown- client requestexit- client notificationtextDocument/didOpen- client notificationtextDocument/didChange- client notificationtextDocument/didClose- client notificationtextDocument/didSave- client notification - It will not beimplemented for now.
textDocument/hover- client requesttextDocument/publishDiagnostics- server notificationdependency errors be published?
does not point at a precise source range. When the document is open, we
expand the diagnostic range to cover the whole document so the editor can
display a file-level diagnostic. If the document is not open, we keep the
range parsed from the compiler log, i.e.,
{start: {line: 0, character: 0}, end: {line: 0, character: 6}}let a=1.Protocol positions are zero-based, so the position is
length - 1.Analysisfor syntax errors and ignore them in the compiler logparser.
TextDocumentDidChangeandprovide instant feedback.
.compiler.log- server featurecompiler_log.ml. Seetests/build_testsfor more examples.
.sourcedirs.json- server feature.sourcedirs.jsonis alwaysgenerated with
build_rootfield for each subpackage.workspace/didChangeWatchedFiles- client notification.compiler.logfiles, which usually means a ReScript build has finished.When a watched log changes, the server re-reads every known compiler log and
republishes diagnostics so stale errors are cleared and monorepo diagnostics
stay in sync.
GlobPatternwithbaseUri(workspace root)?rescript.jsonfiles in the workspace?rescript.json. For example,suffixandpackage-specsare needed to create the code actionOpen compiled JS file. Changes independenciesimpact variousfunctionalities because they modify the state in
Analysis.Shared_types.state.client/registerCapability- server requestworkspace/didChangeWatchedFilesis commonly registereddynamically. The server does not know all file-watch patterns during the
static
initializeresponse. In this LSP, the watcher list depends onproject state, especially
.sourcedirs.json, which tells us where eachReScript build root lives. In monorepos, that means the server needs to
register watchers for compiler logs after initialization, once it has
workspace context. That means watching generated
.compiler.logfiles. Whenthe client sees one change, it sends
workspace/didChangeWatchedFiles, andthe server refreshes diagnostics.
textDocument/diagnostic- client requesttextDocument/completion- client requestcompletionItem/resolve- client requesttextDocument/signatureHelp- client requesttextDocument/definition- client requesttextDocument/declaration- client request - It will not be implementedfor now.
textDocument/typeDefinition- client requesttextDocument/implementation- client request - It will not beimplemented for now.
textDocument/references- client requestSupport v12 CMT files in analysis #8477
textDocument/documentHighlight- client request - It will not beimplemented for now.
textDocument/documentSymbol- client request"document_symbols": "on"configuration. When enabled,tree-sitter is not used for document symbols.
workspace/symbol- client request - It will not be implemented fornow.
textDocument/codeAction- client requestrescript/openCompiled)window/showDocument.rescript/createInterface)rescript/switchImplementationInterface)window/showDocumentrequest.codeAction/resolve- client request - It will not be implemented fornow.
textDocument/codeLens- client requestworkspace/codeLens/refresh- server requestcodeLens/resolve- client request - It will not be implemented fornow.
textDocument/inlayHint- client requestinlayHint/resolve- client request - It will not be implemented fornow.
textDocument/semanticTokens/full- client requesttextDocument/semanticTokens/full/delta- client request - It will notbe implemented for now.
textDocument/semanticTokens/range- client request - It will not beimplemented for now.
textDocument/rename- client requesttextDocument/prepareRename- client requesttextDocument/formatting- client requesttextDocument/rangeFormatting- client request - It will not beimplemented for now.
textDocument/onTypeFormatting- client request - It will not beimplemented for now.
textDocument/foldingRange- client request - It will not beimplemented for now.
textDocument/selectionRange- client request - It will not beimplemented for now.
textDocument/documentLink- client request - It will not beimplemented for now.
documentLink/resolve- client request - It will not be implemented fornow.
textDocument/documentColor- client request - It will not beimplemented for now.
textDocument/linkedEditingRange- client request - It will not beimplemented for now.
workspace/didChangeConfiguration- client notificationworkspace/configuration- server requestworkspace/executeCommand- client requestrescript/dumpServerStateState.t(diagnostics, document store, status, configuration,analysis state (
Analysis.Shared_types.state) and compiler config).rescript/openCompiled: Triggered by a code actionwindow/showDocument.rescript/createInterface: Triggered by a code actioninterface file if the client supports the
window/showDocumentrequest.rescript/switchImplementationInterface: Triggered by a code actionwindow/showDocumentrequest.rescript/dumpCmt: Dump cmt file contentrescript/dumpParseTreeIt will not be implemented for now.rescript/dumpTypedTreeIt will not be implemented for now.window/showDocumentrequest.LSP: support windows/showDocument zed-industries/zed#58099.
textDocument/openCompiled- client requesttextDocument/createInterface- client requesttextDocument/switchImplementationInterface: Currently it's done on theclient side, but it could be a command triggered by a code action.
rescript/startBuild- client request - How the build integrationwill be done. See last point. It will not be implemented for now
initialization and drop this custom request?
rescript/stopBuild?rescript/compilationFinished- server notification - Sent from theserver to the client when compilation is finished
server.ts) sends this notification, and the VSCode clientuses it to run Code Analysis. The server sends it when
.compiler.logchanges. Only VSCode uses this notification.
reanalyze may be integrated into the server instead of running on the
client side.
$/progressserver notification?rescript/compilationStatus- server notification - Sends thecompilation status from the server to the client.
bar. Only VSCode uses this notification.
$/progressserver notification?server.ts) supports stdio and node-ipc. The VSCodeclient uses node-ipc. This implementation supports only stdio.
not be implemented for now.
produced by an existing
rescript build, rather than the LSP starting ormanaging builds itself.
for now. Idea to explore.
rescript watchprocess, it's much harder to compilein memory?
Tests
tests/lsp_tests/adds a dune-driven integration test (test.ml) that bootsthe server against a real ReScript workspace (
basic-workspace/), initializesthe LSP session, sends the
initializednotification, opens source documents,and snapshots responses for representative fixtures.
The LSP test target now also runs
dune runtest, covering inline tests such as.sourcedirs.jsonbuild-root,.compiler.logparsing used by diagnostics andfile-watcher setup.
Breaking Changes
initializationOptions. This server ignoressettings coming from
initializationOptions. SeeSpec should discourage abuse of initializationOptions and didChangeConfiguration microsoft/language-server-protocol#567 (comment).
We use
workspace/configuration.Server settings
Proposed interface.
supportMarkdownLinksis not a setting. It's a greatfeature, but some clients don't have good support; Neovim are a example.
Therefore, I'm promoting it to a setting so users can enable or disable it.
Release and transition plan
Other related topics
Refactor analysis for use on the server side
#8426
#8466
#8478
yojsonandlsplibraries in the analysis library##8436
Shared_types.state#8465