# 機能設計書 70-スタブ生成

## 概要

本ドキュメントは、Clojure LSPにおけるスタブ生成機能の設計を定義する。この機能は、動的に生成されるコード（マクロ展開結果など）のスタブを生成し、clj-kondoで解析することで、補完やナビゲーションの精度を向上させる。

### 本機能の処理概要

**業務上の目的・背景**：Clojureのマクロは、コンパイル時にコードを動的に生成する。しかし、clj-kondoのような静的解析ツールはマクロ展開結果を直接理解できない。例えば、compojure、spec、honeysqlなどのDSLライブラリは、マクロで多くのコードを生成するため、補完や参照検索が機能しない問題がある。スタブ生成機能は、これらのライブラリの実行時情報からスタブファイルを生成し、静的解析可能にする。

**機能の利用シーン**：
- プロジェクト起動時に指定された名前空間のスタブを生成
- DSLライブラリ（compojure-api、spec、honeysqlなど）の補完を有効化
- 外部スタブディレクトリの解析

**主要な処理内容**：
1. :stubs :generation :namespaces で指定された名前空間を読み込む
2. clj-easy/stub ライブラリでスタブファイルを生成
3. 生成されたスタブファイルをclj-kondoで解析
4. 解析結果をキャッシュに保存

**関連システム・外部連携**：clj-easy/stub ライブラリ、clj-kondo解析、ファイルシステム（スタブ出力）。

**権限による制御**：:stubs 設定でスタブ生成の対象を制御。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 該当なし | 該当なし | 該当なし | 本機能はLSPサーバー起動時に自動実行される |

## 機能種別

コード生成処理 / 解析処理 / 初期化処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| :stubs :generation :namespaces | set | No | スタブ生成対象の名前空間 | 名前空間シンボルのセット |
| :stubs :generation :output-dir | string | No | スタブ出力ディレクトリ（デフォルト: .lsp/.cache/stubs） | 有効なパス |
| :stubs :generation :java-command | string | No | Javaコマンド（デフォルト: java） | 実行可能コマンド |
| :stubs :extra-dirs | vector | No | 追加の外部スタブディレクトリ | パスの配列 |

### 入力データソース

- 設定ファイル（.lsp/config.edn）
- クラスパス（依存ライブラリ情報）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| スタブファイル | .clj | 生成されたスタブ定義ファイル |
| 解析結果 | map | スタブの解析情報 |

### 出力先

- スタブ出力ディレクトリ（.lsp/.cache/stubs/）
- 内部DB（:analysis に統合）
- ローカルキャッシュ

## 処理フロー

### 処理シーケンス

```
1. スタブ生成が必要か判定
   └─ 名前空間リストが設定されており、前回と異なる場合
2. 既存のスタブディレクトリを削除
   └─ 古いスタブをクリーンアップ
3. clj-easy/stub でスタブを生成
   └─ クラスパス上のライブラリを実行して定義を収集
4. スタブファイルをclj-kondoで解析
   └─ :external? true で外部解析として処理
5. 解析結果をキャッシュに保存
   └─ db/read-and-update-cache! でローカルキャッシュに保存
6. extra-dirs が指定されている場合
   └─ 追加ディレクトリも同様に解析
```

### フローチャート

```mermaid
flowchart TD
    A[generate-and-analyze-stubs!] --> B{スタブ生成が必要?}
    B -->|No| C{extra-dirs あり?}
    B -->|Yes| D[既存スタブディレクトリ削除]
    D --> E[generate-stubs!]
    E --> F{生成成功?}
    F -->|No| G[エラーログ出力]
    F -->|Yes| H[analyze-stubs!]
    C -->|Yes| H
    C -->|No| I[終了]
    H --> J[clj-kondo 解析]
    J --> K[キャッシュに保存]
    K --> I
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 差分生成 | 前回と名前空間リストが同じ場合はスキップ | full-scan-analysis-startup が false |
| BR-02 | ディレクトリ削除 | 再生成前に既存スタブを削除 | スタブ生成時 |
| BR-03 | 外部解析モード | スタブは :external? true で解析 | 常時 |
| BR-04 | namespace-usages 除外 | スタブの解析結果から namespace-usages を除外 | 常時 |

### 計算ロジック

**スタブ生成判定**:
```clojure
(and (seq namespaces)
     (or (:full-scan-analysis-startup db)
         (not= namespaces (:stubs-generation-namespaces db))))
