コード分析

このプラグインは、クリーンで正しいコードを維持するためのさまざまなコード分析機能を提供します。

リアルタイム診断

LSP Required

Language Server は、入力中にリアルタイムでエラーおよび警告の診断を提供します:

  • Errors --- 赤い下線で表示されます

  • Warnings --- 黄色い下線で表示されます

  • Info --- 控えめな下線で表示されます

すべての診断は Problems パネル (Alt+6) で確認できます。

完全なビルドサイクルを待って問題を発見する代わりに、入力中にエラーや警告のフィードバックをリアルタイムで受け取れるため、問題が複雑化する前にキャッチできます。

コードインスペクション

Native

このプラグインには、Language Server を必要とせずローカルで実行されるビルトインインスペクションが含まれています。

重複 open の検出

同じファイル内で同一モジュールが複数回 open されている場合を検出します。重複する open 文は冗長であり、コードに不要なノイズを加えます。

open Belt
open Belt.Array
open Belt  // Warning: duplicate open statement

let arr = [1, 2, 3]
let doubled = arr->Array.map(x => x * 2)
open Belt
open Belt.Array

let arr = [1, 2, 3]
let doubled = arr->Array.map(x => x * 2)

インスペクションは重複する open 文を警告としてハイライトします。手動で削除するか、Optimize Imports (Ctrl+Alt+O) を使用してすべての重複を自動的に削除できます。

空モジュールの検出

内容のないモジュール宣言について警告します。空のモジュールは、通常、残されたスキャフォールディングや不完全な実装です。

module Utils = {
  // Warning: empty module body
}

module Helpers = {
  let format = (s) => s->String.trim
}

空のモジュール宣言に警告が表示され、内容を追加するか空のモジュールを削除するよう促します。

設定ファイルの欠落

プロジェクトルートに rescript.json (または旧 bsconfig.json) が見つからない場合、プラグインは ReScript ファイルの上部にエディタ通知バーを表示します。この設定ファイルは以下の理由で不可欠です:

  • ReScript compiler --- この設定ファイルがないと、プロジェクトをビルドできません。コンパイラはこのファイルを使用して、ソースディレクトリ、依存関係、ビルド設定を特定します。

  • Language Server --- LSP サーバーはプロジェクト構造を理解するために rescript.json に依存しています。これがないと、自動補完、定義へのジャンプ、診断などの機能が動作しません。

  • Plugin features --- いくつかのプラグイン機能 (Code Lens、セマンティックハイライト、インレイヒント) は Language Server に依存しており、Language Server は設定ファイルを必要とします。

この警告が表示された場合は、プロジェクトルートに rescript.json ファイルを作成してください。最も簡単な方法は、Project Wizard (File > New > Project > ReScript) を使用するか、npm init rescript-app@latest でプロジェクトを初期化することです。

デッドコード分析 (reanalyze)

Native Configuration Required

このプラグインは、rescript-tools に組み込まれた静的分析ツールである reanalyze と統合し、プロジェクト内の未使用コードやデッドコードを検出します。

検出対象

  • デッドコード --- 一度も参照されない未使用の関数、値、型、モジュール

  • デッド例外 --- 一度も発生または捕捉されない例外宣言

  • 未処理例外 --- try/catch ブロックでカバーされていない例外パス

コード例

未使用のヘルパー関数を含むファイルを考えてみましょう:

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.

reanalyze ツールは formatName がデッドコードであることを検出し、関数定義に警告アノテーションを表示します: "formatName is dead code"

クイックフィックス

reanalyze が未使用コードやデッドコードを検出すると、プラグインは Alt+Enter でクイックフィックスを提供します:

1. アンダースコアプレフィックスの付与 --- 識別子を意図的に未使用であるとマークします。ドキュメントや将来の使用のためにコードを残しておきたい場合に便利です:

// 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. 未使用コードの削除 --- 宣言行全体を削除します。コードが本当に不要になった場合に使用します:

// 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}!`

利用可能なクイックフィックスは診断タイプによって異なります:

  • 未使用の変数/引数/値/型/モジュール --- "Prefix with underscore" と "Remove unused code" の両方が提供されます

  • デッドコード/モジュール/型/値/例外 --- "Remove unused code" のみが提供されます

