# 機能設計書 4-範囲フォーマット

## 概要

本ドキュメントは、Prettierの範囲フォーマット機能の設計について記述する。

### 本機能の処理概要

指定された開始位置（rangeStart）と終了位置（rangeEnd）の間のコードのみをフォーマットする機能である。選択範囲のみのフォーマットを可能にし、ファイル全体を変更せずに特定部分のみを整形できる。

**業務上の目的・背景**：大規模なファイルの一部のみを編集した場合や、特定の範囲だけをフォーマットしたい場合に有用である。ファイル全体のフォーマットによる意図しない変更を避けながら、編集部分のみを整形できる。また、エディタでのテキスト選択フォーマット機能のバックエンドとしても使用される。

**機能の利用シーン**：
- エディタでの選択範囲フォーマット
- 特定の関数やクラスのみをフォーマット
- 差分が発生する範囲を最小化したい場合
- Playgroundでの範囲フォーマット機能

**主要な処理内容**：
1. rangeStart/rangeEndから空白を除いた実際の範囲を計算
2. 範囲内のソース要素（文やdeclaration）を特定
3. 範囲部分のみをフォーマット
4. インデントを維持しながら結果を元テキストに挿入
5. カーソル位置の調整

**関連システム・外部連携**：
- format/formatWithCursor関数からの呼び出し
- calculateRange関数による範囲計算
- エディタ統合API

**権限による制御**：特になし。すべてのユーザーがAPIを利用可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 2 | Playground | 補助機能 | 選択範囲のみをフォーマットする処理（rangeStart/rangeEnd指定時） |

## 機能種別

計算処理 / テキスト変換 / 位置計算

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| text | string | Yes | フォーマット対象のソースコード全体 | - |
| options | object | No | フォーマットオプション | - |
| options.rangeStart | number | Yes* | 範囲開始位置（0始まり） | 0以上、正規化される |
| options.rangeEnd | number | Yes* | 範囲終了位置 | text.length以下、正規化される |
| options.cursorOffset | number | No | カーソル位置 | 範囲内の場合のみ追跡 |

*範囲フォーマットとして動作するためには両方必要

### 入力データソース

- エディタからの選択範囲情報
- API呼び出しからの直接指定

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| formatted | string | フォーマット済みの全体テキスト（範囲外は元のまま） |
| cursorOffset | number | 調整後のカーソル位置（範囲外の場合は差分調整のみ） |
| comments | array | 処理されたコメントの配列 |

### 出力先

- API戻り値としてオブジェクトを返却

## 処理フロー

### 処理シーケンス

```
1. formatWithCursor内で範囲フォーマット判定
   └─ rangeStart > 0 または rangeEnd < text.length の場合

2. formatRange関数呼び出し
   └─ テキストをパースしてAST生成

3. calculateRange関数で実際の範囲を計算
   └─ 空白を除いた範囲の開始・終了位置を特定
   └─ ソース要素の境界に合わせて範囲を調整

4. インデント検出
   └─ 範囲開始行のインデントを検出
   └─ alignmentSizeを計算

5. 範囲部分のみをcoreFormatでフォーマット
   └─ インデント情報を渡して適切にフォーマット
   └─ endOfLineは一時的に'lf'を使用

6. 結果の組み立て
   └─ 元テキストの範囲前 + フォーマット結果 + 元テキストの範囲後

7. カーソル位置調整
   └─ 範囲前: そのまま
   └─ 範囲内: coreFormatの結果を使用
   └─ 範囲後: 差分だけオフセット

8. 改行コード変換
   └─ オプションに応じてlfから変換
```

### フローチャート

