# プログラム設計書 22-rename

## 1. 概要

### このプログラムの目的

このプログラムは、Luaコード内のシンボル（変数名、関数名、フィールド名など）を一括して名前変更（リネーム）するためのものです。ユーザーが変数名を変更すると、そのシンボルが使用されている全ての場所を自動的に検出し、一括で変更します。

### このプログラムの役割

- **呼び出し元**: LSPプロトコルの `textDocument/rename` リクエストハンドラ
- **主な処理**: 指定されたシンボルの全参照箇所を検出し、新しい名前に変更するためのテキスト編集情報を生成
- **出力**: テキスト編集のリスト（変更位置と新しいテキスト）

## 2. ファイル基本情報

| 項目 | 内容 |
|-----|------|
| ファイルパス | `script/core/rename.lua` |
| ファイル名 | rename.lua |
| 種別 | ソースコード |
| 使用言語 | Lua |
| 総行数 | 467行 |
| 作成目的 | シンボルのリネーム機能を提供する |

## 3. 用語解説

このプログラムを理解するために必要な専門用語を解説します。

| 用語 | 読み方 | 意味・説明 |
|-----|--------|----------|
| Rename | リネーム | 名前を変更すること。プログラミングでは変数名や関数名を一括変更する機能 |
| Reference | リファレンス | 参照。変数が使用されている場所のこと |
| Local Variable | ローカル変数 | その関数やブロック内でのみ有効な変数 |
| Global Variable | グローバル変数 | プログラム全体からアクセスできる変数 |
| Field | フィールド | テーブル（オブジェクト）のプロパティ |
| Method | メソッド | テーブル（オブジェクト）に属する関数。コロン記法（obj:method()）で定義される |
| LuaDoc | ルアドック | Luaのドキュメンテーションコメント。`---@` で始まるアノテーション |

### 言語固有の概念

- **setglobal/getglobal**: グローバル変数への代入/参照を表すASTノードの種類
- **setlocal/getlocal**: ローカル変数への代入/参照を表すASTノードの種類
- **setfield/getfield**: テーブルフィールドへの代入/参照（`obj.field`形式）
- **setmethod/getmethod**: メソッドの定義/呼び出し（`obj:method()`形式）
- **setindex/getindex**: テーブルのインデックスアクセス（`obj["key"]`形式）

## 4. 依存関係

### このプログラムが使用している外部ファイル

```
rename.lua
    │
    ├─ require 'files' ............. ファイル管理モジュール
    │
    ├─ require 'vm' ................ 仮想マシン（型推論・参照解決）
    │
    ├─ require 'utility' ........... ユーティリティ関数
    │
    ├─ require 'core.find-source' .. ソース検索モジュール
    │
    ├─ require 'parser.guide' ...... AST操作ユーティリティ
    │
    └─ require 'config' ............ 設定管理
```

| 参照ファイル | パス | 用途 |
|------------|------|------|
| files | `script/files.lua` | ファイルテキストとASTの取得 |
| vm | `script/vm/` | シンボルの参照解決 |
| utility | `script/utility.lua` | 文字列処理など汎用関数 |
| find-source | `script/core/find-source.lua` | カーソル位置のソース検索 |
| guide | `script/parser/guide.lua` | AST走査・位置変換 |
| config | `script/config/` | Unicode名の設定確認 |

## 5. データ構造詳細

### 主要な変数・データ項目

#### リネーム結果オブジェクト（424-429行目）

| 項目名 | 型 | 用途・説明 |
|-------|--------|----------|
| start | integer | 変更開始位置（テキスト内の文字位置） |
| finish | integer | 変更終了位置 |
| text | string | 新しいテキスト |
| uri | string | 対象ファイルのURI |

**解説**: リネーム操作の結果として返される各編集箇所の情報です。どのファイルのどの位置をどのテキストに変更するかを示します。

#### accept テーブル（364-387行目）

