# 機能設計書 12-後置補完 (postfix)

## 概要

本ドキュメントは、Lua Language Serverにおける後置補完（Postfix Completion）機能の設計を記述する。この機能は、式の後に特定のキーワードを入力することで、その式を変換するコードスニペットを提供する。

### 本機能の処理概要

後置補完機能は、変数や式の直後にドット等の記号とキーワードを入力することで、その式を囲むコード構造を自動生成する機能である。例えば、`myTable.for`と入力すると`for k, v in pairs(myTable) do ... end`に展開される。

**業務上の目的・背景**：Luaプログラミングにおいて、ループ構造や関数定義、エラーハンドリングなどの定型パターンは頻繁に使用される。後置補完により、これらのパターンを素早く入力でき、タイピング量を削減し開発効率を向上させる。IDE/エディタの生産性機能として広く採用されている手法をLua開発にも適用する。

**機能の利用シーン**：
- テーブルをループ処理したい場合：`myTable.pairs` → `for k, v in pairs(myTable) do ... end`
- 関数を定義したい場合：`obj.myFunc.function` → `function obj.myFunc() ... end`
- エラーハンドリングを追加したい場合：`func().pcall` → `pcall(func)`
- ローカル変数に代入したい場合：`expr.local` → `local x = expr`

**主要な処理内容**：
1. カーソル位置の直前のソースノードを特定
2. 入力されたキーワードとアクション一覧をマッチング
3. マッチしたアクションに対応するスニペットを生成
4. 元の式を削除し、新しいコードスニペットで置換

**関連システム・外部連携**：
- コード補完システム（completion.lua）との統合
- エディタのスニペット機能（insertTextFormat: 2）との連携

**権限による制御**：
- `Lua.completion.postfix`設定により、後置補完のトリガー記号をカスタマイズ可能

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 2 | コード補完 | 補助機能 | 後置記法による補完処理 |

## 機能種別

コード生成 / コード補完補助

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| state | parser.state | Yes | 現在のファイルのパーサー状態 | nil不可 |
| position | integer | Yes | カーソル位置 | 正の整数 |
| results | table | Yes | 補完結果を格納するテーブル | テーブル |

### 入力データソース

- 現在編集中のLuaファイルのAST
- `Lua.completion.postfix`設定値（トリガー記号）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| label | string | 補完候補の表示ラベル |
| kind | integer | CompletionItemKind.Snippet |
| description | string | スニペット展開後のプレビュー（Markdown） |
| textEdit | object | 挿入位置と新しいテキスト |
| sortText | string | ソート順（postfix-NNNN形式） |
| insertTextFormat | integer | 2（スニペット形式） |
| additionalTextEdits | array | 元の式を削除するための編集 |

### 出力先

results配列に補完候補として追加される

## 処理フロー

### 処理シーケンス

```
1. 文字列内かどうかをチェック
   └─ 文字列内の場合は処理を中断
2. カーソル位置から後方に単語を検索
   └─ lookback.findWordで直前の単語を取得
3. トリガー記号をチェック
   └─ 設定された後置補完記号かどうか確認
4. 記号の直前のソースノードを特定
   └─ guide.eachSourceContainで該当ノードを検索
5. 各アクションとマッチング
   └─ 入力単語と各アクションのキーをmatchKey
6. アクション関数を実行
   └─ ソースノードの型をチェック
   └─ 該当する場合はスニペットを生成
7. 補完候補を結果に追加
   └─ textEdit、additionalTextEditsを設定
```

### フローチャート

