Dead Code Analysis in ReScript
This guide provides a detailed walkthrough on how to leverage ReScript’s powerful dead code analysis tools to maintain a clean, efficient, and distraction-free codebase.
Dead code refers to code that's present in your codebase but is never executed. It can lead to:
Increased compilation times
Confusion during development
Misleading assumptions about functionality
ReScript’s language design allows for accurate and efficient dead code analysis using the ReScript Code Analyzer. It is available through the official VS Code extension and the rescript-tools executable bundled with ReScript.
Prerequisites
ReScript VSCode extension (v1.8.2 or higher)
Activation
Open the Command Palette:
Cmd/Ctrl + PRun:
> ReScript: Start Code Analyzer
Deactivation
Run:
> ReScript: Stop Code AnalyzerOr click “Stop Code Analyzer” in the status bar
Result
The “Problems” pane populates with dead code warnings and suggestions.
Command-Line Analysis
To run the analyses configured in rescript.json from the command line:
SHrescript-tools reanalyze -config
ReScript 12.1 added a reactive mode that caches unchanged analysis data:
SHrescript-tools reanalyze -config -reactive
The diagnostic flags -runs <n> and -churn <n> can benchmark cache effectiveness and incremental correctness. -mermaid prints a Mermaid diagram of the reactive pipeline.
reanalyze-server keeps the analysis process alive between requests. JSON analysis requests delegate to it transparently, so integrations can use rescript-tools reanalyze -json without managing the server themselves.
Real-World Use Cases
1. Unused Record Fields
REStype item = {id: string, checked: bool}
type useReturn = {
items: array<item>,
toggleItemChecked: string => unit, // ← Never used
setCheckedOnItem: (string, bool) => unit,
checkAll: unit => unit,
uncheckAll: unit => unit,
}
Remove unused fields to simplify code.
2. Unused Variant Cases
REStype textType =
| Text(string)
| TextWithIcon({icon: React.element, text: string})
| Render(React.element) // ← Never constructed
Removing unused variants allows simplifying rendering logic.
3. Unused Parts of State
REStype validationState = Idle | Invalid | Valid
type state = {
oldPassword: string,
newPassword: string,
newPasswordRepeated: string,
validationState: validationState, // ← Never read
}
Old validation logic might remain after refactors—clean it up.
4. Unnecessary Interface Exposure
RESI// DrilldownTarget.resi
type t
let parse: string => option<t> // ← Never used
let serialize: t => string // ← Never used
Keep interfaces minimal by removing unused exports.
5. Unused Functions
RESlet routerUrlToPath = url => url->String.split("?")->Array.get(0) // ← Never used
let routeUrlStartsWith = (url, prefix) => url->String.startsWith(prefix) // ← Never used
Removing these often uncovers further unused logic.
6. Unused Components
Components never referenced in production should be removed, unless explicitly preserved.
Keeping Some Dead Code
Use @dead and @live
@dead
Suppresses warnings but notifies if code becomes alive again.
REStype user = {
name: string,
@dead age: int,
}
@live
Permanently marks code as alive (no future warnings).
REStype user = {name: string}
@live
let getUserName = user => user.name
Scoped @@live and @@dead
ReScript 12.2 added standalone forms that set the default for the remainder of the current file or module:
RESmodule PublicApi = {
@@live
let load = () => "loaded"
let save = _value => ()
}
module DeferredCleanup = {
@@dead
let oldParser = input => input
let oldSerializer = value => value
}
Items under @@live are treated as live roots and can keep their dependencies alive. Items under @@dead are suppressed but don't keep their dependencies alive. A later @@live or @@dead changes the default again, and nested modules inherit the active setting.
Configuration
Add to your rescript.json:
JSON"reanalyze": {
"analysis": ["dce"],
"suppress": ["src/generated/**", "src/stories/**"],
"unsuppress": ["src/generated/handwritten/**"],
"transitive": false
}
Options:
analysis: Enables dead code analysis (
"dce")suppress: Silences reporting for matching paths (they are still analyzed)
unsuppress: Re-enables reports for matching paths within a suppressed area
transitive: Controls reporting of indirectly dead code
Since ReScript 12.3, suppress and unsuppress accept glob patterns such as src/generated/**. Plain path prefixes continue to work.
Recommendation: Set transitive: false for incremental cleanup.
Summary
ReScript’s dead code analyzer helps you:
Incrementally clean up your codebase
Avoid confusion and complexity
Improve long-term maintainability
Use it regularly for the best results.