セットアップ要件

reanalyze 統合には以下が必要です:

  1. ReScript コンパイラのインストール --- rescript パッケージがプロジェクトの node_modules に存在する必要があります。プラグインは node_modules/rescript/ 内の rescript-tools (Windows では rescript-tools.exe) を検索します。

  2. プロジェクトのビルドが必要 --- reanalyze はコンパイル出力に対して動作します。デッドコード分析の結果を得るには、事前に rescript build を少なくとも一度実行してください。

  3. オプション: rescript.json での reanalyze 設定 --- rescript.jsonreanalyze セクションを追加することで分析を細かく調整できます:

{
  "name": "my-project",
  "sources": [{"dir": "src", "subdirs": true}],
  "reanalyze": {
    "analysis": ["dce", "exception"],
    "suppress": ["src/bindings"],
    "transitive": false
  }
}

設定オプション:

  • analysis --- 有効にする分析タイプの配列: "dce" (デッドコード除去)、"exception" (例外分析)、"termination" (無限ループ検出)

  • suppress --- 分析から除外するディレクトリパスの配列 (FFI バインディングに便利です)

  • unsuppress --- 除外されたディレクトリ内で再び分析対象に含める特定のファイル

  • transitive --- 推移的にデッドな項目を報告するかどうか (デフォルト: false)

動作の仕組み

reanalyze アノテータは 3 つのフェーズで動作します:

  1. Collect --- EDT (Event Dispatch Thread) 上で、ファイルパスとプロジェクトのベースパスを収集します

  2. Annotate --- バックグラウンドスレッドで rescript-tools reanalyze -json を実行し、JSON 出力を解析します

  3. Apply --- 診断結果をエディタの位置にマッピングし、クイックフィックスアクション付きの警告アノテーションを作成します

グローバルインスペクション

エディタ内のファイルごとのアノテーションに加えて、プロジェクト全体を一括で分析することもできます:

  1. Code > Inspect Code を選択します

  2. インスペクションのスコープを選択します (プロジェクト全体または特定のディレクトリ)

  3. 結果は Inspection Results パネルにファイルごとにグループ化されて表示されます

グローバルインスペクションは rescript-tools reanalyze -json を一度だけ実行し、プロジェクト内の影響を受けるすべての ReScript ファイルに結果を分配します。

サーバーモード (ReScript >= 12.1.0)

ReScript 12.1.0 以降がインストールされている場合、プラグインはデッドコード分析を透過的に高速化する reanalyze サーバーデーモンを起動できます。ファイルごとに新しい reanalyze -json プロセスを実行する代わりに、サーバーは分析状態をメモリに保持し、Unix ソケット (.rescript-reanalyze.sock) 経由で通信するため、大規模プロジェクトで大幅に高速な結果が得られます。

サーバーモードの有効化:

サーバーモードは Settings > Languages & Frameworks > ReScript > Enable reanalyze server mode でデフォルトで有効になっています。以下の条件を満たす場合、ReScript プロジェクトを開いたときにサーバーが自動的に起動します:

  1. 設定が有効になっていること

  2. プロジェクトに rescript.json (または bsconfig.json) が含まれていること

  3. ReScript >= 12.1.0 が node_modules にインストールされていること

機能:

  • 自動起動 --- プロジェクトを開くとサーバーが起動し、プロジェクトを閉じると停止します

  • 外部サーバー検出 --- reanalyze サーバーが既に実行中の場合(例: 別のツールによって起動された場合)、プラグインはソケットファイルを通じてそれを検出し、2つ目のインスタンスを起動しません

  • ヘルスモニタリング --- プラグインは5秒ごとにサーバーをチェックし、クラッシュした場合は自動的に再起動します(最大3回)

  • 透過的な高速化 --- ワークフローの変更は不要です。既存のアノテータとインスペクションがサーバーの恩恵を自動的に受けます

注意: サーバーモードが無効の場合や ReScript < 12.1.0 がインストールされている場合、既存の動作は完全に保持されます。プラグインは標準の呼び出しごとの reanalyze -json モードにフォールバックします。