```

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 解析結果マージ | db[:analysis] | UPDATE | スタブ解析結果を統合 |
| キャッシュ更新 | ローカルキャッシュ | WRITE | 解析結果を保存 |

### テーブル別操作詳細

#### db[:analysis]

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | スタブファイルの解析結果 | 全バケット | lsp.kondo/db-with-results 経由 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| result-code 1 | 生成エラー | スタブ生成時に例外発生 | エラーログ出力 |
| result-code 2 | クラスパスなし | クラスパスが見つからない | エラーログ出力 |

### リトライ仕様

リトライは実装されていない。次回起動時に再試行。

## トランザクション仕様

- swap! によるアトミック更新
- ファイルシステム操作は非アトミック

## パフォーマンス要件

- スタブ生成: 名前空間数に依存（数秒〜数十秒）
- キャッシュからのロード: 数秒以内

## セキュリティ考慮事項

- スタブ生成はJavaプロセスを起動してライブラリを実行するため、信頼できないコードへのリスクがある

## 備考

- clj-easy/stub ライブラリ: https://github.com/clj-easy/stub
- 設定ドキュメント: https://clojure-lsp.io/settings/

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

スタブ設定と出力の構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | stubs.clj | `lib/src/clojure_lsp/feature/stubs.clj` | stubs-output-dir（18-20行目） |

**読解のコツ**: スタブは {:stubs {:generation {:namespaces [...] :output-dir "..."}}} 形式で設定される。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | handlers.clj | `lib/src/clojure_lsp/handlers.clj` | initialize 後の非同期タスク（195-197行目） |

**主要処理フロー**:
- **195-197行目**: check-stubs? で設定を確認し、generate-and-analyze-stubs! を呼び出し

#### Step 3: コア機能を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | stubs.clj | `lib/src/clojure_lsp/feature/stubs.clj` | generate-stubs!（29-47行目） |
| 3-2 | stubs.clj | `lib/src/clojure_lsp/feature/stubs.clj` | analyze-stubs!（49-61行目） |
| 3-3 | stubs.clj | `lib/src/clojure_lsp/feature/stubs.clj` | generate-and-analyze-stubs!（63-78行目） |
| 3-4 | stubs.clj | `lib/src/clojure_lsp/feature/stubs.clj` | check-stubs?（80-82行目） |

**主要処理フロー**:
- **29-47行目**: generate-stubs!
  - クラスパスを取得
  - clj-easy/stub の generate! を呼び出し
  - 出力ディレクトリにスタブファイルを生成
- **49-61行目**: analyze-stubs!
  - run-kondo-on-paths! でスタブを解析
  - :external? true, :filter-analysis でnamespace-usagesを除外
  - db-with-results でDBに統合
  - キャッシュに保存
- **63-78行目**: generate-and-analyze-stubs!
  - スタブ生成が必要か判定
  - generate-stubs! と analyze-stubs! を順次実行
  - extra-dirs がある場合は analyze-stubs! のみ実行
- **80-82行目**: check-stubs?
  - スタブ設定が有効か判定

### プログラム呼び出し階層図

```
handlers.clj::initialize (非同期タスク)
    │
    ├─ stubs.clj::check-stubs? (設定確認)
    │
    └─ stubs.clj::generate-and-analyze-stubs!
           │
           ├─ generate-stubs!
           │      │
           │      ├─ delete-directory-recursive (既存スタブ削除)
           │      │
           │      └─ stub/generate! (clj-easy/stub 呼び出し)
           │
           └─ analyze-stubs!
                  │
                  ├─ kondo.clj::run-kondo-on-paths!
                  │      │
                  │      └─ {:external? true
                  │          :filter-analysis #(dissoc % :namespace-usages)}
                  │
                  ├─ lsp.kondo/db-with-results
                  │
                  └─ db/read-and-update-cache!
```

### データフロー図

```
[入力]                         [処理]                              [出力]

設定:                      generate-and-analyze-stubs!
:stubs :generation              │
:namespaces                     ▼
:extra-dirs           スタブ生成が必要か判定
        │                      │
        │                      ▼
        │              generate-stubs!
        │                      │
        │                      ▼
        │              stub/generate!
        │                      │
        │                      ▼
        │              スタブファイル (.lsp/.cache/stubs/)
        │                      │
        │                      ▼
        │              analyze-stubs!
        │                      │
        │                      ▼
        │              run-kondo-on-paths! (:external? true)
        │                      │
        │           ┌──────────┴──────────┐
        │           ▼                     ▼
        │      db[:analysis]         ローカルキャッシュ
        │      にマージ              に保存
        │
        └──── extra-dirs ────▶ analyze-stubs!
                                   │
                                   └─▶ (同様の処理)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| stubs.clj | `lib/src/clojure_lsp/feature/stubs.clj` | ソース | スタブ生成のコアロジック |
| handlers.clj | `lib/src/clojure_lsp/handlers.clj` | ソース | 初期化時の呼び出し |
| startup.clj | `lib/src/clojure_lsp/startup.clj` | ソース | 起動時のタスク管理 |
| kondo.clj | `lib/src/clojure_lsp/kondo.clj` | ソース | clj-kondo解析 |
| db.clj | `lib/src/clojure_lsp/db.clj` | ソース | キャッシュ管理 |
