# 帳票設計書 6-textDocument/publishDiagnostics

## 概要

本ドキュメントは、Lua Language Serverが送信するLSP通知`textDocument/publishDiagnostics`の帳票設計を定義する。この帳票は、Luaソースコードの診断結果をエディタへリアルタイムにプッシュ通知するJSON形式のメッセージである。

### 本帳票の処理概要

本帳票は、LSP（Language Server Protocol）に準拠した診断結果通知であり、ファイル編集時やワークスペース診断時に、検出された問題をエディタに通知する。

**業務上の目的・背景**：エディタとLanguage Server間の連携において、コードの問題をリアルタイムにユーザーに通知するために必要である。構文エラー、型エラー、未使用変数、非推奨APIの使用など、コード品質に関する問題を即座にフィードバックする。

**帳票の利用シーン**：ファイル編集中のリアルタイム診断表示、ファイル保存時の診断更新、ワークスペース全体の診断実行時、またファイルオープン時の初期診断表示に利用される。

**主要な出力内容**：
1. 診断対象ファイルのURI
2. ファイルバージョン（オプション）
3. 診断オブジェクトの配列（位置、重大度、コード、メッセージ、関連情報）

**帳票の出力タイミング**：ファイル変更検出時、ファイルオープン時、ファイル保存時、ワークスペース診断実行時にエディタへプッシュ通知される。

**帳票の利用者**：エディタ（VSCode、Neovim等のLSPクライアント）、開発者（エディタ経由で閲覧）

## 帳票種別

LSP通知（診断結果プッシュ）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| LSP | エディタ連携 | `textDocument/publishDiagnostics` | 自動通知（ファイル変更時） |
| LSP | エディタ連携 | `textDocument/publishDiagnostics` | 自動通知（ファイル保存時） |
| LSP | エディタ連携 | `textDocument/publishDiagnostics` | 自動通知（ワークスペース診断時） |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | JSON-RPC通知 |
| 用紙サイズ | N/A |
| 向き | N/A |
| ファイル名 | N/A（ネットワーク通信） |
| 出力方法 | LSPプロトコル通知（JSON-RPC） |
| 文字コード | UTF-8 |

### LSP通知構造

| 項目 | 内容 |
|-----|------|
| method | `textDocument/publishDiagnostics` |
| params | PublishDiagnosticsParams |

## 帳票レイアウト

### レイアウト概要

JSON-RPC通知としてPublishDiagnosticsParamsを送信する。

```json
{
  "jsonrpc": "2.0",
  "method": "textDocument/publishDiagnostics",
  "params": {
    "uri": "file:///path/to/file.lua",
    "version": 1,
    "diagnostics": [
      {
        "range": {
          "start": {"line": 0, "character": 0},
          "end": {"line": 0, "character": 10}
        },
        "severity": 1,
        "code": "undefined-global",
        "source": "Lua Diagnostics.",
        "message": "Undefined global `foo`.",
        "tags": [],
        "data": "...",
        "relatedInformation": [...]
      }
    ]
  }
}
```

### ヘッダー部（params直下）

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | uri | 診断対象ファイルのURI | params.uri | file:///形式 |
| 2 | version | ファイルバージョン | files.getVersion(uri) | 整数（オプション） |

### 明細部（diagnostics配列）

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | range.start.line | 問題開始行（0-indexed） | converter.packRange() | 整数 |
| 2 | range.start.character | 問題開始列（0-indexed） | converter.packRange() | 整数 |
| 3 | range.end.line | 問題終了行（0-indexed） | converter.packRange() | 整数 |
| 4 | range.end.character | 問題終了列（0-indexed） | converter.packRange() | 整数 |
| 5 | severity | 重大度 | define.DiagnosticSeverity | 1=Error, 2=Warning, 3=Info, 4=Hint |
| 6 | code | 診断コード | diag.code | 文字列（例: undefined-global） |
| 7 | source | 診断ソース | lang.script.DIAG_DIAGNOSTICS/DIAG_SYNTAX_CHECK | 文字列 |
| 8 | message | 診断メッセージ | diag.message | 文字列 |
| 9 | tags | 診断タグ | diag.tags | DiagnosticTag[]（オプション） |
| 10 | data | 追加データ | diag.data | 任意（オプション） |
| 11 | relatedInformation | 関連情報 | diag.related | DiagnosticRelatedInformation[]（オプション） |

### DiagnosticRelatedInformation

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | location.uri | 関連ファイルURI | rel.uri | file:///形式 |
| 2 | location.range | 関連位置 | converter.packRange() | Range |
| 3 | message | 関連メッセージ | rel.message | 文字列 |