デッドコード分析がなければ、未使用の関数や型が静かに蓄積し、ビルド時間と認知負荷が増大します。reanalyze 統合はこのデッドコードを自動的に検出するため、コードベースをスリムで保守しやすく保てます。

シグネチャ同期インスペクション

.res 実装ファイルと対応する .resi インターフェースファイル間の不一致を検出します。.res の宣言が追加・削除された場合や、.resi で宣言されているシグネチャと異なる場合、インスペクションがその相違をハイライトします。

(不一致検出時):

// In Foo.resi:
let greet: string => string

// In Foo.res:
let greet = (name: string, greeting: string) => `${greeting}, ${name}!`
// Warning: signature mismatch with .resi

このインスペクションは、実装がインターフェースから乖離している状況を検出するのに役立ちます。このような乖離はコンパイルエラーの原因となります。

これらのローカルインスペクションは Language Server なしで瞬時に実行され、冗長なインポート、空のスキャフォールディング、設定ファイルの欠如といった一般的なコード品質の問題を、コードレビューで問題になる前にキャッチします。

参考

Code Editing では、自動インスペクションを補完する手動コード変換のための Intention アクション (Alt+Enter) を提供しています。

変更可能性の診断

:= で再代入されることのない ref バインディングを検出します。ミュータブル参照が作成されても一度も変更されない場合、通常の let バインディングに簡略化できます。

let counter = ref(0)
// counter is never reassigned with :=
let value = counter.contents
let counter = 0
let value = counter

警告上で Alt+Enter を押し、Remove unnecessary ref を選択して修正を適用します。

スタイルリンティング

一般的なスタイルの問題を検出し、慣用的な代替案を提案します:

1. 冗長なブール式:

// Before: redundant
let isValid = if condition { true } else { false }
// After: simplified
let isValid = condition

2. 非推奨の Belt. の使用:*

// Before: legacy Belt API
Belt.Array.map(arr, fn)
// After: modern API
Array.map(arr, fn)

3. ブール switch:

// Before: verbose
switch flag { | true => a | false => b }
// After: idiomatic
if flag { a } else { b }

各ルールは Alt+Enter でクイックフィックスを提供します。

リファクタリング提案

改善可能なコードパターンを自動的に検出し、リファクタリングの提案を行います。このインスペクションは、動作を変えずにコードをよりクリーンにする機会を特定します。

検出されるパターン:

1. 抽出可能な繰り返し式:

// Before: same expression used multiple times
let area = width * height
let perimeter = 2 * (width * height)
// Suggestion: Extract common expression into a variable

2. 簡略化可能な条件付き代入:

// Before: verbose conditional
let result = if condition {
  value
} else {
  value
}
// Suggestion: Remove redundant conditional

3. インライン化可能な単一使用変数:

// Before: variable used only once
let temp = compute(x)
process(temp)
// Suggestion: Inline variable

各提案は弱い警告として表示され、Alt+Enter でクイックフィックスを利用できます。

Error Lens

LSP Required

Error Lens は、診断メッセージ(エラー、警告、情報)を該当行の末尾にインラインアノテーションとして表示し、ホバーや Problems パネルの確認なしに即座に視認できるようにします。

let x = "hello" + 1    ← This expression has type int but expected string

同じ行に複数の診断が表示される場合、最も重大度の高いメッセージのみが "(+N more)" のサフィックス付きで表示されます。

重大度の色分け

インラインアノテーションは重大度に応じて色分けされます:

  • Errors --- 赤

  • Warnings --- 黄/琥珀色

  • Info/Hints --- グレー

設定

Error Lens は Settings > Languages & Frameworks > ReScript で設定できます:

  • Enable/Disable --- Error Lens のオン/オフを切り替えます

  • Minimum severity --- 表示する最小の重大度レベルを設定します(例: エラーのみ、またはエラーと警告を表示)

Error Lens は IDE のコード分析パスが完了するたびに自動的に更新されるため、アノテーションは常に最新の診断と同期されます。

影響を受ける行に直接診断を表示することで、Error Lens は下線にホバーしたり Problems パネルを確認したりする必要をなくし、編集の流れを中断することなく問題を発見・修正できます。