```mermaid
flowchart TD
    A[補完開始] --> B{文字列内?}
    B -->|Yes| C[false を返す]
    B -->|No| D[後方検索で単語取得]
    D --> E{トリガー記号?}
    E -->|No| F{特殊記号 ++/++? ?}
    F -->|No| C
    F -->|Yes| G[特殊パターン処理]
    E -->|Yes| H[ソースノード特定]
    G --> H
    H --> I{ノード取得成功?}
    I -->|No| C
    I -->|Yes| J[各アクションをループ]
    J --> K{キーマッチ?}
    K -->|No| J
    K -->|Yes| L[アクション関数実行]
    L --> M{スニペット生成?}
    M -->|No| J
    M -->|Yes| N[補完候補追加]
    N --> O{全アクション処理完了?}
    O -->|No| J
    O -->|Yes| P[結果を返す]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 対象ノード型制限 | アクションごとに対象となるノード型が定義されている | 各アクション実行時 |
| BR-02 | 文字列内除外 | 文字列リテラル内では後置補完を無効化 | 補完開始時 |
| BR-03 | 非フィールド制限 | 一部のアクションは call/getindex/getmethod を含むノードでは無効 | function/method アクション |
| BR-04 | トリガー記号設定 | Lua.completion.postfix設定でトリガー記号を指定可能 | 補完開始時 |

### 計算ロジック

スニペット展開時のプレースホルダ処理:
```lua
-- プレースホルダを展開して説明文を生成
local descText = newText:gsub('%$%{%d+:([^}]+)%}', function (val)
    return val  -- ${1:value} → value
end):gsub('%$%{?%d+%}?', '')  -- $1 や ${1} を削除
```

## 登録済みアクション一覧

| キー | 対象ノード型 | 生成コード例 |
|------|-------------|-------------|
| function | getglobal, getfield, getlocal, local | `function name() ... end` |
| method | getfield, getmethod | `function obj:name() ... end` |
| pcall | getglobal, getfield, getmethod, getindex, getlocal, call | `pcall(func, args)` |
| xpcall | getglobal, getfield, getmethod, getindex, getlocal, call | `xpcall(func, debug.traceback, args)` |
| ifcall | getglobal, getfield, getmethod, getindex, getlocal, call | `if func then func() end` |
| local | getglobal, getfield, getmethod, getindex, getlocal, call, table | `local x = expr` |
| ipairs | getglobal, getfield, getmethod, getindex, getlocal, call, table | `for i, v in ipairs(t) do ... end` |
| pairs | getglobal, getfield, getmethod, getindex, getlocal, call, table | `for k, v in pairs(t) do ... end` |
| unpack | getglobal, getfield, getmethod, getindex, getlocal, call, table | `unpack(t)` |
| insert | getglobal, getfield, getmethod, getindex, getlocal, call, table | `table.insert(t, x)` |
| remove | getglobal, getfield, getmethod, getindex, getlocal, call, table | `table.remove(t, x)` |
| concat | getglobal, getfield, getmethod, getindex, getlocal, call, table | `table.concat(t, x)` |
| ++ | getglobal, getfield, getindex, getlocal | `x = x + 1` |
| ++? | getglobal, getfield, getindex, getlocal | `x = (x or 0) + 1` |

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

この機能はデータベースを使用しない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 無視 | ソースノードが取得できない | falseを返して補完を中断 |
| - | 無視 | 対象ノード型にマッチしない | そのアクションをスキップ |

### リトライ仕様

リトライは行わない。

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

該当なし

## パフォーマンス要件

- 13種類のアクションをループ処理するが、各アクションの処理は軽量
- スニペット生成は文字列操作のみで即座に完了
- 補完応答性に影響を与えない設計

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

特になし（コード補完機能のためセキュリティリスクは低い）

## 備考

- スニペットプレースホルダ（$1, ${1:default}等）を使用
- insertTextFormat: 2 はLSPのSnippet形式を示す
- additionalTextEditsにより元の式を削除し、textEditで新しいコードを挿入

---

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

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

### 推奨読解順序

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

後置補完のアクション定義構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | postfix.lua | `script/core/completion/postfix.lua` | actions配列（9行目）とregister関数（11-18行目） |
| 1-2 | postfix.lua | `script/core/completion/postfix.lua` | accepts テーブル（327-336行目）で対象ノード型を定義 |

**読解のコツ**: `register`関数はDSL的に使用されており、`register 'key' { function ... }`の形式でアクションを登録している。

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

メイン関数の構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | postfix.lua | `script/core/completion/postfix.lua` | メイン関数（381-413行目）がモジュールの戻り値 |

**主要処理フロー**:
1. **382-384行目**: 文字列内チェック
2. **385-390行目**: カーソル位置から後方検索で単語とオフセットを取得
3. **391-396行目**: トリガー記号のチェックとcheckPostFix呼び出し
4. **397-411行目**: 特殊パターン（++、++?）の処理

#### Step 3: 各アクションの実装を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | postfix.lua | `script/core/completion/postfix.lua` | function アクション（33-49行目） |
| 3-2 | postfix.lua | `script/core/completion/postfix.lua` | pcall アクション（76-104行目） |
| 3-3 | postfix.lua | `script/core/completion/postfix.lua` | pairs アクション（205-221行目） |

**主要処理フロー**:
- **各アクション**: ソースの型をチェックし、該当すればcallbackでスニペットを返す
- **hasNonFieldInNode関数（20-31行目）**: call/getindex/getmethodを含むかチェック

#### Step 4: 補完候補生成を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | postfix.lua | `script/core/completion/postfix.lua` | checkPostFix関数（338-379行目） |

**主要処理フロー**:
- **339-344行目**: ソースノードの特定
- **348-378行目**: 各アクションをループし、マッチした場合は補完候補を生成
- **351-354行目**: プレースホルダを展開して説明文を生成
- **360-375行目**: 補完候補オブジェクトの構築

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

```
completion.lua (補完メイン)
    │
    └─ postfix.lua (モジュール関数)
           │
           ├─ guide.isInString() [文字列内チェック]
           ├─ lookback.findWord() [後方単語検索]
           ├─ config.get('Lua.completion.postfix') [設定取得]
           │
           └─ checkPostFix()
                  │
                  ├─ guide.eachSourceContain() [ソースノード特定]
                  ├─ matchKey() [キーワードマッチング]
                  │
                  └─ 各アクション関数
                         │
                         ├─ hasNonFieldInNode() [型チェック]
                         └─ subString() [部分文字列取得]
