You are currently looking at the v12 docs, which are still a work in progress. If you miss anything, you may find it in the older v11 docs here.
Editor
This section is about the editor plugin for ReScript. It adds syntax highlighting, autocomplete, type hints, formatting, code navigation, code analysis for .res
and .resi
files.
Plugins
Community Supported
We don't officially support these; use them at your own risk!
Code analysis
The code analysis provides extra checks for your ReScript project, such as detecting dead code and unhandled exceptions. It's powered by reanalyze, which is built into the extension — no separate install required.
How to Use
Open the command palette and run:
ReScript: Start Code Analyzer
Warnings like dead code will show inline in the editor.
Suppression actions are available where applicable.
To stop analysis, click Stop Code Analyzer in the status bar.
Configuration
Add a reanalyze
section to your rescript.json
to control what the analyzer checks or ignores. You’ll get autocomplete for config options in the editor.
More details: reanalyze config docs
Exception analysis
The exception analysis is designed to keep track statically of the exceptions that might be thrown at runtime. It works by issuing warnings and recognizing annotations. Warnings are issued whenever an exception is thrown and not immediately caught. Annotations are used to push warnings from he local point where the exception is thrown, to the outside context: callers of the current function. Nested functions need to be annotated separately.
Instructions on how to run the exception analysis using the -exception
and -exception-cmt
command-line arguments, or how to add "analysis": ["exception"]
in rescript.json
are contained in the reanalyze config docs.
Here's an example, where the analysis reports a warning any time an exception is thrown, and not caught:
RESCRIPTlet throws = () => throw(Not_found)
reports:
SH
Exception Analysis
File "A.res", line 1, characters 4-10
throws might throw Not_found (A.res:1:19) and is not annotated with @throws(Not_found)
No warning is reported when a @throws
annotation is added:
RESCRIPT@throws(Not_found)
let throws = () => throw(Not_found)
When a function throws multiple exceptions, a tuple annotation is used:
RESCRIPTexception A
exception B
@throws([A, B])
let twoExceptions = (x, y) => {
if (x) {
throw(A)
}
if (y) {
throw(B)
}
}
It is possible to silence the analysis by adding a @doesNotThrow
annotation:
RESCRIPT@throws(Invalid_argument)
let stringMake1 = String.make(12, ' ')
// Silence only the make function
let stringMake2 = (@doesNotThrow String.make)(12, ' ')
// Silence the entire call (including arguments to make)
let stringMake3 = @doesNotThrow String.make(12, ' ')
Limitations
The libraries currently modeled are limited to the standard library, Belt and Js modules. Models are currently vendored in the analysis, and are easy to add (see
analysis/reanalyze/src/ExnLib.ml
)Generic exceptions are not understood by the analysis. For example
exn
is not recognized below (only concrete exceptions are):
RESCRIPTtry (foo()) { | exn => throw(exn) }
Uses of e.g.
List.head
are interpreted as belonging to the standard library. If you re-defineList
in the local scope, the analysis it will think it's dealing withList
from the standard library.There is no special support for module functions.
Guide
Look here for a more detailed guide about how to use the code analysis tool.
Caveats
Doesn't support cross-package dead code analysis in monorepos. Run it per package instead.
Editor features
Below are features and configurations of the editor tooling that might be good to know about.
Pipe completions
Pipes (->
) are a huge and important part of the ReScript language, for many reasons. Because of that, extra care has gone into the editor experience for using pipes.
Default pipe completion rules for non-builtin types
By default, using ->
will give completions from the module where the type of the expression you're piping on is defined. So, if you're piping on something of the type SomeModule.t
(like someValue->
) then you'll get completions for all functions defined in SomeModule
that take the type t
as the first unlabelled argument.
Pipe completions for builtin types
For builtin types, completion will automatically happen based on the standard library module for that type. So, array
types will get completions from the Array
module, string
gets completions from String
, and so on.
There is a way to enhance this behavior via configuration, described further down in this document.
Dot completion enhancements
In ReScript, using a dot (.
) normally means "access record field". But, because using .
to trigger completions is so pervasive in for example JavaScript, we extend .
to trigger completions in more scenarios than just for record field access.
This behavior has the following important implications:
Improves discoverability (E.g. using a
.
will reveal important pipe completions)Enables a more natural completion flow for people used to JavaScript, where dots power more completions naturally
Below is a list of all the scenarios where using dots trigger completion in addition to the normal record field completion.
Objects
When writing a .
on something that's a structural object, you'll get completions for those object properties. Example:
RESlet obj = {
"first": true,
"second": false
}
let x = obj.
// Will give the following completions for object property access:
// - ["first"]
// - ["second"]
Pipe completions for anything
When writing .
on anything, the editor will try to do pipe completion for the value on the left of the .
. Example:
RESlet arr = [1, 2, 3]
let x = arr.
// Will give the following pipe completions:
// - ->Array.length
// - ->Array.filter
// - ->Array.map
@editor.completeFrom
for drawing completions from additional modules
You can configure any type you have control over to draw pipe completions from additional modules, in addition to the main module where the type is defined, via the @editor.completeFrom
decorator. This is useful in many different scenarios:
When you, for various reasons, need to have your type definition separate from its "main module". Could be because of cyclic dependencies, a need for the type to be in a recursive type definition chain, and so on.
You have separate modules with useful functions for your type but that you don't want to (or can't) include in the main module of that type.
Let's look at an example:
RES// Types.res
// In this example types need to live separately in their own file, for various reasons
type htmlInput
// Utils.res
module HtmlInput = {
/** Gets the HTML input value. */
@get
external value: Types.htmlInput => option<string> = "value"
}
In the example above, if we try and pipe on something of the type Types.htmlInput
, we'll get no completions because there are no functions in Types
that take htmlInput
as its first unlabelled argument. But, better DX would be for the editor to draw completions from our util functions for htmlInput
in the Utils.HtmlInput
module.
With @editor.completeFrom
, we can fix this. Let's look at an updated example:
RES// Types.res
@editor.completeFrom(Utils.HtmlInput)
type htmlInput
// Utils.res
module HtmlInput = {
/** Gets the HTML input value. */
@get
external value: Types.htmlInput => option<string> = "value"
}
Now when piping on a value of the type Types.htmlInput
, the editor tooling will know to include relevant functions from the module Utils.HtmlInput
, and you'll get the completions you expect, even if the functions aren't located in the same module.
You can point out multiple modules to draw completions from for a type either by repeating
@editor.completeFrom
with a single module path each time, or by passing an array with all the module paths you want to include, like@editor.completeFrom([Utils.HtmlInput, HtmlInputUtils])
.
Configuring the editor via editor
in rescript.json
There's certain configuration you can do for the editor on a per project basis in rescript.json
. Below lists all of the configuration available.
autocomplete
for pipe completion
The autocomplete
property of editor
in rescript.json
let's you map types to modules on the project level that you want the editor to leverage when doing autocomplete for pipes.
This is useful in scenarios like:
You have your own util module(s) for builtin types. Maybe you have an
ArrayExtra
with helpers for arrays that you want to get completions from whenever dealing with arrays.You have your own util module(s) for types you don't control yourself (and therefore can't use
@editor.completeFrom
), like from external packages you install.
To configure, you pass autocomplete
an object where the keys are the path to the type you want to target, and then an array of the path to each module you want to include for consideration for pipe completions.
Let's take two examples.
Enhancing completion for builtin types
First, let's look at including our own ArrayExtra
in all completions for array
:
JSON{
"editor": {
"autocomplete": {
"array": ["ArrayExtra"]
}
}
}
Now, when using pipes on arrays, you'll get completions both from the standard library array functions, and also from your own ArrayExtra
module.
RESlet x = [1, 2, 3]->
// Example of what completing at the pipe might look like
- Array.length
- Array.map
- Array.filter
- ArrayExtra.someArrayFn
- ArrayExtra.myOtherArrayFn
Note: generic types like promise.t
and result.t
do not need any additional types in the rescript.json
:
JSON"editor": {
"autocomplete": {
"promise": ["PromiseExt"],
"result": ["ResultExt"]
}
}
Enhancing completion for non-builtin types
Now, let's look at an example of when you have a non-builtin type that you don't have control over.
In this example, imagine this:
We're writing an app using
fastify
We're using an external package that provides the necessary bindings in a
Fastify
moduleWe've got our own extra file
FastifyExtra
that has various custom util functions that operate on the main typeFastify.t
We now want the editor to always suggest completions from the FastifyExtra
module, in addition to the regular completions from the main Fastify
module.
Let's configure this using the editor.autocomplete
config in rescript.json
:
JSON{
"editor": {
"autocomplete": {
"Fastify.t": ["FastifyExt"]
}
}
}
Now, when using pipes on anything of type Fastify.t
, we'll also get completions from our custom FastifyExtra
.
Enhancing completion for non-builtin types with namespaces
When a project uses a namespace, this affects the internal representation of type names used in the autocomplete
configuration.
Consider the geolocation type from the Experimental WebAPI bindings.
This project specifies in its rescript.json
:
JSON{
"name": "@rescript/webapi",
"namespace": "WebAPI"
}
This makes the geolocation
type internally represented as GeolocationAPI-WebAPI.geolocation
, where:
GeolocationAPI
is the module nameWebAPI
is the namespacegeolocation
is the type name
Important: You must use this internal representation when configuring autocomplete for namespaced types:
JSON{
"editor": {
"autocomplete": {
"GeolocationAPI-WebAPI.geolocation": ["GeolocationExt"]
}
}
}