型ミスマッチインラインヒント

型エラーが期待される型と実際の型の不一致を含む場合、Error Lens は両方の型を並べて表示する構造化されたインラインヒントを表示します:

let x: string = 42    ← Expected: string, Actual: int

この構造化された表示により、Problems パネルを開いたりエラーにホバーしたりすることなく、型エラーを一目で理解しやすくなります。

型ミスマッチ差分ハイライト

型ミスマッチエラーでは、Error Lens は期待される型と実際の型の間で異なる部分を視覚的にハイライトします。一致する部分は標準の色で表示され、異なる部分はコントラストのある色でハイライトされるため、型がどこで分岐しているかを正確に特定しやすくなります。

let x: option<string> = Some(42)
← Expected: option<[string]> | Actual: option<[int]>

この例では、option<...> は両方の型で一致するため、stringint の部分のみがハイライトされます。

フォーマットチェック

Native Configuration Required

プラグインは、ReScript ファイルが rescript format に従ってフォーマットされているかどうかを確認し、未フォーマットのファイルを警告でハイライトできます。

動作の仕組み

有効にすると、プラグインはバックグラウンドで rescript format --stdin を実行し、出力を現在のエディタの内容と比較します。異なる場合、コードがフォーマットされていないことを示すファイルレベルのアノテーションが表示されます。

クイックフィックス

アノテーション上で Alt+Enter を押し、Format this file を選択してコードを即座にフォーマットします。あるいは、標準のフォーマットショートカット (macOS では Cmd+Option+L、Windows/Linux では Ctrl+Alt+L) を使用します。

設定

フォーマットチェックはデフォルトで無効です。有効にするには:

  1. Settings > Languages & Frameworks > ReScript を開きます

  2. Enable format check (highlight unformatted code) にチェックを入れます

要件

  • rescript CLI がプロジェクトの node_modules にインストールされている必要があります。プラグインは node_modules/.bin/rescript から自動的に検出します。

Format Check は手動レビューなしでチーム全体で一貫したコードスタイルを強制します — 未フォーマットのファイルは自動的にフラグ付けされ、ショートカット一つで修正できます。

問題ハイライトフィルター

Native

プラグインは、node_modules/ やその他の依存関係ディレクトリなど、コード分析のハイライトが有用でないディレクトリでハイライトを抑制します。これにより、サードパーティライブラリのファイルからのノイズが Problems パネルやエディタガターに表示されるのを防ぎます。

フィルター対象ディレクトリ

  • node_modules/ --- npm/yarn/pnpm パッケージディレクトリ

  • ソースコードに含まれないその他の自動生成またはベンダーディレクトリ

動作の仕組み

プラグインは IntelliJ の ProblemHighlightFilter 拡張ポイントを実装しており、ファイルがコード分析のハイライトを受けるべきかどうかを判定します。フィルター対象ディレクトリ内のファイルはハイライトから除外され、誤検知の減少と IDE パフォーマンスの向上につながります。

このフィルターは、ローカルインスペクションと Language Server の両方からのすべてのハイライトタイプ(エラー、警告、情報)に適用されます。

これにより Problems パネルが自分のコードの問題に集中でき、サードパーティライブラリからのノイズをフィルタリングして、診断の混乱やエディタの速度低下を防ぎます。

インポートの最適化

Native

Ctrl+Alt+O (macOS では Cmd+Alt+O) を押して、現在のファイルのインポートを最適化します。

インポートオプティマイザは以下のアクションを実行します:

  • 重複する open 文の削除 --- 同じモジュールパスが複数の open 文に含まれている場合、最初の出現のみが保持されます

  • 順序の保持 --- 一意な open 文はファイル内の元の位置にそのまま残ります

  • 重複しない open の保持 --- オプティマイザは完全な重複のみを削除します。未使用の open は削除しません (コンパイラによるセマンティック分析が必要なため)

例:

// Before optimization
open Belt
open Belt.Array
open Belt
open Belt.Option
open Belt.Array

