# 帳票設計書 16-インポートコンパイル時間レポート

## 概要

本ドキュメントは、serve-dサーバーが提供する「インポートコンパイル時間レポート」の帳票設計を定義する。各import文のコンパイル時間を計測し、CodeLensとしてエディタ上に表示する。コンパイル時間が10ms以上の場合にミリ秒単位で表示する。

### 本帳票の処理概要

本帳票は、D言語ソースファイル内の各import文がコンパイル時間に与える影響を計測し、その結果をCodeLens（エディタ上のインライン情報表示）として出力する機能である。LSPプロトコルの `textDocument/codeLens` リクエストでCodeLensアイテムを生成し、`codeLens/resolve` リクエストで実際のコンパイル時間計測を行う2段階方式で動作する。

**業務上の目的・背景**：D言語のコンパイル時間はimport文の数と種類に大きく影響される。特に大規模プロジェクトでは、不要なimportや重いモジュールのimportがビルド時間を増大させる要因となる。開発者がimport文ごとのコンパイルコストを可視化し、ビルド時間の最適化に活用するために、各import文のコンパイル時間をエディタ上で直接表示する機能が必要となる。

**帳票の利用シーン**：D言語ソースファイルの編集中に、各import文の上部にCodeLensとして「~Xms for importing this」のようなテキストが表示される。開発者はこの情報を基に、不要なimportの削除や、軽量な代替モジュールへの置き換えを検討できる。特にコンパイル時間が長いプロジェクトのパフォーマンスチューニングで有用である。

**主要な出力内容**：
1. 各import文に対応するCodeLensアイテム（位置情報とメタデータ）
2. 解決済みCodeLens（コンパイル時間の計測結果テキスト）

**帳票の出力タイミング**：
- CodeLens生成: ドキュメント表示・スクロール時に `textDocument/codeLens` リクエストが自動送信された時点
- コンパイル時間計測: エディタがCodeLensを表示する際に `codeLens/resolve` リクエストが送信された時点

**帳票の利用者**：D言語で開発を行うソフトウェアエンジニア、ビルド時間最適化を担当する開発者。

## 帳票種別

一覧表（CodeLens情報表示）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | エディタ（VSCode等）のコードビュー | LSP: `textDocument/codeLens` / `codeLens/resolve` | ドキュメント表示時に自動表示 |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | JSON（LSPレスポンス） |
| 用紙サイズ | N/A（エディタインライン表示） |
| 向き | N/A |
| ファイル名 | N/A（LSPレスポンスとして返却） |
| 出力方法 | LSP JSON-RPCレスポンス（CodeLens） |
| 文字コード | UTF-8 |

### PDF固有設定

該当なし

### Excel固有設定

該当なし

## 帳票レイアウト

### レイアウト概要

2段階のLSPレスポンスとして出力される。第1段階（codeLens）でCodeLensアイテムの位置とメタデータを返却し、第2段階（resolve）で実際のコマンドテキストを返却する。

```
[第1段階: textDocument/codeLens]
┌──────────────────────────────────────┐
│  CodeLens[]                          │
│  ├─ CodeLens[0]                      │
│  │   ├─ range: import文の位置        │
│  │   ├─ command: null (未解決)        │
│  │   └─ data: {                      │
│  │       type: "importcompilecheck", │
│  │       code: "import文テキスト",     │
│  │       module: "モジュール名",       │
│  │       file: "ファイルパス"          │
│  │     }                             │
│  └─ CodeLens[N]                      │
└──────────────────────────────────────┘

[第2段階: codeLens/resolve]
┌──────────────────────────────────────┐
│  CodeLens                            │
│  ├─ range: import文の位置            │
│  ├─ command:                         │
│  │   └─ title: "~Xms for importing  │
│  │             this"                 │
│  └─ data: (そのまま)                 │
└──────────────────────────────────────┘
```

### ヘッダー部

該当なし

