# 機能設計書 10-ドキュメント保存 (textDocument/didSave)

## 概要

本ドキュメントは、serve-d LSPサーバーにおけるドキュメント保存 (textDocument/didSave) 機能の設計を記述する。テキストドキュメントの保存通知を処理し、DScanner・DUBリント・DUBレシピ更新・インデックス再構築・ユニットテスト検出を実行する。

### 本機能の処理概要

LSPプロトコルの `textDocument/didSave` 通知を処理する。この通知は複数のハンドラが登録されており、保存されたファイルの種類に応じて異なる処理が実行される。D言語ファイルの場合はDScanner静的解析とDUBビルドリントを並列実行し、DUBレシピファイル（dub.json/dub.sdl）の場合はDUB依存関係の更新を行う。さらにインデックスの再構築やユニットテストの再スキャンもトリガーされる。

**業務上の目的・背景**：ファイル保存は、開発者が「区切りの良いところまで編集した」というシグナルである。このタイミングで包括的な解析を実行することで、編集中のデバウンス付きリントでは検出しきれないビルドレベルのエラーや、DUB依存関係の変更を反映できる。保存時の解析は編集中の解析より重い処理を含むが、頻度が低いため許容される。

**機能の利用シーン**：エディタでファイルを保存（Ctrl+S等）した際に自動的に通知される。

**主要な処理内容**：
1. D言語/dietファイルの場合: DScanner静的解析とDUBビルドリントを並列実行
2. DUBレシピファイル（dub.json/dub.sdl）の場合: DUB依存関係の更新とDUBツリー更新通知
3. インデックスの再構築（reindexOnSave）
4. ユニットテストの再スキャン（onDidSaveCheckForUnittest、--provide test-runner時のみ）

**関連システム・外部連携**：LSPクライアント（publishDiagnostics通知、coded/updateDubTree通知）。DScanner。DUBビルドシステム。dfmt。IndexComponent。テストプロバイダ。

**権限による制御**：特に権限制御は存在しない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 13 | DUBツリー更新通知 | 補助機能 | ファイル保存時にDUBレシピの変更を検出しDUBツリー更新通知を送信 |
| 36 | DUB ビルドリント | 補助機能 | テキスト保存時にDUBビルドリントがトリガーされる |
| 69 | テストプロバイダー | 補助機能 | ファイル保存時にユニットテストの再スキャンがトリガーされる |

## 機能種別

イベントハンドリング（複合保存時処理）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| params | DidSaveTextDocumentParams | Yes | 保存されたテキストドキュメント情報 | LSPプロトコル仕様に準拠 |
| params.textDocument.uri | DocumentUri | Yes | ドキュメントのURI | file://形式 |

### 入力データソース

LSPクライアントからのJSON-RPC通知（メソッド名: "textDocument/didSave"）

## 出力仕様

### 出力データ

通知処理のため、レスポンスは返却しない。各種リント結果はtextDocument/publishDiagnostics通知として送信される。

### 出力先

- 診断マネージャ経由でLSPクライアントへpublishDiagnostics通知
- DUBレシピ変更時: coded/updateDubTree通知
- テストプロバイダ: coded/pushProjectTests通知

## 処理フロー

### 処理シーケンス

```
1. メインハンドラ（onDidSaveDocument - extension.d）
   a. ワークスペースルートと設定の取得
   b. ドキュメントの取得とファイル名抽出
   c. D言語/dietファイルの場合:
      └─ enableLintingチェック
      └─ joinAllで並列実行:
         ├─ enableStaticLinting → DScanner lint + dscannerChangeTimeoutクリア
         └─ enableDubLinting → DUB lint
2. DUBレシピハンドラ（onDidSaveDubRecipe - dub.d）
   a. ファイル名がdub.json/dub.sdlか判定
   b. DUBコンポーネントの存在確認（なければアタッチ試行）
   c. レシピ構文チェック
   d. DUBアップグレード実行
   e. coded/updateDubTree通知送信
3. インデックスハンドラ（reindexOnSave - index.d）
   a. D言語ファイルかつenableIndex設定チェック
   b. reindexChangeTimeoutをクリア
   c. IndexComponent.reindexSavedで再インデックス
   d. delayedSaveIndexで2.5秒後にインデックス保存
4. テストハンドラ（onDidSaveCheckForUnittest - test_provider.d）
   a. doTrackTests設定チェック（--provide test-runnerが必要）
   b. .dファイルか判定
   c. ファイバーでrescanFileを実行
   d. coded/pushProjectTests通知送信
```

