# 機能設計書 53-jest-phabricator

## 概要

本ドキュメントは、Jestテスティングフレームワークにおける `jest-phabricator` パッケージの機能設計について記述する。このパッケージは、Phabricator CI システムとの統合用にテスト結果を変換するレポータープロセッサである。

### 本機能の処理概要

`jest-phabricator` は、Jestのテスト結果（特にカバレッジデータ）をPhabricatorが理解できる形式に変換し、コードレビューシステムとのシームレスな統合を実現する。

**業務上の目的・背景**：Phabricatorは、Meta（旧Facebook）が開発したコードレビュー・プロジェクト管理ツールであり、多くの企業でCI/CDパイプラインに組み込まれている。JestはMeta発のテストフレームワークであり、Phabricatorとの連携が重要なユースケースとなる。このパッケージにより、Jestのカバレッジレポートを行単位の簡潔なサマリー形式に変換し、Phabricatorのコードレビュー画面で直接カバレッジ情報を確認できるようになる。

**機能の利用シーン**：
- PhabricatorをCI/CDシステムとして使用しているプロジェクト
- コードレビュー時に行単位のカバレッジ情報を表示したい場合
- Arcコマンドラインツールからのテスト実行時

**主要な処理内容**：
1. Jestの集約テスト結果（AggregatedResult）を受け取る
2. カバレッジマップから各ファイルのライン単位カバレッジを抽出
3. 各行を 'C'（Covered）、'U'（Uncovered）、'N'（Not executable）に変換
4. 変換されたサマリー形式でカバレッジマップを置き換えた結果を返却

**関連システム・外部連携**：
- `@jest/test-result`：AggregatedResult型の参照
- `istanbul-lib-coverage`：カバレッジマップのファイルカバレッジ取得
- Phabricator：最終的なカバレッジデータの消費者

**権限による制御**：本パッケージは結果変換のみを行い、権限制御は存在しない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | Phabricatorコードレビュー画面 | 結果表示画面 | 行単位カバレッジ情報の表示 |

## 機能種別

データ変換 / レポート処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| results | AggregatedResult | Yes | Jestの集約テスト結果 | coverageMapがnullの場合はそのまま返却 |

### 入力データソース

- Jestのテスト実行結果（AggregatedResult）
- coverageMap: istanbul-lib-coverageのCoverageMapオブジェクト

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| results | AggregatedResult | 変換されたcoverageMapを持つテスト結果 |
| coverageMap | Record<string, string> | ファイルパス → カバレッジサマリー文字列のマッピング |

### カバレッジサマリー形式

各ファイルのカバレッジは以下の文字からなる文字列で表現される：

| 文字 | 意味 | 説明 |
|------|------|------|
| C | Covered | その行がカバーされている（実行された） |
| U | Uncovered | その行がカバーされていない（実行されなかった） |
| N | Not executable | その行は実行可能コードではない（コメント、空行等） |

例：`"NCCCUNNNCCC"` は11行のファイルで、1行目は非実行可能、2-4行目はカバー済み、5行目は未カバー、6-8行目は非実行可能、9-11行目はカバー済みを表す。

### 出力先

- Jestの testResultsProcessor 設定経由で呼び出され、変換結果はJestのレポートパイプラインに戻される
- 最終的にはPhabricatorに送信される

## 処理フロー

### 処理シーケンス

```
1. PhabricatorProcessor関数が呼び出される
   └─ 引数: AggregatedResult

2. summarize関数でcoverageMapを変換
   └─ coverageMapがnullの場合はそのまま返却

3. 各ファイルのカバレッジを処理
   └─ coverageMap.files() でファイル一覧を取得
   └─ 各ファイルに対してfileCoverageFor()でカバレッジ取得
   └─ getLineCoverage()で行カバレッジを取得

4. 行ごとのカバレッジ状態を変換
   └─ カバレッジ値 > 0 の場合: 'C'
   └─ カバレッジ値 === 0 の場合: 'U'
   └─ 未定義の場合: 'N'

5. 結果を文字列として結合
   └─ covered.join('')

6. 変換されたcoverageMapで結果を更新
   └─ スプレッド演算子で新しいAggregatedResultを作成
```

### フローチャート

```mermaid
flowchart TD
    A[開始: PhabricatorProcessor] --> B{coverageMapがnull?}
    B -->|Yes| C[そのまま返却]
    B -->|No| D[summaries = {}]
    D --> E[各ファイルをループ]
    E --> F[getLineCoverage取得]
    F --> G[各行をループ]
    G --> H{lineCoverage > 0?}
    H -->|Yes| I[covered[n-1] = 'C']
    H -->|No| J[covered[n-1] = 'U']
    I --> K[次の行へ]
    J --> K
    K --> L{行ループ完了?}
    L -->|No| G
    L -->|Yes| M[未定義行を'N'で埋める]
    M --> N[summaries[file] = joined]
    N --> O{ファイルループ完了?}
    O -->|No| E
    O -->|Yes| P[coverageMapを置換して返却]
    P --> Q[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-53-01 | 行番号は1から開始 | ソースコードの行番号は1から始まるが、配列インデックスは0から | 常時 |
| BR-53-02 | 未実行行はNで表現 | lineCoverageに含まれない行は実行不可能コードとみなす | 常時 |
| BR-53-03 | coverageMapがnullの場合 | 変換せずそのまま返却 | coverageMapがnull/undefined |

### 計算ロジック

行カバレッジの変換ロジック：
```typescript
// lineCoverageは { 行番号: 実行回数 } の形式
for (const lineNumber of Object.keys(lineCoverage)) {
  const number = Number.parseInt(lineNumber, 10);
  // 行番号は1から始まるため、配列インデックスは number - 1
  covered[number - 1] = lineCoverage[number] ? 'C' : 'U';
}