### 明細部（CodeLensアイテム）

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | range | import文のソースコード位置 | ドキュメント内のimport文マッチ位置 | Position（行, 列） | N/A |
| 2 | data.type | CodeLens種別 | 固定値 "importcompilecheck" | 文字列 | N/A |
| 3 | data.code | import文テキスト全体 | 正規表現マッチ結果 | 文字列 | N/A |
| 4 | data.module | インポート対象モジュール名 | 正規表現のキャプチャグループ1 | ドット区切り文字列 | N/A |
| 5 | data.file | ソースファイルパス | document.uri.uriToFile | ファイルシステムパス | N/A |

### 解決済みCodeLens（codeLens/resolve後）

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | command.title | コンパイル時間表示テキスト | DMDComponent.measureSync結果 | "~Xms for importing this" / "no noticable effect for importing this" | N/A |

### フッター部

該当なし

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| d.enableDMDImportTiming | 設定でインポートタイミング機能が有効であること | Yes |
| LanguageId == "d" | ドキュメントの言語がD言語であること | Yes |
| import文パターンマッチ | ドキュメント内にimport文が存在すること | Yes |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | import文の出現位置 | ドキュメント内の出現順（昇順） |

### 改ページ条件

該当なし

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

### 参照テーブル一覧

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| TextDocumentManager（メモリ） | ドキュメントテキストの取得 | textDocument.uri |
| DMDComponent（メモリ） | コンパイル時間計測 | backend.best!DMDComponent(file) |
| CompileCache（メモリ、静的） | 計測結果のキャッシュ | code文字列をキーとした一致検索 |

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

#### TextDocumentManager

| 参照項目（カラム名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| rawText | import文正規表現マッチの対象テキスト | params.textDocument.uri | ドキュメント全体のテキスト |
| uri | data.file | uriToFileで変換 | ファイルパスとして格納 |

#### DMDComponent

| 参照項目（カラム名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| measureSync(code, null, 20, 500) | コンパイル時間（ミリ秒） | codeLens/resolve時に呼び出し | 20回計測、500msタイムアウト |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| コンパイル時間（ms） | `cast(int) round(ret.duration.total!"msecs" / 5.0) * 5` | 5ms単位に丸め | DMDの計測結果を5ms刻みに丸める |
| 表示テキスト | decMs < 10 ? "no noticable effect" : "~" ~ decMs ~ "ms" | N/A | 10ms未満は「効果なし」表示 |
| キャッシュ有効期間 | コンパイル時間>=500ms: 20分、>=30ms: 5分、<30ms: 2分 | N/A | std.* モジュールはキャッシュ無期限 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[textDocument/codeLensリクエスト受信] --> B[ドキュメント取得・言語チェック]
    B --> C{languageId == 'd'?}
    C -->|No| D[空配列返却]
    C -->|Yes| E{enableDMDImportTiming?}
    E -->|No| D
    E -->|Yes| F[importRegexでimport文を全マッチ]
    F --> G[各マッチに対しCodeLensアイテム生成]
    G --> H[CodeLens配列返却]
    H --> I[終了 - 第1段階完了]

    J[codeLens/resolveリクエスト受信] --> K[lens.data検証]
    K --> L{type == 'importcompilecheck'?}
    L -->|No| M[Unknown lens type例外]
    L -->|Yes| N[code, module, file取得]
    N --> O[getImportCompilationTime呼び出し]
    O --> P{キャッシュにヒット?}
    P -->|Yes| Q[キャッシュ値返却]
    P -->|No| R[DMDComponent.measureSyncで計測]
    R --> S[結果を5ms単位に丸め]
    S --> T[キャッシュに保存]
    T --> U[Command.title設定]
    U --> V[解決済みCodeLens返却]
    Q --> U
    V --> W[終了 - 第2段階完了]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| 無効なlensデータ | lens.dataがnullまたはobjectでない | "Invalid Lens Object" | 例外スロー |