| 項目名 | 型 | 用途・説明 |
|-------|--------|----------|
| 'label' | boolean | ラベルをリネーム対象として許可 |
| 'local' | boolean | ローカル変数定義を許可 |
| 'setlocal' | boolean | ローカル変数への代入を許可 |
| 'getlocal' | boolean | ローカル変数の参照を許可 |
| その他 | boolean | 各種ASTノードタイプを許可 |

**解説**: リネーム可能なシンボルの種類を定義するテーブルです。このテーブルに含まれる種類のノードのみがリネーム対象として認識されます。

## 6. 処理詳細解説

このプログラムの処理内容をステップバイステップで説明します。

### 6.1 処理一覧

| No | 処理名 | 行番号 | 概要 |
|----|-------|--------|------|
| 1 | isValidName | 19-29 | 名前の妥当性チェック |
| 2 | isValidGlobal | 34-44 | グローバル名の妥当性チェック |
| 3 | renameLocal | 73-79 | ローカル変数のリネーム |
| 4 | renameField | 81-125 | フィールドのリネーム |
| 5 | renameGlobal | 127-145 | グローバル変数のリネーム |
| 6 | ofLocal | 147-163 | ローカル変数の全参照をリネーム |
| 7 | ofField | 207-213 | フィールドの全参照をリネーム |
| 8 | ofGlobal | 216-229 | グローバル変数の全参照をリネーム |
| 9 | rename | 285-325 | リネーム処理の振り分け |
| 10 | m.rename | 392-442 | リネームのメインエントリ |

### 6.2 isValidName の詳細（19-29行目）

**この処理は何をするものか**: 新しい変数名が有効なLua識別子かどうかをチェックします。

**処理の流れ**:

```
ステップ1: null/空文字チェック
    │
    ↓
ステップ2: Unicode設定確認
    │
    ↓
ステップ3: 正規表現でパターンマッチ
```

**ステップバイステップ解説**:

1. **ステップ1: null/空文字チェック**（20-22行目）

   ```lua
   if not str then
       return false
   end
   ```

   まず、渡された文字列が存在するかをチェックします。nil（未定義）の場合は無効と判定します。

2. **ステップ2: Unicode設定確認**（23行目）

   ```lua
   local allowUnicode = config.get(uri, 'Lua.runtime.unicodeName')
   ```

   設定から、Unicode文字を変数名に使用できるかどうかを確認します。

3. **ステップ3: パターンマッチ**（24-28行目）

   - もしUnicodeが許可されている場合: `^[%a_\x80-\xff][%w_\x80-\xff]*$` というパターン
   - そうでなければ: `^[%a_][%w_]*$` というパターン

   これらのパターンは「先頭が英字またはアンダースコア、続いて英数字またはアンダースコア」という Lua の識別子規則をチェックします。

**この処理のポイント**:
- Luaの変数名は数字で始められません
- Unicode対応はオプショナルで、設定で切り替え可能

### 6.3 renameField の詳細（81-125行目）

**この処理は何をするものか**: テーブルのフィールド（`obj.field`形式）をリネームします。無効な名前の場合は `obj["field"]` 形式に自動変換します。

**処理の流れ**:

```
ステップ1: 名前の妥当性をチェック
    │
    ↓（有効な名前の場合）
ステップ2: そのままリネーム
    │
    ↓（無効な名前の場合）
ステップ3: 親ノードの種類を判定
    │
    ↓
ステップ4: 適切な形式に変換してリネーム
```

**ステップバイステップ解説**:

1. **ステップ1-2: 有効な名前の場合**（82-85行目）

   ```lua
   if isValidName(guide.getUri(source), newname) then
       callback(source, source.start, source.finish, newname)
       return true
   end
   ```

   新しい名前が有効なLua識別子なら、単純に名前を置換します。

2. **ステップ3: setfield/getfield の場合**（87-91行目）

   ```lua
   if parent.type == 'setfield'
   or parent.type == 'getfield' then
       local dot = parent.dot
       local newstr = '[' .. util.viewString(newname) .. ']'
       callback(source, dot.start, source.finish, newstr)
   ```

   `obj.field` 形式の場合、ドット（.）から始まる部分を `["newname"]` 形式に変換します。これにより、特殊文字を含む名前でもアクセス可能になります。

