# 機能設計書 16-関数抽出

## 概要

本ドキュメントは、clojure-lspの関数抽出（extract-function）機能の設計を記述する。この機能は選択された式を新しいプライベート関数として抽出し、元の位置を関数呼び出しに置き換えるリファクタリング機能である。

### 本機能の処理概要

関数抽出機能は、コードの一部を独立した関数として切り出し、コードの再利用性と可読性を向上させる。

**業務上の目的・背景**：長い関数や複雑なロジックは保守性が低下する。本機能により、開発者は特定の処理を独立した関数として抽出でき、コードの構造化と再利用性の向上が可能となる。

**機能の利用シーン**：
- 長い関数を分割したい場合
- 繰り返し使用される処理を関数化したい場合
- テストしやすい単位にコードを分割したい場合
- コードの責務を明確に分離したい場合

**主要な処理内容**：
1. 選択範囲内の式を特定
2. 式内で参照されている外部変数（ローカル）を検出
3. 新しいdefn-形式の関数を生成
4. 元の位置を関数呼び出しに置き換え
5. スレッドマクロ内の場合は適切に処理

**関連システム・外部連携**：clj-kondoの解析結果を使用してローカル変数の使用状況を検出する。

**権限による制御**：特になし。すべてのユーザーが利用可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| N/A | エディタ | 主画面 | LSPクライアント（VS Code等）からコードアクションまたはコマンドとして実行 |

## 機能種別

リファクタリング / コード抽出

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| uri | string | Yes | 対象ファイルのURI | file://形式のURI |
| row | integer | Yes | 選択開始位置の行番号（1-indexed） | 正の整数 |
| col | integer | Yes | 選択開始位置の列番号（1-indexed） | 正の整数 |
| fn-name | string | Yes | 新しい関数名 | 有効なClojureシンボル |
| end-row | integer | No | 選択終了位置の行番号 | 正の整数 |
| end-col | integer | No | 選択終了位置の列番号 | 正の整数 |

### 入力データソース

- LSPクライアントからのworkspace/executeCommandリクエスト（extract-functionコマンド）
- コードアクションから実行

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| range | Range | 編集対象の範囲（複数） |
| newText | string | 新しい関数定義と関数呼び出し |

### 出力先

- LSPクライアントへWorkspaceEditとして返却

## 処理フロー

### 処理シーケンス

