# 機能設計書 18-Letへ移動

## 概要

本ドキュメントは、clojure-lspのLetへ移動（move-to-let）機能の設計を記述する。この機能はカーソル位置の式を既存のletバインディングに追加するリファクタリング機能である。

### 本機能の処理概要

Letへ移動機能は、式を既存のletに新しいバインディングとして追加し、元の位置をバインディングシンボルで置き換える。

**業務上の目的・背景**：Clojure開発において、既に存在するletに新しいバインディングを追加することは一般的なリファクタリングパターンである。本機能により、手動でコードを移動する手間を省き、素早く既存のletにバインディングを追加できる。

**機能の利用シーン**：
- 既存のletに計算結果を追加したい場合
- 同じ式が複数箇所で使用されていて、共通のバインディングにまとめたい場合
- let内の式を段階的に構築したい場合
- コードの構造を整理したい場合

**主要な処理内容**：
1. カーソル位置の式を特定
2. 有効なletフォームを探索
3. 既存のバインディング内で同じ式を置換
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） | 正の整数 |
| binding-name | string | Yes | 新しいバインディング名 | 有効なClojureシンボル |

### 入力データソース

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

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| range | Range | 編集対象の範囲 |
| newText | string | 更新されたletフォームを含むテキスト |

### 出力先

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

## 処理フロー

### 処理シーケンス