3. **ステップ4: setmethod の特殊処理**（97-123行目）

   メソッド定義（`function obj:method() end`）の場合は特に複雑です：

   ```lua
   -- function mt:name () end --> mt['newname'] = function (self) end
   ```

   この変換では：
   - メソッド構文をプロパティ構文に変換
   - 暗黙の `self` 引数を明示的な引数として追加

**この処理のポイント**:
- コロン記法（`obj:method()`）からドット記法（`obj.method(self)`）への変換
- 無効な識別子は自動的にブラケット記法に変換される

### 6.4 ofLocal の詳細（147-163行目）

**この処理は何をするものか**: ローカル変数とその全ての参照箇所をリネームします。

**処理の流れ**:

```
ステップ1: 定義位置をリネーム
    │
    ↓
ステップ2: 全参照をリネーム
    │
    ↓
ステップ3: 関連するLuaDocもリネーム
```

**ステップバイステップ解説**:

1. **ステップ1: 定義位置をリネーム**（148行目）

   ```lua
   renameLocal(source, newname, callback)
   ```

   まず変数の定義位置（`local x`の部分）をリネームします。

2. **ステップ2: 全参照をリネーム**（149-153行目）

   ```lua
   if source.ref then
       for _, ref in ipairs(source.ref) do
           renameLocal(ref, newname, callback)
       end
   end
   ```

   変数の全参照箇所（`source.ref`に格納）をループして、それぞれリネームします。

3. **ステップ3: LuaDocのリネーム**（154-162行目）

   もし変数が関数の引数で、`---@param`アノテーションがある場合、そのパラメータ名もリネームします。

**この処理のポイント**:
- `source.ref`には、その変数への全参照が配列として格納されています
- LuaDocアノテーションも連動して更新されます

### 6.5 m.rename の詳細（392-442行目）

**この処理は何をするものか**: リネーム機能のメインエントリポイントです。カーソル位置のシンボルを特定し、リネーム処理を実行します。

**処理の流れ**:

```
ステップ1: パラメータの検証
    │
    ↓
ステップ2: ASTの取得
    │
    ↓
ステップ3: カーソル位置のソースを検索
    │
    ↓
ステップ4: リネーム処理を実行
    │
    ↓
ステップ5: 結果をフィルタリング
    │
    ↓
ステップ6: 結果を返す
```

**ステップバイステップ解説**:

1. **ステップ1-2: 初期検証**（393-401行目）

   ```lua
   if not newname then
       return nil
   end
   local ast = files.getState(uri)
   if not ast then
       return nil
   end
   ```

   新しい名前が指定されているか、対象ファイルのASTが存在するかをチェックします。

2. **ステップ3: ソースの検索**（400-403行目）

   ```lua
   local source = findSource(ast, pos, accept)
   if not source then
       return nil
   end
   ```

   `accept`テーブルで定義された種類のノードを、指定位置から検索します。

3. **ステップ4: リネーム処理**（404-430行目）

   ```lua
   rename(source, newname, function (target, start, finish, text)
       -- ...編集情報を収集...
       results[#results+1] = {
           start  = start,
           finish = finish,
           text   = text,
           uri    = turi,
       }
   end)
   ```

   `rename`関数を呼び出し、コールバックで各編集情報を収集します。

4. **ステップ5: フィルタリング**（417-423行目）

   - メタファイル（定義ファイル）への変更はスキップ
   - ライブラリファイルへの変更は、編集対象もライブラリの場合のみ許可
   - 重複する編集はスキップ

**この処理のポイント**:
- `mark`テーブルで重複編集を防止
- ライブラリファイルの変更を制限することで、意図しない変更を防止

## 7. 処理フロー図

プログラム全体の処理の流れを図で示します。