```mermaid
flowchart TD
    A[formatWithCursor] --> B{範囲指定あり?}
    B -->|Yes| C[formatRange呼び出し]
    B -->|No| D[coreFormat呼び出し]
    C --> E[parseText - AST生成]
    E --> F[calculateRange - 範囲計算]
    F --> G{範囲計算成功?}
    G -->|No| H[null返却]
    G -->|Yes| I[インデント検出]
    I --> J[coreFormat - 範囲部分フォーマット]
    J --> K[結果組み立て]
    K --> L[カーソル位置調整]
    L --> M[改行コード変換]
    M --> N[結果返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 空白除外 | 範囲の先頭・末尾の空白は実質的な範囲から除外 | 常時 |
| BR-02 | ソース要素境界 | 範囲はソース要素（文やdeclaration）の境界に拡張 | 常時 |
| BR-03 | インデント維持 | 元のインデントを維持してフォーマット | 常時 |
| BR-04 | 範囲外不変 | 範囲外のテキストは一切変更しない | 常時 |
| BR-05 | カーソル位置区分 | カーソル位置は範囲前/範囲内/範囲後で異なる処理 | cursorOffset指定時 |
| BR-06 | 改行コード一時統一 | 範囲フォーマット中は一時的にlfを使用 | 常時 |

### 計算ロジック

**範囲計算（calculateRange）**：
1. 範囲の先頭から最初の非空白文字を探す
2. 範囲の末尾から最後の非空白文字を探す
3. 各位置でfindNodeAtOffsetを呼び出し、ソース要素を特定
4. 開始・終了ノードの兄弟関係を考慮して最終範囲を決定

**インデントサイズ計算**：
```javascript
const rangeStart2 = Math.min(
  rangeStart,
  text.lastIndexOf("\n", rangeStart) + 1
);
const indentString = text.slice(rangeStart2, rangeStart).match(/^\s*/)[0];
const alignmentSize = getAlignmentSize(indentString, opts.tabWidth);
```

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

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

本機能はデータベース操作を行わない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 範囲計算失敗 | 指定範囲内にソース要素がない | 範囲を調整、または全体フォーマット |
| SyntaxError | パースエラー | ソースコードの構文エラー | 入力コードの構文を修正 |

### リトライ仕様

リトライは行わない。範囲計算失敗時はnullを返却し、呼び出し元で処理。

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

トランザクション管理は不要（ステートレスな処理）。

## パフォーマンス要件

- 範囲サイズに比例した処理時間
- 大規模ファイルでも範囲が小さければ高速に処理

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

- 入力コードの実行は行わない（静的解析のみ）
- 範囲情報は数値のみで機密性なし

## 備考

- 範囲フォーマットは全言語でサポートされているわけではない
- JSON、GraphQL等の一部言語でサポート
- 範囲がソース要素の境界と一致しない場合、自動的に拡張される

---

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

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

### 推奨読解順序

#### Step 1: 範囲フォーマット判定を理解する

formatWithCursor関数内での範囲フォーマット分岐を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | core.js | `src/main/core.js` | formatWithCursor内の分岐 |

**主要処理フロー**:
- **344-346行目**: 範囲フォーマット判定
  ```javascript
  if (options.rangeStart > 0 || options.rangeEnd < text.length) {
    result = await formatRange(text, options);
  }
  ```

#### Step 2: formatRange関数を理解する

範囲フォーマットの本体処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | core.js | `src/main/core.js` | formatRange関数 |

**主要処理フロー**:
- **184-246行目**: formatRange関数
  - **185行目**: パース処理
  - **186行目**: calculateRangeで範囲計算
  - **192-198行目**: インデント検出
  - **200-215行目**: coreFormat呼び出し
  - **221-229行目**: カーソル位置調整
  - **231-243行目**: 結果組み立てと改行コード変換

#### Step 3: 範囲計算を理解する

calculateRange関数による範囲の特定を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | range.js | `src/main/range.js` | calculateRange関数 |

**主要処理フロー**:
- **195-265行目**: calculateRange関数
  - **199-209行目**: 空白を除いた範囲の計算
  - **213-224行目**: 開始ノードの特定
  - **226-238行目**: 終了ノードの特定
  - **243-258行目**: 最終範囲の決定

#### Step 4: ソース要素の特定を理解する

findNodeAtOffsetとisSourceElement関数を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | range.js | `src/main/range.js` | findNodeAtOffset, isSourceElement |

**主要処理フロー**:
- **61-111行目**: findNodeAtOffset - 指定オフセットのノード検索
- **157-187行目**: isSourceElement - パーサー別のソース要素判定

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

```
formatWithCursor (src/main/core.js:324-367)
    │
    └─ formatRange (src/main/core.js:184-246)
           │
           ├─ parseText (src/main/parse.js)
           │
           ├─ calculateRange (src/main/range.js:195-265)
           │      │
           │      ├─ findNodeAtOffset (src/main/range.js:61-111)
           │      │      └─ getSortedChildNodes
           │      │
           │      ├─ isSourceElement (src/main/range.js:157-187)
           │      │
           │      └─ findSiblingAncestors (src/main/range.js:25-59)
           │
           ├─ getAlignmentSize (src/utilities/get-alignment-size.js)
           │
           └─ coreFormat (src/main/core.js:28-182)
                  └─ addAlignmentToDoc
```

### データフロー図

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

全体テキスト ───────────▶ parseText ──────────────────────▶ AST
    │                                                         │
    │                                                         ▼
rangeStart ───────────▶ calculateRange ──────────────────▶ [実際の範囲]
rangeEnd                     │                               start, end
                             │                                   │
                             ▼                                   ▼
                        範囲テキスト抽出 ─────────────────▶ 範囲テキスト
                                                                │
                                                                ▼
                                               coreFormat ───▶ フォーマット済み
                                                              範囲テキスト
                                                                │
                                                                ▼
                                               テキスト結合 ───▶ 全体テキスト
                                               (範囲前+結果+範囲後)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| core.js | `src/main/core.js` | ソース | formatRange関数の定義 |
| range.js | `src/main/range.js` | ソース | calculateRange、findNodeAtOffset関数 |
| get-alignment-size.js | `src/utilities/get-alignment-size.js` | ソース | インデントサイズ計算 |
| parse.js | `src/main/parse.js` | ソース | パース処理 |