let x = [1, 2, 3]->Array.map(v => Some(v))
// After Ctrl+Alt+O
open Belt
open Belt.Array
open Belt.Option

let x = [1, 2, 3]->Array.map(v => Some(v))

オプティマイザ実行後、通知で結果が表示されます (例: オプティマイザの実行後、結果が通知として表示されます (例: "Removed 2 duplicate open statement(s)" または "No duplicate open statements found")。

冗長な open 文を手動で探して削除する代わりに、ショートカット一つですべての重複を一括クリーンアップでき、手間なくインポートを整理できます。

クイックフィックス (LSP)

LSP Required

Language Server は Alt+Enter (またはガター内の電球アイコン) を通じて自動コード修正を提供します。これらの LSP ベースのクイックフィックスは ReScript コンパイラのセマンティック分析に基づいて動作し、ローカルインスペクションでは対応できない状況を処理できます。

@rescript/language-server は標準の textDocument/codeAction を通じて以下の 9 種類のクイックフィックスをプラグインに提供します:

  • 不足しているパターンケースの追加 --- switch ブロックの末尾に未処理コンストラクタを挿入します (simpleAddMissingCases)

  • Some でラップ / option をアンラップ --- 周囲のコンテキストが option 型を期待している場合に値を Some(...) で包む、または option を取り出します (wrapInSome, unwrapOptional)

  • 不足しているレコードフィールドの追加 --- レコード型で必須にもかかわらずリテラルに含まれていないフィールドを補完します (addUndefinedRecordFields)

  • プリミティブ変換の挿入 --- int / float / string の型不整合が報告された際に、式を int_of_stringfloat_of_int などで包みます (simpleConversion)

  • 提案された名前への置換 --- Did you mean ...? でコンパイラが提案した値に、誤記された識別子を置換します (didYouMean)

  • 未使用コードの削除 --- reanalyze が未使用と判定した宣言を削除します (removeUnusedCode; rescript.jsonreanalyze ブロック有効化が必要)

  • ローカルモジュールをファイルに抽出 --- module M = {{ ... }} 宣言をプロジェクトルート直下の新規 M.res ファイルへ移動します (extractLocalModuleToFile)

  • catch-all パターンの展開 --- 明示的な _ => ケースをバリアントコンストラクタごとの分岐に展開します (expandCatchAllPatterns)

  • uncurried への変換 --- 旧来の ReScript v10 / v11 のコードベースで、curried 呼び出し箇所を uncurried 形式 f(. x) に変換します (applyUncurried; uncurried-by-default ビルドでは出力されません)

クイックフィックスは、カーソル位置のコンパイラ診断に基づいてコンテキストに応じて表示されます。Alt+Enter を押してすべての利用可能なアクションを確認するか、エディタガターに表示される電球アイコンをクリックしてください。

未解決参照のクイックフィックス

識別子を解決できない場合、プラグインはそれを解決するためのクイックフィックスを提供します:

  • open 文の追加 — 参照されているシンボルを含むモジュールの open 文を挿入します

  • モジュール修飾子の追加 — 識別子にフルモジュールパスのプレフィックスを付加します

例:

// "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)

使用箇所からの関数生成

まだ存在しない関数を呼び出した場合、プラグインはスタブ関数定義を生成できます。未解決の関数呼び出し上で Alt+Enter を押し、Generate function を選択してください。

let result = processData(input, config)
// processData is not defined
let processData = (input, config) => {
  todo
}

let result = processData(input, config)

型ホールのクイックフィックス

コンパイラが型ホール (型プレースホルダとして使用される _) を報告した場合、プラグインは埋めるべき候補型を提案します。型ホールの診断上で Alt+Enter を押すと、一致する型の提案が表示されます。

let parse: string => _ = jsonStr => {
  // compiler error: type hole found
}
let parse: string => JSON.t = jsonStr => {
  // type hole filled with suggested type
}

クイックフィックスはコンパイラの診断を解析して候補型を抽出し、それらを置換オプションとして提供します。

LSP クイックフィックスはコンパイラエラーをワンクリック修正に変換し、手動でコードを編集する代わりに、エラー箇所から直接型の不一致、不足しているインポート、不完全なパターンを解決できます。