### フローチャート

```mermaid
flowchart TD
    A[didSave通知受信] --> B[onDidSaveDocument]
    A --> C[onDidSaveDubRecipe]
    A --> D[reindexOnSave]
    A --> E[onDidSaveCheckForUnittest]

    B --> B1{D/dietファイル?}
    B1 -->|Yes| B2{enableLinting?}
    B1 -->|No| B9[スキップ]
    B2 -->|Yes| B3[DScanner lint 並列]
    B2 -->|Yes| B4[DUB lint 並列]
    B2 -->|No| B9

    C --> C1{dub.json/dub.sdl?}
    C1 -->|Yes| C2[DUBアップグレード]
    C1 -->|No| C3[スキップ]
    C2 --> C4[coded/updateDubTree通知]

    D --> D1{D言語 && enableIndex?}
    D1 -->|Yes| D2[reindexSaved]
    D1 -->|No| D3[スキップ]

    E --> E1{doTrackTests && .d?}
    E1 -->|Yes| E2[rescanFile]
    E1 -->|No| E3[スキップ]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | D/dietファイルリント | D言語ファイルまたはdietテンプレートファイルの場合にリントを実行 | getLanguageId == "d" or "diet" |
| BR-02 | DScanner + DUB並列実行 | DScannerリントとDUBリントはjoinAllで並列実行される | enableLinting=true |
| BR-03 | DScannerタイマークリア | 保存時DScanner実行後、didChange由来のデバウンスタイマーをクリアする | enableStaticLinting=true |
| BR-04 | DUBレシピ更新 | dub.json/dub.sdlファイル保存時にDUBアップグレードと依存関係更新を実行 | ファイル名がdub.json/dub.sdlの場合 |
| BR-05 | インデックス再構築 | D言語ファイル保存時にIndexComponentで再インデックスを実行し、2.5秒後にディスク保存 | enableIndex=true |
| BR-06 | テスト再スキャン | --provide test-runner指定時に.dファイル保存でユニットテストを再スキャン | doTrackTests=true |

### 計算ロジック

特になし。

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

本機能ではデータベース操作は発生しない。IndexComponentのインデックスはインメモリで管理され、delayedSaveIndexで2.5秒後にファイルシステムに保存される。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| d.ext.dubUpgradeFail | エラー | DUBコンポーネントのアタッチに失敗した場合 | showMessage(error)で通知 |
| d.ext.dubInvalidRecipeSyntax | エラー | DUBレシピファイルの構文が無効な場合 | showMessage(error)で構文エラーを通知 |

### リトライ仕様

リトライは行わない。次回保存時に再実行される。

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

トランザクションは存在しない。各ハンドラは独立して実行される。

## パフォーマンス要件

- DScannerとDUBリントはjoinAllで並列実行され、処理時間の短縮を図っている。
- インデックスの保存はdelayedSaveIndexで2.5秒のデバウンスを持ち、連続保存時の不要なI/Oを抑制。
- DUBレシピ更新時はreportProgressで進捗を通知し、ユーザーに処理状況を伝える。

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

特になし。

## 備考

- textDocument/didSave通知は複数のファイル（extension.d, dub.d, index.d, test_provider.d）で個別にハンドラが登録されており、LSPルーターが全てのハンドラを呼び出す。
- initialize時のTextDocumentSyncOptionsでsave.includeText=falseに設定されているため、保存通知にテキスト本文は含まれない。
- DUBレシピ更新処理（dub.d 234-行目）はレシピの構文チェック、DUBアップグレード、インポートパス更新、coded/updateDubTree通知の一連の処理を含む。

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | types.d | `source/served/types.d` | Configuration.D: enableLinting（86行目）、enableStaticLinting（88行目）、enableDubLinting（89行目）、enableIndex（93行目） |

#### Step 2: メインハンドラを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | extension.d | `source/served/extension.d` | onDidSaveDocument関数（1316-1345行目）: @protocolNotification("textDocument/didSave")メインハンドラ |

**主要処理フロー**:
1. **1319-1322行目**: ワークスペースルート・設定・ドキュメント・ファイル名の取得
2. **1324行目**: D言語/dietファイル判定
3. **1326行目**: enableLintingチェック
4. **1328-1343行目**: joinAllでDScannerリントとDUBリントを並列実行

#### Step 3: DUBレシピ更新ハンドラを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | dub.d | `source/served/commands/dub.d` | onDidSaveDubRecipe関数（234行目〜）: DUBレシピファイル保存時の更新処理 |

**主要処理フロー**:
- **237-239行目**: ファイル名がdub.json/dub.sdlか判定
- **246-257行目**: DUBコンポーネント存在確認。なければアタッチ試行
- **260-269行目**: レシピ構文チェック
- **324行目**: coded/updateDubTree通知送信

#### Step 4: インデックス更新ハンドラを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | index.d | `source/served/commands/index.d` | reindexOnSave関数（96-114行目）: 保存時インデックス再構築 |

**主要処理フロー**:
- **99-103行目**: D言語判定とenableIndex設定チェック
- **106行目**: reindexChangeTimeoutクリア（didChange由来の遅延を解除）
- **111行目**: IndexComponent.reindexSavedで再インデックス
- **112行目**: delayedSaveIndex（117-123行目）で2.5秒後にインデックスファイル保存

#### Step 5: テストプロバイダハンドラを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | test_provider.d | `source/served/commands/test_provider.d` | onDidSaveCheckForUnittest関数（48-69行目）: ユニットテスト再スキャン |

**主要処理フロー**:
- **51-52行目**: doTrackTestsフラグチェック
- **54-55行目**: .dファイル判定
- **61-68行目**: ファイバーでrescanFileとcoded/pushProjectTests通知を実行

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

```
textDocument/didSave 通知
    |
    +-- onDidSaveDocument(extension.d 1316行目)
    |       +-- [D/diet] joinAll
    |           +-- [enableStaticLinting] served.linters.dscanner.lint()
    |           +-- [enableDubLinting]    served.linters.dub.lint()
    |
    +-- onDidSaveDubRecipe(dub.d 234行目)
    |       +-- [dub.json/dub.sdl]
    |           +-- DubComponent.validateRecipeSyntaxOnFileSystem()
    |           +-- DubComponent.upgrade()
    |           +-- rpc.notifyMethod("coded/updateDubTree")
    |
    +-- reindexOnSave(index.d 96行目)
    |       +-- [D言語 && enableIndex]
    |           +-- IndexComponent.reindexSaved()
    |           +-- delayedSaveIndex() [2.5秒後]
    |
    +-- onDidSaveCheckForUnittest(test_provider.d 48行目)
            +-- [doTrackTests && .d]
                +-- rescanFile()
                +-- rpc.notifyMethod("coded/pushProjectTests")