```
1. 対象式の特定
   └─ z/skip-whitespaceでカーソル位置の式を取得

2. 有効なletフォームの探索
   └─ find-let-formで移動先となるletを探索

3. 既存バインディング内の置換
   └─ replace-in-bind-valuesで同じ式をシンボルで置換

4. 新しいバインディングの追加
   └─ z/insert-left または z/append-child

5. ボディ内の同じ式の置換
   └─ z/prewalkで全ての同じ式を置換

6. 編集結果の生成
   └─ 範囲とloc（編集後のジッパー）を返却
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B[z/skip-whitespaceで式を特定]
    B --> C{式が存在?}
    C -->|No| D[nil返却]
    C -->|Yes| E[find-let-form探索]
    E --> F{有効なlet?}
    F -->|No| G[introduce-let + expand-let]
    F -->|Yes| H[bindings-locを取得]
    H --> I[replace-in-bind-values]
    I --> J[新バインディング追加]
    J --> K[ボディ内の同じ式を置換]
    K --> L[編集結果生成]
    G --> L
    L --> M[終了]
    D --> M
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 有効なlet探索 | 式のスコープを考慮して有効なletを探索 | widest-scoped-localを使用 |
| BR-02 | バインディング内置換 | 既存バインディングの値に同じ式があれば置換 | replace-in-bind-values |
| BR-03 | 重複バインディング | 最初に出現した式の直前にバインディングを追加 | mark-position-whenを使用 |
| BR-04 | let不在時 | 有効なletがない場合はintroduce-letとexpand-letを実行 | let-top-loc = nil |
| BR-05 | インデント | 既存バインディングの列位置に合わせてインデント | :col from meta |

### 計算ロジック

- バインディングの追加位置: 最初に置換が発生した位置の直前
- インデント幅: `(:col (meta (z/node bindings-loc)))` で取得

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

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ローカル変数検出 | :analysis | SELECT | widest-scoped-localで使用 |

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

#### :analysis

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

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| N/A | nil返却 | 対象式が見つからない | nilを返却（何もしない） |
| N/A | nil返却 | 有効なletが見つからずフォールバックも失敗 | nilを返却 |

### リトライ仕様

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

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

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

## パフォーマンス要件

- AST操作とローカル変数検出を含むが、通常のファイルサイズでは即時完了

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

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

## 備考

- 変換例:
  - 入力:
    ```clojure
    (let [a 1]
      (inc a))
    ```
  - 選択: `(inc a)`
  - 出力:
    ```clojure
    (let [a 1
          b (inc a)]
      b)
    ```
- 有効なletがない場合はintroduce-letを実行し、expand-letで適切なスコープまで拡張

---

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

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

### 推奨読解順序

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | command.clj | `lib/src/clojure_lsp/feature/command.clj` | run-command :move-to-let（104-105行目） |
| 1-2 | transform.clj | `lib/src/clojure_lsp/refactor/transform.clj` | move-to-let関数（340-382行目） |

**主要処理フロー**:
1. **343行目**: `z/skip-whitespace`で対象式を取得
2. **344行目**: `find-let-form`で有効なletを探索
3. **345-351行目**: letがある場合の処理
4. **377-381行目**: letがない場合のフォールバック

#### Step 2: 有効なlet探索を理解する

find-let-form関数のロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | transform.clj | `lib/src/clojure_lsp/refactor/transform.clj` | find-let-form関数（255-271行目） |
| 2-2 | transform.clj | `lib/src/clojure_lsp/refactor/transform.clj` | widest-scoped-local関数（231-244行目） |

**主要処理フロー**:
- **258-259行目**: `edit/find-ops-up "let"`でletを探索
- **262行目**: バインディング位置を取得
- **263行目**: `widest-scoped-local`で最も広いスコープのローカル変数を取得
- **264-270行目**: スコープの妥当性を検証

#### Step 3: バインディング追加処理を理解する

バインディングの追加と置換ロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | transform.clj | `lib/src/clojure_lsp/refactor/transform.clj` | replace-in-bind-values関数（213-229行目） |
| 3-2 | transform.clj | `lib/src/clojure_lsp/refactor/transform.clj` | move-to-let関数のバインディング追加部分（352-369行目） |

**主要処理フロー**:
- **213-229行目**: 既存バインディング値内の同じ式を置換
- **356-361行目**: 置換位置があればその直前にバインディングを追加
- **362-369行目**: 置換位置がなければ末尾にバインディングを追加

#### Step 4: ボディ内の置換を理解する

letボディ内の同じ式を置換するロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | transform.clj | `lib/src/clojure_lsp/refactor/transform.clj` | move-to-let関数のボディ置換部分（370-374行目） |

**主要処理フロー**:
- **370-374行目**: `loop`と`z/next`で全ての同じ式を探索して置換

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

```
handlers/execute-command
    │
    └─ f.command/call-command
           │
           └─ run-command :move-to-let
                  │
                  └─ r.transform/move-to-let
                         │
                         ├─ z/skip-whitespace
                         │
                         ├─ find-let-form
                         │      ├─ edit/find-ops-up "let"
                         │      └─ widest-scoped-local
                         │             └─ q/find-local-usages-defined-outside-form
                         │
                         ├─ (letある場合)
                         │      ├─ replace-in-bind-values
                         │      │      └─ find-within, z/replace
                         │      ├─ z/insert-left / z/append-child
                         │      └─ loop (ボディ置換)
                         │
                         └─ (letない場合)
                                ├─ introduce-let
                                └─ expand-let (繰り返し)
```

### データフロー図

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

zloc, uri, db ────────┬─> find-let-form ──────> let-top-loc
                      │
binding-name ─────────┼─> symbol変換 ─────────> binding-sym
                      │
zloc ─────────────────┼─> z/string ───────────> bound-string
                      │
let-top-loc ──────────┴─> replace-in-bind-values ──> with-binding
                              │
                              ├─> 同じ式をシンボルで置換
                              └─> バインディング追加
                                      │
                                      └─> loop (ボディ置換)
                                              │
                                              └─────────────> [{:range :loc}]
                                                                   │
                                                                   └─> WorkspaceEdit
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| transform.clj | `lib/src/clojure_lsp/refactor/transform.clj` | ソース | Letへ移動のメインロジック |
| 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` | ソース | ローカル変数検出クエリ |
| edit.clj | `lib/src/clojure_lsp/refactor/edit.clj` | ソース | ジッパー操作ユーティリティ |
| transform_test.clj | `lib/test/clojure_lsp/refactor/transform_test.clj` | テスト | 単体テスト |