### フッター部

N/A（JSON構造のためフッターなし）

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| 診断有効 | Lua.diagnostics.enable = true | Yes |
| ファイル存在 | files.exists(uri) = true | Yes |
| ワークスペース準備完了 | ws.isReady(uri) = true | Yes |
| スキーム対象 | Lua.diagnostics.enableScheme に含まれる | Yes |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | なし | 診断検出順 |

### 改ページ条件

N/A（LSP通知のため改ページなし）

## データベース参照仕様

### 参照テーブル一覧

本帳票はデータベースを使用せず、診断エンジンの解析結果を参照する。

| データソース | 用途 | 取得方法 |
|-------------|------|---------|
| files.getState(uri) | AST取得 | ファイル管理 |
| files.getVersion(uri) | バージョン取得 | ファイル管理 |
| core.diagnostics | 診断実行 | 診断エンジン |
| m.cache[uri] | 診断キャッシュ | 診断プロバイダー |

### テーブル別参照項目詳細

#### 診断結果

| 参照項目 | 帳票項目との対応 | 取得条件 | 備考 |
|---------|----------------|---------|------|
| diag.start/finish | range | なし | converter.packRangeで変換 |
| diag.level | severity | なし | 数値に変換 |
| diag.code | code | なし | 診断種別識別子 |
| diag.message | message | なし | 診断メッセージ |
| diag.tags | tags | なし | オプション |
| diag.related | relatedInformation | なし | オプション |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| 位置変換 | converter.packRange(state, start, finish) | N/A | 内部位置からLSP位置へ |
| 重大度 | define.DiagnosticSeverity[level] | N/A | 文字列から数値へ |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[ファイル変更検出] --> B{診断有効?}
    B -->|No| Z[終了]
    B -->|Yes| C{ワークスペース準備完了?}
    C -->|No| Z
    C -->|Yes| D[await.delay]
    D --> E[files.getState取得]
    E --> F{AST取得成功?}
    F -->|No| G[m.clear呼び出し]
    G --> Z
    F -->|Yes| H[構文エラー検出]
    H --> I[診断エンジン実行]
    I --> J[各診断をbuildDiagnosticで変換]
    J --> K[診断結果マージ]
    K --> L{キャッシュと同一?}
    L -->|Yes| Z
    L -->|No| M[キャッシュ更新]
    M --> N[proto.notify呼び出し]
    N --> O[JSON-RPC送信]
    O --> Z
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| ファイルなし | files.exists(uri) = false | diagnosticsを空配列で送信 | 正常動作（クリア） |
| AST取得失敗 | files.getState(uri) = nil | diagnosticsを空配列で送信 | 正常動作（クリア） |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | ファイルあたり数十〜数百診断 |
| 目標出力時間 | ファイル編集後500ms以内に通知 |
| 同時出力数上限 | Hint/Information診断は10000件まで制限 |

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

- 診断メッセージにはソースコードの一部が含まれる可能性があるため、ネットワーク経由の場合は暗号化を検討
- file://スキームのURIが外部に露出しないよう注意

## 備考

- `m.clear(uri)`で診断をクリア（空の診断配列を送信）
- 診断結果はキャッシュされ、変更がない場合は再送信しない
- `Lua.diagnostics.workspaceDelay`で診断遅延を設定可能
- `Lua.diagnostics.workspaceEvent`で診断トリガーを設定可能（OnChange/OnSave）
- Hint/Information診断は10000件を超えると切り捨てられる

---

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

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

### 推奨読解順序

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

LSP診断のデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | define.lua | `script/proto/define.lua` | DiagnosticSeverity定義 |
| 1-2 | diagnostic.lua | `script/provider/diagnostic.lua` | buildDiagnostic関数（86-123行目）で出力構造を理解 |
| 1-3 | diagnostic.lua | `script/provider/diagnostic.lua` | buildSyntaxError関数（36-84行目）で構文エラー構造を理解 |

**読解のコツ**: LSP仕様のDiagnostic構造に準拠。range, severity, code, source, message, tags, relatedInformationが主要フィールド。

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

通知送信のトリガーを特定。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | diagnostic.lua | `script/provider/diagnostic.lua` | files.watch（678-705行目）でファイルイベント監視 |
| 2-2 | diagnostic.lua | `script/provider/diagnostic.lua` | m.refresh関数（450-464行目）で診断リフレッシュ |

**主要処理フロー**:
1. **678-705行目**: ファイルイベント監視（create/update/remove/open/close/save）
2. **682-683行目**: remove時にm.clear()とm.refresh()呼び出し
3. **688-691行目**: update時にm.stopScopeDiag()とm.refresh()呼び出し
4. **450-464行目**: m.refresh()でm.doDiagnostic()を非同期実行