```
1. 選択範囲の式を特定
   └─ find-zlocs-in-rangeで選択された式を取得

2. エラーチェック
   └─ 同一親レベル、マクロ抽出不可等のバリデーション

3. 外部参照ローカル変数の検出
   └─ q/find-local-usages-defined-outside-formで検出

4. 新しい関数の生成
   └─ new-defn-zlocでdefn-形式を生成

5. 関数呼び出しの生成
   └─ 関数名とパラメータで呼び出し式を生成

6. 編集結果の生成
   └─ 新関数追加と元位置の置換を返却
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B[選択範囲の式を特定]
    B --> C{有効な選択?}
    C -->|No| D[エラー返却]
    C -->|Yes| E[外部参照変数を検出]
    E --> F{スレッドコンテキスト?}
    F -->|Yes| G[スレッド用ボディ生成]
    F -->|No| H[通常ボディ生成]
    G --> I[new-defn-zloc生成]
    H --> I
    I --> J[関数呼び出し生成]
    J --> K[編集結果生成]
    K --> L[終了]
    D --> L
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | プライベート関数 | 抽出される関数は常にプライベート（defn-）として生成 | 常に |
| BR-02 | 外部参照パラメータ | 選択範囲外で定義されたローカル変数を関数パラメータとして追加 | ローカル変数が存在する場合 |
| BR-03 | 同一レベル制約 | 選択範囲の開始と終了は同一の親を持つ必要がある | 常に |
| BR-04 | マクロ抽出不可 | スレッドマクロ（->、->>等）自体は抽出できない | thread-symbolsに含まれる |
| BR-05 | スレッドコンテキスト | スレッドマクロ内の式を抽出する場合、適切にスレッド形式を維持 | threaded-context? = true |
| BR-06 | 関数配置 | 新しい関数は現在のトップレベルフォームの直前に配置 | 常に |

### 計算ロジック

- パラメータ名: 外部参照ローカル変数の:nameを使用
- コメント処理: 抽出される式に含まれるコメントも保持

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

本機能はデータベース（db）を読み取り専用で使用する。

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ローカル変数検出 | :analysis | SELECT | find-local-usages-defined-outside-formで使用 |

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

#### :analysis

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | :local-usages | 指定範囲外で定義されたもの | パラメータ生成に使用 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| :invalid-params | No expressions to extract | 選択範囲に式がない | エラーメッセージを返却 |
| :invalid-params | Expressions must be at the same level | 異なる親レベルの式を選択 | エラーメッセージを返却 |
| :invalid-params | Can't extract a macro | スレッドマクロを抽出しようとした | エラーメッセージを返却 |

### リトライ仕様

特になし。同期的に1回のみ実行。

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

- クライアントに編集結果を返却するのみで、サーバー側でのトランザクション管理は不要

## パフォーマンス要件

- ローカル変数の検出にclj-kondo解析結果を使用
- 通常のファイルサイズでは即時完了

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

- ファイルシステムへの直接書き込みは行わない（LSP経由でクライアントに委譲）

## 備考

- 変換例:
  - 入力:
    ```clojure
    (defn foo [x]
      (let [y 1]
        (+ x y)))
    ```
  - 選択: `(+ x y)`
  - 出力:
    ```clojure
    (defn- new-function [x y]
      (+ x y))

    (defn foo [x]
      (let [y 1]
        (new-function x y)))
    ```
- :use-metadata-for-privacy?設定がtrueの場合は`^:private`メタデータを使用

---

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

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

### 推奨読解順序

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

処理の起点となる関数を特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | command.clj | `lib/src/clojure_lsp/feature/command.clj` | run-command :extract-function（80-81行目） |
| 1-2 | transform.clj | `lib/src/clojure_lsp/refactor/transform.clj` | extract-function関数（723-748行目） |

**主要処理フロー**:
1. **729行目**: `find-zlocs-in-range`で選択された式を取得
2. **730行目**: `check-for-errors`でバリデーション
3. **736-738行目**: `q/find-local-usages-defined-outside-form`で外部参照を検出
4. **745行目**: `new-defn-zloc`で新関数を生成

#### Step 2: 選択範囲の式を特定する処理を理解する

find-zlocs-in-range関数のロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | transform.clj | `lib/src/clojure_lsp/refactor/transform.clj` | find-zlocs-in-range関数（515-549行目） |

**主要処理フロー**:
- **521行目**: 選択有無の判定（single-cursor?）
- **523行目**: next-expr-startで次の式を探索
- **528行目**: トークン上またはnilの場合は親を取得
- **545-548行目**: 選択範囲内の式を収集

#### Step 3: 新関数生成処理を理解する

new-defn-zloc関数のロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | transform.clj | `lib/src/clojure_lsp/refactor/transform.clj` | new-defn-zloc関数（384-407行目） |

**主要処理フロー**:
- **385-394行目**: defn/defn-/^:private形式の選択
- **396-401行目**: パラメータベクターの追加
- **402-406行目**: ボディ式の追加

#### Step 4: スレッドコンテキスト処理を理解する

スレッドマクロ内の式を抽出する場合の特別処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | transform.clj | `lib/src/clojure_lsp/refactor/transform.clj` | threaded-context?関数（598-600行目） |
| 4-2 | transform.clj | `lib/src/clojure_lsp/refactor/transform.clj` | create-threaded-body関数（685-710行目） |

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

```
handlers/execute-command
    │
    └─ f.command/call-command
           │
           └─ run-command :extract-function
                  │
                  └─ r.transform/extract-function
                         │
                         ├─ find-zlocs-in-range
                         │      ├─ single-cursor?
                         │      ├─ next-expr-start
                         │      └─ locate-parent-expr
                         │
                         ├─ check-for-errors
                         │      ├─ all-ignorable?
                         │      └─ different-parents?
                         │
                         ├─ q/find-local-usages-defined-outside-form
                         │
                         ├─ threaded-context?
                         │      └─ create-threaded-body (if true)
                         │
                         ├─ new-defn-zloc
                         │      └─ interpose-newlines
                         │
                         └─ prepend-preserving-comment
```

### データフロー図

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

row, col, end-row, end-col
    │
    └─> find-zlocs-in-range ──> selected-expressions
                                      │
uri, db[:analysis] ──────────────────┼─> find-local-usages ──> invoked-params
                                      │
selected-expressions ────────────────┼─> body-nodes
                                      │
fn-name, invoked-params, body-nodes ─┴─> new-defn-zloc ──> defn形式
                                      │
                                      └─> 関数呼び出し生成
                                              │
                                              └─────────────> [{:range :loc}, ...]
                                                                   │
                                                                   └─> WorkspaceEdit
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| transform.clj | `lib/src/clojure_lsp/refactor/transform.clj` | ソース | 関数抽出のメインロジック |
| command.clj | `lib/src/clojure_lsp/feature/command.clj` | ソース | コマンドディスパッチャー |
| code_actions.clj | `lib/src/clojure_lsp/feature/code_actions.clj` | ソース | コードアクション提供 |
| queries.clj | `lib/src/clojure_lsp/queries.clj` | ソース | ローカル変数検出クエリ |
| transform_test.clj | `lib/test/clojure_lsp/refactor/transform_test.clj` | テスト | 単体テスト |