```

### データフロー図

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

DidSaveTextDocumentParams ──────> onDidSaveDocument()
  .textDocument.uri       ──────>   DScanner lint ───────────────────> publishDiagnostics
                                    DUB lint      ───────────────────> publishDiagnostics
                          ──────> onDidSaveDubRecipe()
                                    DUB upgrade   ───────────────────> coded/updateDubTree
                          ──────> reindexOnSave()
                                    IndexComponent.reindexSaved ─────> インデックスファイル
                          ──────> onDidSaveCheckForUnittest()
                                    rescanFile    ───────────────────> coded/pushProjectTests
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| extension.d | `source/served/extension.d` | ソース | onDidSaveDocument関数の実装（1316-1345行目） |
| dub.d | `source/served/commands/dub.d` | ソース | onDidSaveDubRecipe関数の実装（DUBレシピ更新） |
| index.d | `source/served/commands/index.d` | ソース | reindexOnSave関数の実装（インデックス再構築） |
| test_provider.d | `source/served/commands/test_provider.d` | ソース | onDidSaveCheckForUnittest関数の実装（テスト再スキャン） |
| dscanner.d | `source/served/linters/dscanner.d` | ソース | DScanner静的解析リンター |
| dub.d | `source/served/linters/dub.d` | ソース | DUBビルドリンター |
| diagnosticmanager.d | `source/served/linters/diagnosticmanager.d` | ソース | 診断結果の管理と配信 |
| types.d | `source/served/types.d` | ソース | Configuration設定値の定義 |
