Code Analysis¶
The plugin provides several code analysis features to help you maintain clean, correct code.
Real-Time Diagnostics¶
LSP Required
The Language Server provides real-time error and warning diagnostics as you type:
Errors — Shown with red underlines
Warnings — Shown with yellow underlines
Info — Shown with subtle underlines
View all diagnostics in the Problems panel (Alt+6).
Instead of waiting for a full build cycle to discover issues, you get immediate feedback on errors and warnings as you type, catching problems before they compound.
Code Inspections¶
Native
The plugin includes built-in inspections that run locally without requiring the Language Server.
Duplicate Open Detection¶
Reports adjacent repeated opens only when both resolve to the same supported local module. Identical module names alone do not prove redundancy. Reopens and unknown module identities are preserved.
module A = {let value = 1}
open A
open A
Console.log(value)
module A = {let value = 1}
open A
Console.log(value)
Use the inspection quick fix or Optimize Imports (Ctrl+Alt+O) to remove this proven redundant open in one undoable edit.
Empty Module Detection¶
Warns about module declarations that have no content. Empty modules are typically leftover scaffolding or incomplete implementations.
module Utils = {
// Warning: empty module body
}
module Helpers = {
let format = (s) => s->String.trim
}
The warning appears on the empty module declaration, prompting you to either add content or remove the empty module.
Mismatched JSX Closing Tag¶
Warns when a JSX element’s closing tag name does not match its opening tag name. Such mismatches usually result from renaming a tag and forgetting to update the matching side.
let view = <div> {React.string("hello")} </span>
// Warning: Closing tag 'span' does not match opening tag 'div'
The warning is highlighted on the closing tag name, so you can spot and fix the mismatch immediately. Fragments (<> ... </>) and self-closing elements (<input />) are never flagged, and correctly nested elements sharing the same name are left untouched.
Missing Configuration¶
When no rescript.json (or legacy bsconfig.json) is found in the project root, the plugin displays an editor notification bar at the top of ReScript files. This configuration file is essential for:
ReScript compiler — Without it, the project cannot be built. The compiler uses this file to locate source directories, dependencies, and build settings.
Language Server — The LSP server relies on
rescript.jsonto understand the project structure. Without it, features like auto-completion, go-to-definition, and diagnostics will not function.Plugin features — Several plugin features (Code Lens, semantic highlighting, inlay hints) depend on the Language Server, which in turn requires the configuration file.
If you see this warning, create a rescript.json file in your project root. The easiest way is to use the Project Wizard (File > New > Project > ReScript) or initialize a project with npm init rescript-app@latest.
Dead Code Analysis (reanalyze)¶
Per-invocation reanalyze CLI analysis uses concurrent output capture, IDE progress cancellation, a total command timeout and a 4 MiB limit per output stream. Failed or incomplete output is not parsed as diagnostics; the editor shows a file-level warning, or project inspection shows a notification. The persistent reanalyze server has a separate lifecycle.
Native Configuration Required
The plugin integrates with reanalyze, a static analysis tool built into rescript-tools, to detect unused and dead code in your project.
What It Detects¶
Dead code — Unused functions, values, types, and modules that are never referenced
Dead exceptions — Exception declarations that are never raised or caught
Unhandled exceptions — Exception paths not covered by try/catch blocks
Code Example¶
Consider a file with an unused helper function:
let formatName = (first, last) => `${first} ${last}`
let greet = (name) => `Hello, ${name}!`
// Only greet is used elsewhere in the project.
// formatName is never called from any other module.
The reanalyze tool detects that formatName is dead code and displays a warning annotation on the function definition: “formatName is dead code”.
Quick Fixes¶
When reanalyze identifies unused or dead code, the plugin offers quick fixes via Alt+Enter:
1. Prefix with underscore — Marks the identifier as intentionally unused. This is useful when you want to keep the code for documentation or future use:
// Before: warning on unused function
let formatName = (first, last) => `${first} ${last}`
// After: applying "Prefix with underscore" quick fix
let _formatName = (first, last) => `${first} ${last}`
2. Remove unused code — Deletes the entire declaration line. Use this when the code is truly no longer needed:
// Before
let formatName = (first, last) => `${first} ${last}`
let greet = (name) => `Hello, ${name}!`
// After: applying "Remove unused code" quick fix on formatName
let greet = (name) => `Hello, ${name}!`
The available quick fixes depend on the diagnostic type:
Unused variables/arguments/values/types/modules — Both “Prefix with underscore” and “Remove unused code” are offered
Dead code/modules/types/values/exceptions — Only “Remove unused code” is offered
Setup Requirements¶
The reanalyze integration requires:
ReScript compiler installed — The
rescriptpackage must be in your project’snode_modules. The plugin looks forrescript-tools(orrescript-tools.exeon Windows) innode_modules/rescript/.Project must be built — reanalyze operates on the compiled output. Run
rescript buildat least once before expecting dead code analysis results.Optional: reanalyze configuration in rescript.json — You can fine-tune the analysis by adding a
reanalyzesection to yourrescript.json:
{
"name": "my-project",
"sources": [{"dir": "src", "subdirs": true}],
"reanalyze": {
"analysis": ["dce", "exception"],
"suppress": ["src/bindings"],
"transitive": false
}
}
Configuration options:
analysis— Array of analysis types to enable:"dce"(dead code elimination),"exception"(exception analysis),"termination"(infinite loop detection)suppress— Array of directory paths to exclude from analysis (useful for FFI bindings)unsuppress— Specific files within suppressed directories to re-includetransitive— Whether to report transitively dead items (default:false)
How It Works¶
The reanalyze annotator follows a three-phase lifecycle:
Collect — On the EDT (Event Dispatch Thread), gathers the file path and project base path
Annotate — In a background thread, runs
rescript-tools reanalyze -jsonand parses the JSON outputApply — Maps diagnostics back to editor positions and creates warning annotations with quick-fix actions
Global Inspection¶
In addition to per-file annotations in the editor, you can analyze the entire project at once:
Go to Code > Inspect Code
Select the inspection scope (whole project or specific directories)
Results appear in the Inspection Results panel, grouped by file
The global inspection runs a single invocation of rescript-tools reanalyze -json and distributes the results across all affected ReScript files in the project.
Server Mode (ReScript >= 12.1.0)¶
When ReScript 12.1.0 or later is installed, the plugin can start a reanalyze server daemon that transparently accelerates dead code analysis. Instead of running a fresh reanalyze -json process for every file, the server keeps analysis state in memory and communicates via a Unix socket (.rescript-reanalyze.sock), resulting in significantly faster results on large projects.
Enabling server mode:
Server mode is enabled by default in Settings > Languages & Frameworks > ReScript > Enable reanalyze server mode. The server starts automatically when a ReScript project is opened, provided:
The setting is enabled
The project contains
rescript.json(orbsconfig.json)ReScript >= 12.1.0 is installed in
node_modules
Features:
Automatic startup — The server starts when the project opens and stops when the project closes
External server detection — If a reanalyze server is already running (e.g., started by another tool), the plugin detects it via the socket file and does not start a second instance
Health monitoring — The plugin checks the server every 5 seconds and automatically restarts it (up to 3 times) if it crashes
Transparent acceleration — No changes to your workflow; the existing annotator and inspection automatically benefit from the server
Note: Existing behavior is fully preserved when server mode is disabled or ReScript < 12.1.0 is installed. The plugin falls back to the standard per-invocation reanalyze -json mode.
Without dead code analysis, unused functions and types silently accumulate, increasing build times and cognitive load. Reanalyze integration surfaces this dead code automatically so you can keep your codebase lean and maintainable.
Signature Sync Inspection¶
Detects mismatches between .res implementation files and their .resi interface files. When a declaration in .res is added, removed, or has a different signature than what’s declared in .resi, the inspection highlights the discrepancy.
Example (mismatch detected):
// In Foo.resi:
let greet: string => string
// In Foo.res:
let greet = (name: string, greeting: string) => `${greeting}, ${name}!`
// Warning: signature mismatch with .resi
The inspection helps catch situations where the implementation has diverged from the interface, which would cause compilation errors.
These local inspections run instantly without the Language Server, catching common code quality issues like redundant imports, empty scaffolding, and missing configuration before they become problems in code review.
See also
Code Editing offers Intention Actions (Alt+Enter) for manual code transformations that complement automated inspections.
Mutability Diagnostics¶
Offers Remove unnecessary ref only for local scalar literal references inside braced function bodies when every use is a direct .contents read. The fix updates the initializer and every read together in one undo step. Writes (:= or .contents =), aliases, box arguments or returns, exports, closures, shadowing, open/include, opaque interop or attributes anywhere in the file, nonliteral initializers, and unknown syntax are refused. Any document change after inspection invalidates the prepared fix.
let run = () => {
let counter = ref(0)
let next = counter.contents + 1
counter.contents + next
}
let run = () => {
let counter = 0
let next = counter + 1
counter + next
}
Press Alt+Enter on the warning and select Remove unnecessary ref to apply the fix.
Style Linting¶
Detects common style issues and suggests idiomatic alternatives:
1. Redundant boolean expression:
// Before: redundant
let isValid = if condition { true } else { false }
// After: simplified
let isValid = condition
2. Deprecated Belt. usage:*
// Before: legacy Belt API
Belt.Array.map(arr, fn)
// After: modern API
Array.map(arr, fn)
3. Boolean switch:
// Before: verbose
switch flag { | true => a | false => b }
// After: idiomatic
if flag { a } else { b }
Each rule provides a quick fix via Alt+Enter.
Suggested Refactoring¶
Automatically detects code patterns that can be improved and proposes refactoring suggestions. This inspection identifies opportunities for cleaner code without changing behavior.
Detected patterns:
1. Extractable repeated expressions:
// Before: same expression used multiple times
let area = width * height
let perimeter = 2 * (width * height)
// Suggestion: Extract common expression into a variable
2. Simplifiable conditional assignments:
// Before: verbose conditional
let result = if condition {
value
} else {
value
}
// Suggestion: Remove redundant conditional
3. Inlineable single-use variables:
// Before: variable used only once
let temp = compute(x)
process(temp)
// Suggestion: Inline variable
Each suggestion appears as a weak warning with a quick fix via Alt+Enter.
Error Lens¶
LSP Required
Error Lens displays diagnostic messages (errors, warnings, info) as inline annotations at the end of the affected line, providing immediate visibility without needing to hover or check the Problems panel.
let x = "hello" + 1 ← This expression has type int but expected string
When multiple diagnostics appear on the same line, only the highest-severity message is shown with a “(+N more)” suffix.
Severity Colors¶
Inline annotations are colored by severity:
Errors — Red
Warnings — Yellow/amber
Info/Hints — Gray
Configuration¶
Error Lens can be configured in Settings > Languages & Frameworks > ReScript:
Enable/Disable — Toggle Error Lens on or off
Minimum severity — Set the minimum severity level to display (e.g., show only errors, or errors and warnings)
Error Lens updates automatically whenever the IDE’s code analysis pass completes, so annotations stay in sync with the latest diagnostics.
By showing diagnostics directly on the affected line, Error Lens eliminates the need to hover over underlines or check the Problems panel, letting you spot and fix issues without breaking your editing flow.
Type Mismatch Inline Hints¶
When a type error involves a mismatch between expected and actual types, Error Lens displays a structured inline hint showing both types side by side:
let x: string = 42 ← Expected: string, Actual: int
This structured display makes it easier to understand type errors at a glance without needing to open the Problems panel or hover over the error.
Type Mismatch Diff Highlighting¶
For type mismatch errors, Error Lens visually highlights the parts that differ between the expected and actual types. Segments that match are shown in the standard color, while differing segments are highlighted in a contrasting color, making it easy to pinpoint exactly where the types diverge.
let x: option<string> = Some(42)
← Expected: option<[string]> | Actual: option<[int]>
In this example, only string vs int would be highlighted, since option<...> matches in both types.
Format Check¶
Native Configuration Required
The plugin can check whether your ReScript files are formatted according to rescript format and highlight unformatted files with a warning.
How It Works¶
When enabled, the plugin runs rescript format --stdin in the background and compares the output with the current editor content. If they differ, a file-level annotation appears indicating that the code is not formatted.
Quick Fix¶
Press Alt+Enter on the annotation and select Format this file to reformat the code immediately. Alternatively, use the standard format shortcut (Cmd+Option+L on macOS or Ctrl+Alt+L on Windows/Linux).
Configuration¶
The format check is disabled by default. To enable it:
Go to Settings > Languages & Frameworks > ReScript
Check Enable format check (highlight unformatted code)
Requirements¶
The
rescriptCLI must be installed in the project’snode_modules. The plugin auto-detects it fromnode_modules/.bin/rescript.
Format Check helps enforce a consistent code style across your team without manual review — unformatted files are flagged automatically and can be fixed with a single shortcut.
Problem Highlight Filter¶
Native
The plugin suppresses code analysis highlights in directories where they are not useful, such as node_modules/ and other dependency directories. This prevents noise from third-party library files appearing in the Problems panel and editor gutter.
Filtered Directories¶
node_modules/— npm/yarn/pnpm package directoriesOther generated or vendored directories that are not part of your source code
How It Works¶
The plugin implements IntelliJ’s ProblemHighlightFilter extension point, which checks whether a file should receive code analysis highlights. Files inside filtered directories are excluded from highlighting, reducing false positives and improving IDE performance.
This filter applies to all highlight types (errors, warnings, info) from both local inspections and the Language Server.
This keeps the Problems panel focused on issues in your own code, filtering out noise from third-party libraries that would otherwise clutter your diagnostics and slow down the editor.
Import Optimization¶
Native
Press Ctrl+Alt+O (or Cmd+Alt+O on macOS) to optimize imports in the current file.
The optimizer preserves source order and removes only proven adjacent local-module duplicates. Reopens separated by code and unknown modules remain unchanged. See Import Optimization for the supported subset and diagnostic limitations.
Quick Fixes (LSP)¶
LSP Required
The Language Server provides automatic code fixes via Alt+Enter (or the light bulb icon in the gutter). These LSP-powered quick fixes operate on semantic analysis from the ReScript compiler and can handle situations that local inspections cannot.
@rescript/language-server ships nine categories of quick fix that the plugin receives through the standard textDocument/codeAction flow:
Add missing pattern cases — Inserts unhandled constructors at the end of a
switchblock (simpleAddMissingCases)Wrap in
Some/ unwrap option — Wraps a value inSome(...)(or unwraps an option) when the surrounding context expects anoptiontype (wrapInSome,unwrapOptional)Add missing record fields — Fills in fields that are required by the record type but absent from the literal (
addUndefinedRecordFields)Insert primitive conversion — Wraps an expression with
int_of_string,float_of_int, etc. when anint/float/stringmismatch is reported (simpleConversion)Replace with suggested name — Replaces a misspelled identifier with the value the compiler suggested in Did you mean …? (
didYouMean)Remove unused code — Deletes a declaration that reanalyze flagged as unused (
removeUnusedCode; requires areanalyzeblock inrescript.json)Extract local module to file — Moves a
module M = { ... }declaration into a newM.resfile at the project root (extractLocalModuleToFile)Expand catch-all pattern — Replaces an explicit
_ =>case with one branch per variant constructor (expandCatchAllPatterns)Apply uncurried — Converts a curried call site into the uncurried form
f(. x)on legacy ReScript v10 / v11 codebases (applyUncurried; not emitted on uncurried-by-default builds)
Quick fixes appear contextually based on the compiler diagnostic at the cursor position. Press Alt+Enter to see all available actions, or click the light bulb icon that appears in the editor gutter.
Unresolved Reference Quick Fix¶
When an identifier cannot be resolved, the plugin offers quick fixes to resolve it:
Add
openstatement — Insert anopenstatement for the module containing the referenced symbolAdd module qualifier — Prefix the identifier with its full module path
Example:
// "map" is unresolved
let result = map(arr, fn)
// Quick fix: Add open Belt.Array
open Belt.Array
let result = map(arr, fn)
// Or: Add qualifier
let result = Belt.Array.map(arr, fn)
Generate Function from Usage¶
When you call a function that does not exist yet, the plugin can generate a stub function definition. Press Alt+Enter on the unresolved function call and choose Generate function.
let result = processData(input, config)
// processData is not defined
let processData = (input, config) => {
todo
}
let result = processData(input, config)
Type Hole Quick Fix¶
When the compiler reports a type hole (_ used as a type placeholder), the plugin suggests candidate types to fill in. Press Alt+Enter on the type hole diagnostic to see matching type suggestions.
let parse: string => _ = jsonStr => {
// compiler error: type hole found
}
let parse: string => JSON.t = jsonStr => {
// type hole filled with suggested type
}
The quick fix parses the compiler diagnostic to extract candidate types and offers them as replacement options.
LSP quick fixes turn compiler errors into one-click corrections, letting you resolve type mismatches, missing imports, and incomplete patterns directly from the error location instead of manually editing the code.
Type Narrowing Visualizer¶
ReScript narrows the scrutinee of a switch expression to a more specific type inside each arm. The plugin visualizes this narrowing inline so you can see the refined type without hovering on every arm.
When the LSP server is running, an inlay hint is rendered immediately after each arm’s => showing the narrowed type. The hint reuses the same hover information that powers Quick Documentation, so it always agrees with what textDocument/hover returns at the scrutinee position.
Pattern bindings get an extra hint of their own. When an arm introduces a binding (Some(x), Loaded(payload, error), …), a second inlay is anchored right after each binding identifier showing that binding’s own narrowed type. Bare lowercase catch-all arms like | other => ... are intentionally skipped because the binding’s type is identical to the scrutinee — no information is gained.
let describe = result =>
switch result {
| Ok(value) => value
| Error(_) => "fallback"
}
let describe = result =>
switch result {
| Ok(value: string) =>: result<string, error> value
| Error(_) =>: result<string, error> "fallback"
}
The visualizer also handles or-patterns (| Some(_) | None => is treated as a single arm), when guards, and nested switch expressions.
Configuration¶
Toggle from Settings > Editor > Inlay Hints > ReScript > Type narrowing in switch arms.
The project-level flag
narrowingHintsEnabled(default on) is exposed onRescriptProjectSettingsfor projects that want to disable this hint without touching other inlay providers.A 50-arm-per-file soft cap suppresses the hint on switch-heavy files to keep editor scrolling responsive.
When the LSP server is not running the visualizer is silent — it never displays a partial or stale type. Trivial types (unit, free type variables) are also suppressed because they convey no narrowing information.