# 機能設計書 23-コードクリーンアップ

## 概要

本ドキュメントは、Roslynにおける「コードクリーンアップ」機能の設計を記述するものである。コードクリーンアップ機能は、複数のコード整形プロバイダーを順次適用してコードを整理・正規化する機能である。

### 本機能の処理概要

コードクリーンアップ機能は、指定された範囲に対して複数のICodeCleanupProviderを順次適用し、コードの整形や正規化を行う機能である。フォーマット、不要なusing文の削除、ソートなど、様々なクリーンアップ操作を一括で実行できる。

**業務上の目的・背景**：ソースコードの一貫性と可読性を維持するためには、定期的なコードクリーンアップが必要である。手動で複数の整形操作を行うのは時間がかかるため、複数の整形プロバイダーを一括で適用できる機能が求められる。また、保存時やコミット前に自動的にクリーンアップを実行することで、チーム全体でのコードスタイルの統一を図ることができる。

**機能の利用シーン**：
1. ファイル保存時の自動整形
2. ドキュメント全体またはフォルダー単位での一括クリーンアップ
3. コミット前のコード整理
4. コードレビュー準備のためのフォーマット統一

**主要な処理内容**：
1. クリーンアップ対象範囲の正規化
2. 範囲をトークンにアノテーション付与して追跡
3. 各ICodeCleanupProviderの順次実行
4. プロバイダー間でのSpan追跡と更新
5. 最終結果の返却

**関連システム・外部連携**：
- フォーマッターサービス(Formatter)との連携
- 診断エンジンとの連携(アナライザーFixの適用)
- 保存時のフック処理

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | コードクリーンアップダイアログ | 主画面 | クリーンアッププロファイルの選択と実行 |

## 機能種別

コード変換処理 / 整形

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| document | Document | Yes | 対象のドキュメント | 有効なドキュメントであること |
| spans | ImmutableArray<TextSpan> | Yes | クリーンアップ対象の範囲 | 空でないこと |
| options | CodeCleanupOptions | Yes | クリーンアップオプション | - |
| providers | ImmutableArray<ICodeCleanupProvider> | No | 使用するプロバイダー | Defaultの場合は言語のデフォルトを使用 |
| cancellationToken | CancellationToken | Yes | キャンセルトークン | - |

### 入力データソース

- ドキュメントの構文ツリー(SyntaxTree)
- 言語サービスで登録されたICodeCleanupProvider

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| Document | Document | クリーンアップ後のドキュメント |
| SyntaxNode | SyntaxNode | クリーンアップ後の構文ノード(SyntaxNode版API使用時) |

### 出力先

- クリーンアップされた新しいドキュメントとして返却

## 処理フロー

### 処理シーケンス

```
1. 範囲の正規化
   └─ spans.ToNormalizedSpans()で重複・隣接範囲をマージ
2. 全体クリーンアップ判定
   └─ 範囲がドキュメント全体をカバーする場合は最適化パスを使用
3. アノテーション付与
   └─ AnnotateNodeForTextSpansで範囲をトークンにマーキング
4. プロバイダー順次実行
   └─ IterateAllCodeCleanupProvidersAsyncで各プロバイダーを実行
5. Span追跡
   └─ 各プロバイダー実行後にGetTextSpansFromAnnotationでSpanを再計算
6. 結果返却
   └─ 変更があった場合は新しいドキュメント、なければ元のドキュメントを返却
```

### フローチャート

