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

  1. Open the Command Palette: Cmd/Ctrl + P

  2. Run: > ReScript: Start Code Analyzer

Deactivation

  • Run: > ReScript: Stop Code Analyzer

  • Or 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:

SH
rescript-tools reanalyze -config

ReScript 12.1 added a reactive mode that caches unchanged analysis data:

SH
rescript-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

RES
type 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

RES
type 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

RES
type 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

RES
let 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.

RES
type user = { name: string, @dead age: int, }

@live

Permanently marks code as alive (no future warnings).

RES
type 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:

RES
module 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.