// 未定義の行（実行不可能コード）は'N'で埋める
for (let i = 0; i < covered.length; i++) {
  if (!covered[i]) {
    covered[i] = 'N';
  }
}
```

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

本パッケージはデータベース操作を行わない。

### 操作別データベース影響一覧

該当なし

### テーブル別操作詳細

該当なし

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | null安全 | coverageMapがnullの場合 | そのまま返却（エラーなし） |

### リトライ仕様

該当なし（同期処理のため）

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

該当なし（メモリ内変換のみ）

## パフォーマンス要件

- カバレッジマップのファイル数とコード行数に比例した処理時間
- 大規模プロジェクトでも秒オーダーで完了する軽量な変換処理

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

- 入力データの検証は行わない（Jestからの信頼されたデータを想定）
- ファイルパスはそのまま出力されるため、機密パスが含まれる可能性に注意

## 備考

- testResultsProcessor設定で使用: `"testResultsProcessor": "jest-phabricator"`
- Node.js ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 が必要
- バージョン 30.2.0 時点の情報

---

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

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

### 推奨読解順序

#### Step 1: 全体構造を理解する

パッケージは単一ファイルで構成されており、構造が非常にシンプル。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | index.ts | `packages/jest-phabricator/src/index.ts` | 全処理ロジックを含む単一ファイル |

**主要処理フロー**:
- **8行目**: @jest/test-resultからAggregatedResult型をインポート
- **10行目**: CoverageMap型エイリアスの定義
- **12-39行目**: summarize関数 - カバレッジマップの変換処理
- **41-44行目**: PhabricatorProcessor関数 - メインエントリポイント

**読解のコツ**: istanbul-lib-coverageのAPIを理解することが重要。files()でファイル一覧、fileCoverageFor()で個別ファイルのカバレッジ、getLineCoverage()で行カバレッジを取得する。

#### Step 2: summarize関数の詳細

カバレッジ変換のコアロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `packages/jest-phabricator/src/index.ts` | 12-39行目のsummarize関数 |

**主要処理フロー**:
- **12-15行目**: null/undefinedチェック
- **17行目**: summaries = Object.create(null) でプロトタイプなしオブジェクト作成
- **19行目**: coverageMap.files()で全ファイルパスを取得
- **20行目**: covered配列を初期化
- **21行目**: fileCoverageFor(file).getLineCoverage()で行カバレッジ取得
- **23-27行目**: 各行のカバレッジをC/Uに変換（行番号は1始まりなので-1）
- **29-33行目**: 未定義行をNで埋める
- **35行目**: 文字列として結合してsummariesに格納

**読解のコツ**: `covered[number - 1]` の `-1` は、ソースコード行番号が1から始まるのに対し、JavaScriptの配列インデックスが0から始まるための調整。

#### Step 3: PhabricatorProcessor関数

エクスポートされるメイン関数を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | index.ts | `packages/jest-phabricator/src/index.ts` | 41-44行目 |

**主要処理フロー**:
- **41行目**: export default - testResultsProcessorとして使用
- **43行目**: AggregatedResultを受け取り、coverageMapを置換した新オブジェクトを返却
- **44行目**: スプレッド演算子で元の結果を保持しつつcoverageMapのみ上書き

**読解のコツ**: イミュータブルな変換を行い、元のAggregatedResultは変更しない。

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

```
Jest testResultsProcessor
    │
    └─ PhabricatorProcessor (index.ts:41)
           │
           └─ summarize (index.ts:12)
                  │
                  ├─ coverageMap.files() [istanbul-lib-coverage]
                  │
                  └─ fileCoverageFor(file).getLineCoverage() [istanbul-lib-coverage]
```

### データフロー図

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

AggregatedResult ─────────▶ PhabricatorProcessor
    │                              │
    ├─ coverageMap                 │
    │      │                       ▼
    │      └──────────────▶ summarize()
    │                              │
    │                              ├─ files() ───▶ ファイル一覧
    │                              │
    │                              ├─ getLineCoverage() ───▶ 行カバレッジ
    │                              │
    │                              └─ C/U/N変換 ───▶ summaries
    │                                                    │
    └─ その他プロパティ ───────────────────────────────▶ AggregatedResult'
                                                         │
                                                         └─ coverageMap: summaries
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.ts | `packages/jest-phabricator/src/index.ts` | ソース | 全処理ロジック（summarize, PhabricatorProcessor） |
| package.json | `packages/jest-phabricator/package.json` | 設定 | パッケージメタデータ、依存関係 |

### 依存パッケージの理解

| パッケージ | 役割 | 主要API |
|-----------|------|---------|
| @jest/test-result | テスト結果の型定義 | AggregatedResult |
| istanbul-lib-coverage | カバレッジデータ処理 | CoverageMap.files(), fileCoverageFor(), getLineCoverage() |