```

### データフロー図

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

カーソル位置(position) ──┐
                        │
パーサー状態(state) ─────┼──▶ postfix() ──▶ results[]
                        │         │
                        │         ▼
                        │   ┌────────────────┐
                        │   │ 後方単語検索    │
                        │   └────────────────┘
                        │         │
                        │         ▼
                        │   ┌────────────────┐
                        │   │ ソースノード特定 │
                        │   └────────────────┘
                        │         │
                        │         ▼
                        │   ┌────────────────┐
                        │   │ アクションマッチ │
                        │   └────────────────┘
                        │         │
                        │         ▼
                        │   ┌────────────────┐
                        │   │ スニペット生成   │
                        │   └────────────────┘
                        │         │
                        │         ▼
                        └─ [補完候補オブジェクト]
                              - label
                              - kind (Snippet)
                              - textEdit
                              - additionalTextEdits
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| postfix.lua | `script/core/completion/postfix.lua` | ソース | 後置補完のメイン実装 |
| completion.lua | `script/core/completion/completion.lua` | ソース | 補完機能のメインモジュール、postfixを呼び出す |
| look-backward.lua | `script/core/look-backward.lua` | ソース | カーソル後方検索ユーティリティ |
| substring.lua | `script/core/substring.lua` | ソース | 部分文字列取得ユーティリティ |
| matchkey.lua | `script/core/matchkey.lua` | ソース | キーワードマッチング処理 |
| guide.lua | `script/parser/guide.lua` | ソース | AST操作ユーティリティ |
| define.lua | `script/proto/define.lua` | ソース | CompletionItemKind等の定数定義 |
| markdown.lua | `script/provider/markdown.lua` | ソース | Markdown生成ユーティリティ |
