# 帳票設計書 12-フォーマット進捗

## 概要

本ドキュメントは、Prettierの`--write`オプション実行時にTTY環境で出力される「フォーマット進捗」の帳票設計について記述する。この帳票は、各ファイルのフォーマット処理にかかった時間をリアルタイムで標準出力に表示する機能である。

### 本帳票の処理概要

この帳票では、`prettier --write` コマンド実行時に、各ファイルのフォーマット処理完了後、ファイル名と処理時間（ミリ秒）を標準出力に表示する。TTY（端末）環境では、処理中のファイル名が一時的に表示され、処理完了後に処理時間付きの情報に置き換えられる。

**業務上の目的・背景**：大量のファイルをフォーマットする際に、処理の進捗状況を視覚的に把握するために使用される。開発者がフォーマット処理の進行状況をリアルタイムで確認でき、処理が停滞していないことを確認できる。また、各ファイルの処理時間を表示することで、パフォーマンス上の問題があるファイルを特定する助けにもなる。

**帳票の利用シーン**：
- ローカル開発環境で `prettier --write .` を実行し、プロジェクト全体をフォーマットする場合
- フォーマット処理のパフォーマンスを確認・監視する場合
- 対話的な開発作業で、どのファイルが処理されたかをリアルタイムで確認する場合

**主要な出力内容**：
1. ファイルパス（現在の作業ディレクトリからの相対パス、POSIX形式）
2. 処理時間（ミリ秒単位、四捨五入）
3. 変更なしの場合は "(unchanged)" の付加
4. キャッシュヒット時は "(cached)" の付加

**帳票の出力タイミング**：`prettier --write <ファイルパターン>` コマンド実行時に、各ファイルのフォーマット処理完了直後に出力される。TTY環境でのみリアルタイム進捗表示が有効となり、CI環境やパイプ処理時は出力が抑制または簡略化される。

**帳票の利用者**：開発者（ターミナル利用時）

## 帳票種別

進捗出力（標準出力へのリアルタイムテキスト出力）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | CLI（コマンドライン・TTY環境） | - | `prettier --write <files>` |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | テキスト（標準出力） |
| 用紙サイズ | - |
| 向き | - |
| ファイル名 | - （標準出力への直接出力） |
| 出力方法 | 標準出力（stdout） |
| 文字コード | UTF-8 |

### 出力形式詳細

- 各ファイルの処理結果は1行ごとに出力される
- ファイルパスは現在の作業ディレクトリからの相対パスで、POSIX形式（スラッシュ区切り）に正規化される
- 処理時間はミリ秒単位で四捨五入されて表示される
- 変更がなかったファイルはグレー色で表示され、"(unchanged)" が付加される
- キャッシュが有効でキャッシュヒットした場合は "(cached)" が追加される

### 出力例

```
src/index.js 45ms
src/utils.js 12ms (unchanged)
src/components/Button.js 78ms
src/components/Modal.js 23ms (unchanged) (cached)
```

## 帳票レイアウト

### レイアウト概要

帳票はヘッダー部を持たず、各ファイルの処理結果が1行ずつリアルタイムで出力される動的なリスト形式である。

```
┌─────────────────────────────────────────────────────┐
│ {ファイルパス1} {処理時間}ms                          │
│ {ファイルパス2} {処理時間}ms (unchanged)              │
│ {ファイルパス3} {処理時間}ms (unchanged) (cached)     │
│ ...                                                 │
└─────────────────────────────────────────────────────┘
```

### TTY環境での動的表示

TTY環境では、処理中のファイル名が一時的に表示され（クリア可能）、処理完了後に最終結果で置き換えられる。

```
処理中: src/components/Modal.js  ← 一時表示（その後クリアされる）
↓
src/components/Modal.js 23ms (unchanged)  ← 最終出力
```

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | ファイルパス | 処理対象ファイルのパス | `path.relative(cwd, filename)` + `normalizeToPosix()` | 相対パス（POSIX形式） | 可変 |
| 2 | 処理時間 | フォーマット処理にかかった時間 | `mockable.getTimestamp() - start` | `{ms}ms` | 可変 |
| 3 | 変更状態 | 変更の有無を示すラベル | `output !== input` の否定 | `(unchanged)` または空 | 固定 |
| 4 | キャッシュ状態 | キャッシュヒットを示すラベル | `isCacheExists` | `(cached)` または空 | 固定 |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| --write オプション | ファイル書き込みモードが有効 | Yes |
| TTY環境 | `process.stdout.isTTY` が `true` | Yes（進捗表示の場合） |
| 非CI環境 | `ci-info` モジュールで CI が検出されない | Yes（進捗表示の場合） |
| --check/--list-different なし | チェックモードでないこと | Yes |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | 処理順序 | globパターンによる展開順（処理完了順） |