| 不明なlens型 | data.typeが"importcompilecheck"以外 | "Unknown lens type" | 例外スロー |
| コードなし | data.codeが空文字列 | "No valid code provided" | 例外スロー、command初期化 |
| モジュールなし | data.moduleが空文字列 | "No valid module provided" | 例外スロー、command初期化 |
| ファイルなし | data.fileが空文字列 | "No valid file provided" | 例外スロー、command初期化 |
| コンパイル失敗 | DMDComponent.measureSyncが失敗 | "Compilation failed" | 例外スロー、command初期化 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 1ファイルあたり5-50件のimport文 |
| 目標出力時間 | codeLens: 100ms以下、resolve: 1000ms以下（計測時間含む） |
| 同時出力数上限 | resolve同時実行: 1件（importCompilationTimeRunning排他制御） |

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

本帳票はDMDコンパイラを使用してコンパイル時間を計測するため、ローカルファイルシステムへのアクセスとプロセス実行が伴う。信頼できるソースコードに対してのみ使用すべきである。

## 備考

- `enableDMDImportTiming` 設定はデフォルトでfalse。明示的に有効化する必要がある
- コンパイル時間の計測は `DMDComponent.measureSync` を使用し、DMDコンパイラのプロセスを起動する
- 計測結果はメモリ内キャッシュに保存され、キャッシュ期間は計測値に応じて2分、5分、20分と変動する。`std.*` モジュールのインポートは再計測対象にならない
- `importCompilationTimeRunning` フラグによる排他制御で、同時に1つのコンパイル時間計測のみ実行される
- 計測後100ms間はFiber.yield()で待機し、連続リクエストの処理を可能にする
- import文の正規表現は `code_actions.d` で定義され、`package` スコープで共有される

---

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

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

### 推奨読解順序

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

CodeLensの標準LSPデータ構造と、本帳票固有のdata構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | code_lens.d | `source/served/commands/code_lens.d` | CodeLens.dataの構造: `type`, `code`, `module`, `file` の4フィールド（40-46行目） |
| 1-2 | code_lens.d | `source/served/commands/code_lens.d` | `CompileCache` 内部構造体（102-107行目）: `at`（計測時刻）、`code`（import文テキスト）、`ret`（計測結果ms） |

**読解のコツ**: CodeLensのdataフィールドはJsonValue型で、任意のJSON構造を格納できる。本帳票では `type` フィールドで種別を識別し、`code` / `module` / `file` で計測に必要な情報を保持する。

#### Step 2: エントリーポイントを理解する（CodeLens生成）

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | code_lens.d | `source/served/commands/code_lens.d` | `provideCodeLens`関数（20-50行目）。`@protocolMethod("textDocument/codeLens")` で登録 |

**主要処理フロー**:
1. **20行目**: `@protocolMethod("textDocument/codeLens")` でLSPメソッド登録
2. **23-26行目**: ドキュメント取得、ファイルパス抽出、言語チェック（D言語以外は空配列）
3. **28行目**: `workspace(...).config.d.enableDMDImportTiming` で機能有効チェック
4. **33行目**: `document.rawText.matchAll(importRegex)` でimport文を正規表現マッチ
5. **35-36行目**: マッチ位置からPosition計算（`movePositionBytes`を使用した増分計算）
6. **40-46行目**: CodeLensアイテム構築。range、data（type, code, module, file）を設定

#### Step 3: CodeLens解決（コンパイル時間計測）を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | code_lens.d | `source/served/commands/code_lens.d` | `resolveCodeLens`関数（52-95行目）。`@protocolMethod("codeLens/resolve")` で登録 |

**主要処理フロー**:
1. **52行目**: `@protocolMethod("codeLens/resolve")` でLSPメソッド登録
2. **55-57行目**: lens.dataのバリデーション
3. **60-61行目**: data.typeの取得と分岐
4. **64行目**: "importcompilecheck"ケース
5. **67-80行目**: code, module, fileの各パラメータ取得・検証
6. **82行目**: `getImportCompilationTime(code, module_, file)` 呼び出し
7. **83-84行目**: 10ms未満なら "no noticable effect"、以上なら "~Xms" を表示テキストに設定

