# 通知設計書 3-textDocument/publishDiagnostics

## 概要

本ドキュメントは、serve-d LSPサーバーからクライアントへ送信される `textDocument/publishDiagnostics` 通知の設計を記述する。この通知はLSP標準仕様に準拠し、テキストドキュメントの診断情報（エラー・警告等）をクライアントに公開するために使用される。

### 本通知の処理概要

`textDocument/publishDiagnostics` 通知は、serve-dサーバーが静的解析（D-Scanner）、Dubビルドエラー、dfmtフォーマットエラーの3つの診断プロバイダから収集した診断結果を統合し、クライアントに送信する通知である。diagnosticmanager.dのupdateDiagnostics()関数で送信が行われ、同一URIに対する複数プロバイダの診断結果がマージされて1つの通知として送信される。

**業務上の目的・背景**：D言語のソースコード開発において、リアルタイムのエラー検出と警告表示は開発生産性に直結する。serve-dは3つの異なる診断プロバイダ（D-Scanner静的解析、Dubビルドエラー、dfmtフォーマットエラー）の結果を統合管理し、エディタ上にインラインで表示することで、ユーザーがコード品質の問題を即座に把握できるようにする。

**通知の送信タイミング**：ドキュメントの変更時（didChange）、保存時（didSave）、開いた時（didOpen、設定による）にリント処理が実行され、その結果として診断情報が更新されるたびに送信される。

**通知の受信者**：LSPクライアント（VSCode等のエディタ）が受信し、エディタ上にインライン診断マーカー（波線等）として表示する。

**通知内容の概要**：ドキュメントURI、診断情報の配列（位置、重要度、メッセージ、ソース等）で構成される。

**期待されるアクション**：ユーザーが診断情報を確認し、コードの問題箇所を修正する。

## 通知種別

LSP標準通知（サーバー -> クライアント）/ エディタ内インライン診断表示

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（JSON-RPC通知） |
| 優先度 | 中 |
| リトライ | 無し |

### 送信先決定ロジック

変更されたドキュメントのURIに対して送信される。uriHintパラメータが指定された場合は該当URIのみ、指定されない場合は全ての統合診断結果が送信される。

## 通知テンプレート

### LSP通知の場合

| 項目 | 内容 |
|-----|------|
| メソッド名 | `textDocument/publishDiagnostics` |
| パラメータ型 | `PublishDiagnosticsParams` |
| 形式 | JSON-RPC 2.0 通知 |

### 本文テンプレート

```json
{
  "jsonrpc": "2.0",
  "method": "textDocument/publishDiagnostics",
  "params": {
    "uri": "file:///path/to/file.d",
    "diagnostics": [
      {
        "range": {
          "start": {"line": 0, "character": 0},
          "end": {"line": 0, "character": 10}
        },
        "severity": 1,
        "message": "診断メッセージ"
      }
    ]
  }
}
```

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| uri | 診断対象のドキュメントURI | PublishDiagnosticsParams.uri | Yes |
| diagnostics | 診断情報の配列 | 3つの診断プロバイダの統合結果 | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| ドキュメント変更 | textDocument/didChange | D言語ファイルかつリンティング有効 | D-Scannerによる静的解析結果を送信 |
| ドキュメント変更 | textDocument/didChange | D言語ファイル | dfmtによるフォーマット診断結果を送信 |
| ドキュメント保存 | textDocument/didSave | D/Dietファイルかつリンティング有効 | D-Scanner + Dubリント結果を送信 |
| ドキュメント開放 | textDocument/didOpen | lintOnFileOpen設定が"always"またはプロジェクト内ファイル | didChangeハンドラに委譲して診断実行 |
| ドキュメント閉鎖 | textDocument/didClose | 外部プロジェクトのファイル | 診断情報をクリアして送信 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| enableLinting=false | d.enableLintingがfalseの場合、全リンティングが無効化される |
| enableStaticLinting=false | D-Scannerによる静的解析が無効化される |
| enableDubLinting=false | Dubによるビルドエラー検出が無効化される |
| D言語以外のファイル | getLanguageId()が"d"でない場合、リンティングが実行されない |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[ドキュメント変更/保存イベント] --> B{リンティング有効?}
    B -->|有効| C[各診断プロバイダ実行]
    B -->|無効| Z[送信スキップ]
    C --> D[D-Scanner静的解析]
    C --> E[Dubビルドエラー検出]
    C --> F[dfmtフォーマットチェック]
    D --> G[diagnostics配列に格納]
    E --> G
    F --> G
    G --> H[updateDiagnostics呼び出し]
    H --> I[combineDiagnostics - 3プロバイダの結果統合]
    I --> J[URI毎にJSON-RPC通知を送信]
    J --> K[古い診断のクリア通知送信]
    K --> L[publishedUris更新]
    L --> M[終了]