```mermaid
flowchart TD
    A[リネーム開始] --> B{パラメータ検証}
    B -->|無効| Z[nullを返す]
    B -->|有効| C[ASTを取得]
    C --> D{AST存在?}
    D -->|No| Z
    D -->|Yes| E[カーソル位置のソースを検索]
    E --> F{ソース発見?}
    F -->|No| Z
    F -->|Yes| G{ソースの種類}
    G -->|local| H[ofLocal]
    G -->|field| I[ofField]
    G -->|global| J[ofGlobal]
    G -->|その他| K[対応する処理]
    H --> L[結果を収集]
    I --> L
    J --> L
    K --> L
    L --> M[フィルタリング]
    M --> N[結果を返す]
```

**図の説明**:
- **リネーム開始**: ユーザーがリネームを実行（F2キー等）
- **パラメータ検証**: 新しい名前が有効かチェック
- **ソースの種類**: シンボルの種類によって処理を分岐
- **結果を返す**: テキスト編集のリストをLSPクライアントに返却

## 8. 外部連携

### 呼び出し関係図

```
[呼び出し元]                    [このプログラム]                    [呼び出し先]
                                     │
provider.lua ──────────────▶ rename.lua ─────────────────▶ vm.getRefs()
  (textDocument/rename)              │                        (参照の取得)
                                     │
                                     └──────────────▶ files.getState()
                                                        (AST取得)
```

### 呼び出し元（このプログラムを呼び出すもの）

| プログラム名 | 呼び出し契機 | 渡されるデータ |
|------------|------------|--------------|
| provider.lua | ユーザーがリネームを実行 | URI、位置（pos）、新しい名前（newname） |

### 呼び出し先（このプログラムが呼び出すもの）

| プログラム名 | 呼び出し目的 | 渡すデータ | 受け取るデータ |
|------------|------------|----------|--------------|
| vm.getRefs | シンボルの全参照を取得 | ソースノード | 参照のリスト |
| vm.getGlobal | グローバル変数情報を取得 | 種類、名前 | グローバル変数オブジェクト |
| files.getState | ファイルのAST取得 | URI | AST |
| files.getText | ファイルのテキスト取得 | URI | テキスト文字列 |

## 9. エラー処理

このプログラムで発生する可能性があるエラーと、その対処方法を説明します。

### エラー一覧

| エラー種別 | 発生条件 | 対処内容 | 該当行 |
|----------|---------|---------|--------|
| 名前なし | newname が nil | null を返す | 393-395 |
| ASTなし | ファイルが未読み込み | null を返す | 396-398 |
| ソースなし | カーソル位置にシンボルがない | null を返す | 400-403 |
| 結果なし | リネーム対象が見つからない | null を返す | 437-439 |

### エラー発生時の動作

このプログラムは例外を投げません。エラー状態の場合は `nil` を返し、エディタ側で「リネームできません」などのメッセージが表示されます。

## 10. このプログラムを読むときのポイント

このプログラムを理解するためのアドバイスです。

### 推奨する読み方

1. **まず m.rename（392-442行目）を見る**: 全体の処理の流れが分かります
2. **次に rename（285-325行目）を見る**: シンボル種類による分岐が分かります
3. **最後に ofLocal/ofField/ofGlobal を見る**: 具体的なリネームロジックが分かります

### 注意すべきポイント

- `callback` パターンが多用されています。結果を直接返すのではなく、コールバック関数を呼び出して結果を通知します
- `:` 記法（メソッド）から `.` 記法への変換時、`self` 引数が自動追加されます
- `util.viewString` は文字列をLuaのリテラル形式（引用符付き）に変換します

### 関連して読むべきファイル

| ファイル | パス | 関連性 |
|---------|------|--------|
| reference.lua | `script/core/reference.lua` | 参照検索の仕組みが分かります |
| find-source.lua | `script/core/find-source.lua` | カーソル位置のシンボル検索の仕組み |
| vm.lua | `script/vm/` | 参照解決の仕組みが分かります |

## 11. 備考

- このファイルには `m.rename` と `m.prepareRename` の2つのエクスポート関数があります
- `m.prepareRename` は No.23 で別途解説します
- `Forcing` 変数（8行目）は強制リネームフラグですが、現在は使用されていない可能性があります