#### Step 4: コンパイル時間計測の内部実装を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | code_lens.d | `source/served/commands/code_lens.d` | `getImportCompilationTime`関数（98-143行目）。キャッシュ管理と実計測ロジック |

**主要処理フロー**:
- **102-107行目**: `CompileCache` 構造体定義（at: 計測時刻、code: import文、ret: 結果ms）
- **109行目**: `cache` 静的配列（キャッシュストレージ）
- **113-125行目**: キャッシュヒットチェック。キャッシュ有効期間は計測値に応じて変動（500ms以上: 20分、30ms以上: 5分、それ以下: 2分）。`std.*`モジュールは常にキャッシュヒット
- **127-131行目**: `importCompilationTimeRunning` 排他制御。他の計測が実行中の場合はFiber.yield()で待機
- **133行目**: `backend.best!DMDComponent(file).measureSync(code, null, 20, 500)` で実計測。20回繰り返し、500msタイムアウト
- **136行目**: `round(ret.duration.total!"msecs" / 5.0) * 5` で5ms単位に丸め
- **140-141行目**: 100ms間のyieldループ（連続リクエスト対応）

#### Step 5: import文正規表現を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | code_actions.d | `source/served/commands/code_actions.d` | `importRegex` 定義（30行目）。import文にマッチする正規表現。package スコープで共有 |

**主要処理フロー**:
- **30行目**: 正規表現パターン。`import\s+` で始まり、エイリアスインポート、モジュール名、選択的インポートに対応。キャプチャグループ1がモジュール名

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

```
textDocument/codeLens (LSPリクエスト)
    │
    ├─ code_lens.provideCodeLens()                   [code_lens.d:20-50]
    │      │
    │      ├─ documents[uri] → Document取得
    │      ├─ workspace(...).config.d.enableDMDImportTiming チェック
    │      └─ document.rawText.matchAll(importRegex)  [code_actions.d:30]
    │             └─ 各マッチ → CodeLens { range, data } 生成
    │
codeLens/resolve (LSPリクエスト)
    │
    ├─ code_lens.resolveCodeLens()                   [code_lens.d:52-95]
    │      │
    │      ├─ lens.data解析 → type, code, module, file抽出
    │      │
    │      └─ getImportCompilationTime()              [code_lens.d:98-143]
    │             │
    │             ├─ CompileCache チェック             [キャッシュヒット判定]
    │             │
    │             └─ backend.best!DMDComponent.measureSync()  [DMD計測]
    │                    └─ 結果 → 5ms丸め → キャッシュ保存
    │
    └─ Command.title = "~Xms for importing this"
```

### データフロー図

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

CodeLensParams          ───▶  provideCodeLens()             ───▶  CodeLens[]
  └─ textDocument.uri          │                                  (未解決、data付き)
                               ├─ Document.rawText取得
D言語ソースコード       ───▶  └─ importRegex全マッチ

CodeLens (未解決)       ───▶  resolveCodeLens()             ───▶  CodeLens
  └─ data                      │                                  (command.title付き)
    ├─ code                    ├─ キャッシュチェック
    ├─ module                  └─ DMDComponent.measureSync
    └─ file            ───▶       └─ コンパイル時間計測

DMDコンパイラ           ───▶  [計測プロセス実行]            ───▶  duration(msecs)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| code_lens.d | `source/served/commands/code_lens.d` | ソース | provideCodeLens, resolveCodeLens, getImportCompilationTime関数の定義 |
| code_actions.d | `source/served/commands/code_actions.d` | ソース | importRegex正規表現の定義（package スコープ） |
| extension.d | `source/served/extension.d` | ソース | CodeLensOptions設定、codeLensProvider: resolveProvider: true |
| types.d | `source/served/types.d` | ソース | Configuration.D.enableDMDImportTiming設定フィールド（デフォルトfalse） |