```mermaid
flowchart TD
    A[開始: CleanupAsync] --> B[spans.ToNormalizedSpans]
    B --> C{spans空?}
    C -->|Yes| D[元のドキュメントを返却]
    C -->|No| E{全体クリーンアップ?}
    E -->|Yes| F[Span追跡なしで実行]
    E -->|No| G[AnnotateNodeForTextSpans]
    G --> H{アノテーション必要?}
    H -->|No| F
    H -->|Yes| I[アノテーション付きドキュメント作成]
    I --> J[IterateAllCodeCleanupProvidersAsync]
    F --> J
    J --> K[各ICodeCleanupProvider.CleanupAsync]
    K --> L{次のProvider?}
    L -->|Yes| M[GetTextSpansFromAnnotation]
    M --> K
    L -->|No| N{変更あり?}
    N -->|Yes| O[新しいドキュメントを返却]
    N -->|No| D
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 空範囲時のスキップ | 範囲が空の場合は何も変更せず元のドキュメントを返却 | spans.Any() == false |
| BR-02 | 全体クリーンアップ最適化 | 範囲がドキュメント全体をカバーする場合はSpan追跡を省略 | CleanupWholeNode判定 |
| BR-03 | 回避範囲の除外 | GetSpansToAvoidで返される範囲はクリーンアップから除外 | 常時 |
| BR-04 | 元ドキュメント保持 | 変更がなかった場合はアノテーション付きドキュメントではなく元のドキュメントを返却 | 最終Provider実行後に変更なし |

### 計算ロジック

Span追跡アルゴリズム:
1. 各Spanの前後のトークンにSyntaxAnnotationを付与
2. プロバイダー実行後、アノテーションからSpanを再計算
3. SpanMarkerType(Normal, BeginningOfFile, EndOfFile)でエッジケースを処理

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

該当なし（インメモリ操作のみ）

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | キャンセル | OperationCancelledException | 処理を中断 |
| - | Span追跡失敗 | プロバイダーがSpan外のコードを変更した場合 | 該当Spanを無視して継続 |

### リトライ仕様

なし

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

該当なし

## パフォーマンス要件

- Logger.LogBlockでFunctionId.CodeCleanup_CleanupAsyncをロギング
- 全体クリーンアップ時はSpan追跡を省略して最適化
- 各プロバイダーの実行時間もFunctionId.CodeCleanup_IterateOneCodeCleanupでロギング

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

- DEBUG時のみエラー検証(VerifyNoErrorsAsync)を実行

## 備考

- AbstractCodeCleanerServiceは言語サービスとして登録される
- C#とVBで各言語固有のGetDefaultProvidersを実装
- GetSpansToAvoidで構造化コメントなどの特殊領域を除外可能

---

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

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

### 推奨読解順序

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

まず、コードクリーンアップで使用される主要なデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ICodeCleanupProvider.cs | `src/Workspaces/Core/Portable/CodeCleanup/Providers/ICodeCleanupProvider.cs` | プロバイダーのインターフェース |
| 1-2 | CodeCleanupOptions.cs | `src/Workspaces/Core/Portable/CodeCleanup/CodeCleanupOptions.cs` | オプションの構造 |
| 1-3 | SpanMarker | `src/Workspaces/Core/Portable/CodeCleanup/AbstractCodeCleanerService.cs` 633-672行目 | Span追跡用アノテーション |

**読解のコツ**: SpanMarkerクラスはSpanMarkerType(Normal, BeginningOfFile, EndOfFile)を使ってファイルの先頭・末尾のエッジケースを処理する。

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

処理の起点となるCleanupAsyncを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | AbstractCodeCleanerService.cs | `src/Workspaces/Core/Portable/CodeCleanup/AbstractCodeCleanerService.cs` | メインのサービス実装 |

**主要処理フロー**:
1. **29-71行目**: CleanupAsync(Document版) - メインエントリーポイント
2. **34-38行目**: 空範囲のガード処理
3. **44-49行目**: 全体クリーンアップの最適化判定
4. **52-69行目**: アノテーション付与とプロバイダー実行

#### Step 3: アノテーション処理を理解する

Span追跡のためのアノテーション付与と復元を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | AbstractCodeCleanerService.cs | `src/Workspaces/Core/Portable/CodeCleanup/AbstractCodeCleanerService.cs` | 269-315行目: AnnotateNodeForTextSpans |

**主要処理フロー**:
- **269-315行目**: 範囲の前後トークンにアノテーションを付与
- **114-144行目**: GetTextSpansFromAnnotation - アノテーションからSpanを復元
- **146-232行目**: TryGetTextSpanFromAnnotation - エッジケースの処理

#### Step 4: プロバイダー反復処理を理解する

各プロバイダーの順次実行ロジックを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | AbstractCodeCleanerService.cs | `src/Workspaces/Core/Portable/CodeCleanup/AbstractCodeCleanerService.cs` | 454-518行目: IterateAllCodeCleanupProvidersAsync |

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

```
AbstractCodeCleanerService.CleanupAsync
    │
    ├─ spans.ToNormalizedSpans
    │      └─ 範囲の正規化
    │
    ├─ CleanupWholeNode
    │      └─ 全体クリーンアップの判定
    │
    ├─ AnnotateNodeForTextSpans
    │      ├─ GetNonOverlappingSpans
    │      ├─ GetTokensAroundSpan
    │      └─ InjectAnnotations
    │
    └─ IterateAllCodeCleanupProvidersAsync
           │
           ├─ GetSpans
           │      ├─ spanGetter (GetTextSpansFromAnnotation)
           │      └─ GetSpansToAvoid
           │
           └─ ICodeCleanupProvider.CleanupAsync
                  └─ 各プロバイダーの処理
```

### データフロー図

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

spans ───▶ ToNormalizedSpans ───▶ normalizedSpan
                    │
                    ▼
normalizedSpan ───▶ AnnotateNodeForTextSpans ───▶ (annotatedRoot, annotations)
                    │
                    ▼
annotatedDocument ───▶ IterateAllCodeCleanupProvidersAsync
                    │
                    ├──▶ ICodeCleanupProvider[0].CleanupAsync ───▶ modifiedDocument
                    │          │
                    │          ▼
                    │    GetTextSpansFromAnnotation ───▶ updatedSpans
                    │          │
                    ├──▶ ICodeCleanupProvider[1].CleanupAsync ───▶ modifiedDocument
                    │          ...
                    │
                    └──▶ finalDocument
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| AbstractCodeCleanerService.cs | `src/Workspaces/Core/Portable/CodeCleanup/AbstractCodeCleanerService.cs` | ソース | メインサービス実装 |
| ICodeCleanerService.cs | `src/Workspaces/Core/Portable/CodeCleanup/ICodeCleanerService.cs` | ソース | サービスインターフェース |
| ICodeCleanupProvider.cs | `src/Workspaces/Core/Portable/CodeCleanup/Providers/ICodeCleanupProvider.cs` | ソース | プロバイダーインターフェース |
| CodeCleanupOptions.cs | `src/Workspaces/Core/Portable/CodeCleanup/CodeCleanupOptions.cs` | ソース | オプション定義 |
| CSharpCodeCleanerService.cs | `src/Workspaces/CSharp/Portable/CodeCleanup/CSharpCodeCleanerService.cs` | ソース | C#言語固有実装 |
| VisualBasicCodeCleanerService.cs | `src/Workspaces/VisualBasic/Portable/CodeCleanup/VisualBasicCodeCleanerService.cs` | ソース | VB言語固有実装 |
| FormatCodeCleanupProvider.cs | `src/Workspaces/Core/Portable/CodeCleanup/Providers/FormatCodeCleanupProvider.cs` | ソース | フォーマットプロバイダー |
| SimplificationCodeCleanupProvider.cs | `src/Workspaces/Core/Portable/CodeCleanup/Providers/SimplificationCodeCleanupProvider.cs` | ソース | 簡略化プロバイダー |