### 改ページ条件

該当なし（テキスト出力のため）

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

該当なし（ファイルシステムからの読み込み）

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| 処理時間 | `mockable.getTimestamp() - start` | 四捨五入（`Math.round()`） | `performance.now()` でミリ秒精度の計測 |
| 変更判定 | `output !== input` | - | フォーマット後の出力と入力を文字列比較 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[--write オプション指定] --> B{TTY環境?}
    B -->|Yes| C[isTTY = true]
    B -->|No| D[isTTY = false]
    C --> E{CI環境?}
    E -->|Yes| D
    E -->|No| F[進捗表示有効]
    D --> F
    F --> G[ファイルパターン展開]
    G --> H{各ファイルをループ}
    H --> I{isTTY?}
    I -->|Yes| J[ファイル名を一時表示]
    I -->|No| K[表示なし]
    J --> L[start = getTimestamp]
    K --> L
    L --> M[prettier.format実行]
    M --> N[処理時間計算]
    N --> O{isTTY?}
    O -->|Yes| P[一時表示をクリア]
    O -->|No| Q[次へ]
    P --> R{変更あり?}
    Q --> R
    R -->|Yes| S[ファイル書き込み]
    R -->|No| T[スキップ]
    S --> U[処理結果を出力]
    T --> U
    U --> V{次のファイル?}
    V -->|Yes| H
    V -->|No| W[終了]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| パーサー不明 | 対象ファイルのパーサーが特定できない | `No parser could be inferred for file: {filename}` | --ignore-unknown オプションで無視可能 |
| パースエラー | ソースコードの構文エラー | `{filename}: SyntaxError: {message}` | ファイルの構文を修正 |
| ファイル書き込み失敗 | ファイルアクセス権限がない等 | `Unable to write file "{filename}": {error}` | ファイルパーミッションを確認 |
| ファイル読み込み失敗 | ファイルアクセス権限がない等 | `Unable to read file "{filename}": {error}` | ファイルパーミッションを確認 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 数千ファイル |
| 目標出力時間 | ファイル数に比例（1ファイルあたり数十ms） |
| 同時出力数上限 | なし（順次処理） |

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

- ファイルパスと処理時間のみの出力であり、ファイル内容は出力されない
- `.prettierignore` で指定されたファイルは自動的に除外される
- `node_modules` 配下はデフォルトで除外される

## 備考

- TTY環境の判定: `process.stdout.isTTY && !isCI()`
- CI環境では進捗表示が抑制される（`ci-info` パッケージで検出）
- `picocolors` パッケージによる色付け（変更なしファイルはグレー表示）
- 処理中ファイル名の一時表示は `readline` モジュールで制御

---

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

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

### 推奨読解順序

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

まず、CLIオプションと環境検出の仕組みを理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | cli-options.evaluate.js | `src/cli/cli-options.evaluate.js` | `write` オプションの定義（237-242行目）を確認 |
| 1-2 | mockable.js | `src/cli/mockable.js` | `isStreamTTY`, `isCI`, `getTimestamp` の定義を確認 |

**読解のコツ**: `mockable.js` はテスト容易性のためにモック可能な関数群を提供している。`isStreamTTY` は `stream.isTTY` をラップし、`isCI` は `ci-info` パッケージを使用してCI環境を検出する。

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

処理の起点となるファイル・関数を特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.js | `src/cli/index.js` | `main()` 関数でファイル処理への分岐を確認 |

**主要処理フロー**:
1. **111-113行目**: ファイルパターンがある場合は `formatFiles()` を呼び出し

#### Step 3: ファイル処理層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | format.js | `src/cli/format.js` | `formatFiles()` 関数（272-504行目）のTTY判定と進捗出力を確認 |

**主要処理フロー**:
- **300-303行目**: TTY環境の判定
  ```javascript
  const isTTY = mockable.isStreamTTY(process.stdout) && !mockable.isCI();
  ```
- **333-338行目**: TTY環境での処理中ファイル名の一時表示
  ```javascript
  if (isTTY) {
    printedFilename = context.logger.log(fileNameToDisplay, {
      newline: false,
      clearable: true,
    });
  }
  ```
- **365行目**: 処理開始時刻の記録
  ```javascript
  const start = mockable.getTimestamp();
  ```
- **402行目**: 一時表示のクリア
  ```javascript
  printedFilename?.clear();
  ```