#### Step 3: 診断実行処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | diagnostic.lua | `script/provider/diagnostic.lua` | doDiagnostic関数（281-358行目） |
| 3-2 | diagnostic.lua | `script/provider/diagnostic.lua` | pushResult関数（305-331行目） |

**主要処理フロー**:
- **288-292行目**: ファイル状態取得、存在しなければクリア
- **301行目**: `m.syntaxErrors()`で構文エラー取得
- **337行目**: `core()`で診断エンジン実行
- **338行目**: `buildDiagnostic()`で診断オブジェクト生成
- **325-330行目**: `proto.notify('textDocument/publishDiagnostics', ...)`で通知送信

#### Step 4: 通知送信処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | diagnostic.lua | `script/provider/diagnostic.lua` | proto.notify呼び出し（167, 325, 374行目） |
| 4-2 | diagnostic.lua | `script/provider/diagnostic.lua` | m.clear関数（161-172行目） |

**主要処理フロー**:
- **161-172行目**: `m.clear()`で空の診断配列を送信
- **325-330行目**: `pushResult()`内でfull診断配列を送信
- **374-379行目**: `resendDiagnostic()`でキャッシュ済み診断を再送信

#### Step 5: 診断生成処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | init.lua | `script/core/diagnostics/init.lua` | 診断エンジンのメイン処理（172-191行目） |
| 5-2 | diagnostic.lua | `script/provider/diagnostic.lua` | buildDiagnostic関数（86-123行目） |
| 5-3 | diagnostic.lua | `script/provider/diagnostic.lua` | buildSyntaxError関数（36-84行目） |

**主要処理フロー**:
- **init.lua 178-190行目**: 各診断モジュールを順次実行
- **buildDiagnostic 112-122行目**: 診断オブジェクト生成（range, source, severity, message, code, tags, data, relatedInformation）
- **buildSyntaxError 74-83行目**: 構文エラーオブジェクト生成

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

```
files.watch() [ファイルイベント監視]
    │
    ├─ 'create'/'update' イベント
    │      └─ m.refresh(uri)
    │             │
    │             └─ await.call(m.doDiagnostic)
    │
    ├─ 'remove' イベント
    │      ├─ m.clear(uri)
    │      │      └─ proto.notify('publishDiagnostics', {uri, diagnostics: []})
    │      └─ m.refresh(uri)
    │
    └─ 'open' イベント
           └─ m.doDiagnostic(uri)

m.doDiagnostic(uri)
    │
    ├─ isValid(uri) チェック
    │
    ├─ files.getState(uri) 取得
    │      └─ 失敗時: m.clear(uri)
    │
    ├─ m.syntaxErrors(uri, state)
    │      └─ buildSyntaxError() × N
    │
    ├─ core(uri, ..., callback)
    │      │
    │      └─ script/core/diagnostics/init.lua
    │             │
    │             └─ 各診断モジュール実行
    │                    └─ callback(result)
    │                           └─ buildDiagnostic(uri, result)
    │
    └─ pushResult()
           │
           ├─ mergeDiags(syntax, lastDiag, diags)
           │
           ├─ キャッシュ比較
           │
           └─ proto.notify('textDocument/publishDiagnostics', {
                  uri: uri,
                  version: version,
                  diagnostics: full
              })
```

### データフロー図

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

ファイルイベント ──────▶ files.watch()
(create/update/             │
 remove/open/save)          ▼
                      m.refresh() / m.doDiagnostic()
                              │
                   ┌──────────┼──────────┐
                   │          │          │
                   ▼          ▼          ▼
            syntaxErrors   core()    前回診断
                   │          │          │
                   └──────────┼──────────┘
                              │
                              ▼
                      mergeDiags()
                              │
                              ▼
                      キャッシュ比較
                              │
                              ▼
                   proto.notify('publishDiagnostics')
                              │
                              ▼
                    ─────────────────────▶ エディタ
                    JSON-RPC通知
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| diagnostic.lua | `script/provider/diagnostic.lua` | ソース | 診断プロバイダーの中心、通知送信 |
| init.lua | `script/core/diagnostics/init.lua` | ソース | 診断エンジンのメイン処理 |
| define.lua | `script/proto/define.lua` | ソース | DiagnosticSeverity等の定義 |
| converter.lua | `script/proto/converter.lua` | ソース | 位置変換ユーティリティ |
| proto.lua | `script/proto/proto.lua` | ソース | JSON-RPC通信 |
| files.lua | `script/files.lua` | ソース | ファイル管理、イベント発火 |