```

## データベース参照・更新仕様

該当なし（全てインメモリ管理）

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| diagnostics[3] | 3つの診断プロバイダの結果を保持 | DiagnosticCollection[NumDiagnosticProviders]型のモジュールレベル変数 |
| combinedDiagnostics | 統合された診断結果 | DiagnosticCollection型のモジュールレベル変数 |
| publishedUris | 前回送信済みのURI一覧 | DocumentUri[]型。古い診断のクリアに使用 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 送信失敗 | RPC接続が切断されている | 上位で処理 |
| リンター実行エラー | D-Scanner/Dub/dfmtの実行失敗 | 各リンターモジュール内で個別にハンドリング |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0（リトライなし） |
| リトライ間隔 | 該当なし |
| リトライ対象エラー | 該当なし |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし（ただしdidChangeではタイマーによる遅延あり） |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし。ただし、didChangeハンドラではファイルサイズに応じた遅延が設定される（50KiB以下: 200ms、50KiB超: 1000ms for D-Scanner）。

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

診断メッセージにはソースコードの断片やファイルパスが含まれる場合がある。個人情報は含まれない。

## 備考

- NumDiagnosticProviders=3（D-Scanner、Dub、dfmt）
- updateDiagnostics()は直接rpc.send()を呼び出してJSON-RPCパケットを構築する（notifyMethodではなく、RequestMessageRawを直接使用）
- 診断のクリアはdiagnostics配列がnullのPublishDiagnosticsParamsを送信することで行われる
- publishedUrisにより、以前送信したURIで現在は診断がないものに対してクリア通知が送信される

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | diagnosticmanager.d | `source/served/linters/diagnosticmanager.d` | NumDiagnosticProviders=3の定義（9行目）、diagnostics配列（11行目）、combinedDiagnostics（13行目）、publishedUris（14行目） |

**読解のコツ**: diagnostics[3]は固定長3のスロットであり、各リンターが専用のスロットに結果を格納する。DiagnosticCollectionはPublishDiagnosticsParams[]のエイリアス。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | diagnosticmanager.d | `source/served/linters/diagnosticmanager.d` | updateDiagnostics()関数（59行目〜89行目）。combineDiagnostics()で3つのプロバイダ結果を統合し、URI毎にRPC通知を送信。古い診断のクリアも行う |

**主要処理フロー**:
1. **61行目**: combineDiagnostics()呼び出し - 3つのプロバイダ結果を統合
2. **62行目〜71行目**: 統合結果をURI毎にJSON-RPC通知として送信
3. **74行目〜86行目**: 前回送信済みで今回結果がないURIに対してクリア通知を送信
4. **87行目〜88行目**: publishedUrisを更新

#### Step 3: 診断統合処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | diagnosticmanager.d | `source/served/linters/diagnosticmanager.d` | combineDiagnostics()関数（16行目〜37行目）。3つのプロバイダの結果を同一URIでマージする |
| 3-2 | diagnosticmanager.d | `source/served/linters/diagnosticmanager.d` | createDiagnosticsFor()テンプレート関数（43行目〜51行目）。各リンターが診断結果を格納するためのヘルパー |

#### Step 4: 呼び出し元（リンター）を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | extension.d | `source/served/extension.d` | onDidChangeDocument（1148行目）でD-Scannerリントとdfmtリントを呼び出し |
| 4-2 | extension.d | `source/served/extension.d` | onDidSaveDocument（1316行目）でD-ScannerリントとDubリントを呼び出し |
| 4-3 | extension.d | `source/served/extension.d` | onDidCloseDocument（1129行目）で外部プロジェクトファイルの診断クリア |

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

```
[extension.d: onDidChangeDocument / onDidSaveDocument]
    |
    +-- dscanner.lint(document)          [linters/dscanner.d]
    |       +-- createDiagnosticsFor!0   [diagnosticmanager.d:43]
    |       +-- updateDiagnostics()      [diagnosticmanager.d:59]
    |
    +-- dub.lint(document)               [linters/dub.d]
    |       +-- createDiagnosticsFor!1   [diagnosticmanager.d:43]
    |       +-- updateDiagnostics()      [diagnosticmanager.d:59]
    |
    +-- dfmt.lint(document)              [linters/dfmt.d]
            +-- createDiagnosticsFor!2   [diagnosticmanager.d:43]
            +-- updateDiagnostics()      [diagnosticmanager.d:59]
                    |
                    +-- combineDiagnostics()      [diagnosticmanager.d:16]
                    +-- rpc.send(request)          (各URI毎に通知送信)
```

### データフロー図

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

D-Scannerリント結果     -->  diagnostics[0]に格納            -->
Dubリント結果           -->  diagnostics[1]に格納            -->  combineDiagnostics()
dfmtリント結果          -->  diagnostics[2]に格納            -->       |
                                                                      v
                             combinedDiagnostics              -->  updateDiagnostics()
                                                                      |
                                                                      v
                             rpc.send(PublishDiagnosticsParams) --> stdout(クライアントへ)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| diagnosticmanager.d | `source/served/linters/diagnosticmanager.d` | ソース | 診断結果の統合管理と送信（中核モジュール） |
| dscanner.d | `source/served/linters/dscanner.d` | ソース | D-Scanner静的解析リンター |
| dub.d | `source/served/linters/dub.d` | ソース | Dubビルドエラーリンター |
| dfmt.d | `source/served/linters/dfmt.d` | ソース | dfmtフォーマットリンター |
| extension.d | `source/served/extension.d` | ソース | didChange/didSave/didClose/didOpenハンドラからのリンター呼び出し |
| protocol.d | `lsp/source/served/lsp/protocol.d` | ソース | PublishDiagnosticsParams・Diagnostic等のLSPプロトコル型定義 |