- **411-440行目**: 処理結果の出力
  ```javascript
  if (context.argv.write) {
    const timeToDisplay = `${Math.round(mockable.getTimestamp() - start)}ms`;
    if (isDifferent) {
      if (!context.argv.check && !context.argv.listDifferent) {
        context.logger.log(`${fileNameToDisplay} ${timeToDisplay}`);
      }
      // ...
    } else if (!context.argv.check && !context.argv.listDifferent) {
      const message = `${picocolors.gray(fileNameToDisplay)} ${timeToDisplay} (unchanged)`;
      if (isCacheExists) {
        context.logger.log(`${message} (cached)`);
      } else {
        context.logger.log(message);
      }
    }
  }
  ```

#### Step 4: ロガー層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | logger.js | `src/cli/logger.js` | `createLogFunc()` 関数の `clearable` オプション処理を確認 |

**主要処理フロー**:
- **42-43行目**: オプションのデフォルト値設定
  ```javascript
  options = {
    newline: true,
    clearable: false,
    ...options,
  };
  ```
- **49-53行目**: `clearable` オプションが `true` の場合、`clear()` メソッドを持つオブジェクトを返す
  ```javascript
  if (options.clearable) {
    return {
      clear: () => mockable.clearStreamText(stream, message),
    };
  }
  ```

#### Step 5: クリア処理層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | clear-stream-text.js | `src/cli/utilities/clear-stream-text.js` | テキストクリア処理の実装を確認 |

**主要処理フロー**:
- **5-12行目**: 行数計算（ANSI エスケープシーケンスを除去し、端末幅を考慮）
- **14-26行目**: `readline` モジュールを使用してカーソル移動とクリア

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

```
src/cli/index.js
    │
    ├─ run()
    │      └─ main()
    │             └─ formatFiles()
    │                    │
    │                    ├─ mockable.isStreamTTY() ─────┐
    │                    │      └─ process.stdout.isTTY │
    │                    │                               │
    │                    ├─ mockable.isCI() ────────────┤
    │                    │      └─ ci-info.isCI         │
    │                    │                               │
    │                    ├─ [TTY時] logger.log({clearable:true})
    │                    │      └─ process.stdout.write()│
    │                    │                               │
    │                    ├─ mockable.getTimestamp() ────┤
    │                    │      └─ performance.now()     │
    │                    │                               │
    │                    ├─ format() ───────────────────┤
    │                    │      └─ prettier.formatWithCursor()
    │                    │                               │
    │                    ├─ printedFilename.clear() ────┤
    │                    │      └─ clearStreamText()     │
    │                    │             └─ readline      │
    │                    │                               │
    │                    └─ logger.log(result) ─────────┘
    │
    └─ src/cli/utilities/clear-stream-text.js
           └─ clearStreamText()
                  ├─ countLines()
                  ├─ readline.moveCursor()
                  ├─ readline.clearLine()
                  └─ readline.cursorTo()
```

### データフロー図

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

ファイルパターン ───▶ expandPatterns() ───▶ ファイルリスト
                              │
                              ▼
                      TTY環境判定
                      isTTY = stdout.isTTY && !isCI
                              │
              ┌───────────────┴───────────────┐
              │TTY環境                        │非TTY環境
              ▼                               ▼
    一時ファイル名表示                      表示なし
              │                               │
              └───────────────┬───────────────┘
                              │
                              ▼
                    start = getTimestamp()
                              │
                              ▼
                    prettier.format()
                              │
                              ▼
                    処理時間 = getTimestamp() - start
                              │
              ┌───────────────┴───────────────┐
              │TTY環境                        │
              ▼                               │
        一時表示クリア                         │
              │                               │
              └───────────────┬───────────────┘
                              │
              ┌───────────────┴───────────────┐
              │変更あり                       │変更なし
              ▼                               ▼
    "{path} {time}ms"            "{path} {time}ms (unchanged)"
                                 + "(cached)" if cached
                              │
                              ▼
                          stdout
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.js | `src/cli/index.js` | ソース | CLIエントリーポイント |
| format.js | `src/cli/format.js` | ソース | ファイルフォーマット処理、進捗出力 |
| cli-options.evaluate.js | `src/cli/cli-options.evaluate.js` | ソース | CLIオプション定義 |
| logger.js | `src/cli/logger.js` | ソース | ログ出力機能（clearable対応） |
| mockable.js | `src/cli/mockable.js` | ソース | モック可能なユーティリティ（TTY判定、タイムスタンプ） |
| clear-stream-text.js | `src/cli/utilities/clear-stream-text.js` | ソース | 端末テキストクリア処理 |
| context.js | `src/cli/context.js` | ソース | CLIコンテキスト管理 |
| utilities.js | `src/cli/utilities.js` | ソース | ユーティリティ関数（normalizeToPosix等） |
| prettier-internal.js | `src/cli/prettier-internal.js` | ソース | picocolors等の内部モジュール |
