# 機能設計書 60-依存関係アンインストール

## 概要

本ドキュメントは、serve-dのLSPカスタム通知 `served/uninstallDependency` の機能設計を記述する。DUBレシピファイルから依存関係を削除し、dub upgradeとインポート更新を実行する機能である。

### 本機能の処理概要

**業務上の目的・背景**：D言語のDUBプロジェクトにおいて、不要になった依存パッケージをレシピファイルから削除する操作を自動化する。本機能は、dub.sdlまたはdub.json形式のレシピファイルから該当する依存関係の行/エントリを削除し、パッケージ状態の整合性を保つためにDUBのダウンロード確認とインポートパスの更新を一連の処理として実行する。

**機能の利用シーン**：エディタの依存関係管理UI（DUBツリービュー等）から既存パッケージを削除する際に使用される。

**主要な処理内容**：
1. クライアントから `served/uninstallDependency` 通知を受信する
2. 進捗レポートを開始する
3. `removeDependency` 関数でDUBレシピファイルから依存関係を削除する
4. DubComponentの `selectAndDownloadMissing()` でパッケージ整合性を確認する
5. `updateImports()` でインポートパスを更新する
6. 進捗レポートを完了する

**関連システム・外部連携**：ファイルシステム上のDUBレシピファイル（dub.sdl/dub.json）を直接編集する。DubComponentを通じてパッケージ状態を更新する。updateImports（No.54）を内部呼出しする。

**権限による制御**：特に権限による制御はない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 60 | 依存関係削除（served/uninstallDependency） | 主画面 | DUBレシピファイルから依存関係を削除する |

## 機能種別

ファイル編集・状態更新処理（DUBレシピファイル編集、パッケージ状態更新、インポート更新）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| name | string | Yes | 削除対象のDUB依存関係パッケージ名 | なし |

### 入力データソース

LSPクライアントからのJSON-RPC通知（UninstallRequest構造体）

## 出力仕様

### 出力データ

本機能は通知（Notification）であるため、直接の戻り値はない。以下の副作用を生じる。

| 項目名 | 型 | 説明 |
|--------|-----|------|
| レシピファイル編集 | ファイルシステム | dub.sdlまたはdub.jsonから依存関係エントリを削除 |
| パッケージ状態更新 | ファイルシステム | DUBのパッケージ整合性確認 |
| インポートパス更新 | インメモリ | DCDとインデックスのインポートパスを更新 |

### 出力先

- ファイルシステム上のDUBレシピファイル
- 進捗通知によるUI更新

## 処理フロー

### 処理シーケンス

```
1. 通知受信
   └─ UninstallRequestとしてデシリアライズ
2. 進捗レポート開始
   └─ ProgressType.importUpgrades (0/10)
3. 依存関係削除
   └─ removeDependency() でレシピファイルから依存関係を削除
       ├─ [dub.sdlの場合] 対象dependency行を丸ごと削除
       └─ [dub.jsonの場合] 正規表現で対象エントリを削除（4パターンの正規表現で処理）
4. 不足パッケージ確認
   └─ DubComponent.selectAndDownloadMissing() でパッケージ状態を確認
5. 進捗レポート中間
   └─ ProgressType.dubReload (7/10)
6. インポートパス更新
   └─ updateImports(UpdateImportsParams(false)) で進捗通知なしで更新
7. 進捗レポート完了
   └─ ProgressType.dubReload (10/10)
```

### フローチャート

```mermaid
flowchart TD
    A[served/uninstallDependency 通知受信] --> B[進捗レポート開始 0/10]
    B --> C[removeDependency でレシピ編集]
    C --> D{dub.sdl 存在?}
    D -->|Yes| E[SDL: 対象行を削除]
    D -->|No| F{dub.json/package.json 存在?}
    F -->|Yes| G[JSON: 正規表現で削除]
    F -->|No| H[false を返却]
    E --> I{DubComponent あり?}
    G --> I
    H --> I
    I -->|Yes| J[selectAndDownloadMissing]
    I -->|No| K[進捗レポート 7/10]
    J --> K
    K --> L[updateImports 呼出]
    L --> M[進捗レポート完了 10/10]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-60-01 | SDL/JSON自動判定 | dub.sdlが存在する場合はSDL形式、そうでない場合はdub.json/package.jsonを使用する | 常時 |
| BR-60-02 | SDL行削除 | SDL形式では、対象パッケージのdependency行を丸ごと削除する（行の結合で行を除外） | dub.sdl使用時 |
| BR-60-03 | JSON正規表現削除 | JSON形式では4パターンの正規表現を順に試行して対象エントリを削除する | dub.json使用時 |
| BR-60-04 | JSON削除パターン | (1) 末尾カンマ付きエントリ削除 (2) 先頭カンマ付きエントリ削除 (3) 唯一のエントリの場合dependenciesブロック削除（末尾カンマ付き） (4) 唯一のエントリの場合dependenciesブロック削除（先頭カンマ付き） | dub.json使用時 |
| BR-60-05 | depth追跡（SDL） | `{` と `}` の出現数でdepthを追跡し、トップレベルのdependency行のみを対象とする | dub.sdl使用時 |
| BR-60-06 | 無条件後続処理 | removeDependencyの成否に関わらず、selectAndDownloadMissingとupdateImportsを実行する | 常時 |

### 計算ロジック

特になし。

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

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

本機能はデータベースを使用しない。ファイルシステム上のDUBレシピファイルを直接編集する。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 暗黙的失敗（false） | dub.sdl/dub.jsonが存在しない場合 | removeDependencyがfalseを返却するが、後続処理は実行される |
| - | 暗黙的失敗（false） | 対象パッケージ名がレシピファイルに見つからない場合 | removeDependencyがfalseを返却するが、後続処理は実行される |

### リトライ仕様

リトライは不要。

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

トランザクション管理は不要。レシピファイルの書き込みはアトミックではない。

## パフォーマンス要件

レシピファイルの編集はファイルシステムI/Oを伴う。selectAndDownloadMissingはネットワーク通信を伴う可能性がある。

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

正規表現を使用したファイル編集を行うため、パッケージ名にメタ文字が含まれる場合はインジェクションのリスクがある。ただし、DUBのパッケージ名の命名規則により、通常はメタ文字は含まれない。

## 備考

- コードにTODOコメント（609行目）があり、dub.selections.jsonからの削除が未実装である
- コードにTODOコメント（614行目）があり、workspace引数の追加が未実装である
- No.58（インストール）やNo.59（更新）と異なり、removeDependencyの結果に関わらず後続処理（selectAndDownloadMissing, updateImports）が実行される
- JSON形式の削除では4パターンの正規表現を順に試行し、最初にマッチしたパターンで削除する

---

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

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

### 推奨読解順序

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

UninstallRequest構造体のフィールドを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | protoext.d | `source/served/lsp/protoext.d` | UninstallRequest構造体（119-123行目）のnameフィールドのみを持つことを確認 |

**読解のコツ**: No.58（InstallRequest）やNo.59（UpdateRequest）と異なり、UninstallRequestにはversionフィールドがない。削除にはパッケージ名のみで十分であるため。

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

uninstallDependency関数の全体処理を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | dub.d | `source/served/commands/dub.d` | uninstallDependency関数（606-621行目）のエントリーポイント |

**主要処理フロー**:
1. **606行目**: `@protocolNotification("served/uninstallDependency")` - LSP通知として登録
2. **609行目**: `// TODO: remove from dub.selections.json` - 未実装のTODO
3. **613行目**: `reportProgress(ProgressType.importUpgrades, 0, 10, uri)` - 進捗開始
4. **614行目**: `// TODO: add workspace argument` - 未実装のTODO
5. **615行目**: `removeDependency(instance, req.name)` - レシピファイルからの削除
6. **617行目**: `instance.get!DubComponent.selectAndDownloadMissing()` - パッケージ状態更新
7. **619行目**: `updateImports(UpdateImportsParams(false))` - インポートパス更新

#### Step 3: 依存関係削除処理を理解する

removeDependency関数のSDL/JSON各形式での削除ロジックを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | dub.d | `source/served/commands/dub.d` | removeDependency関数（778-823行目） |

**主要処理フロー**:
- **780-799行目**: SDL形式の処理。depth追跡でトップレベルの対象パッケージ行を探し、その行を配列結合で除外
- **800-823行目**: JSON形式の処理。4パターンの正規表現を順に試行
  - **809行目**: パターン1: `"name" : "ver",\s*` (末尾カンマ付きエントリ)
  - **811行目**: パターン2: `\s*,\s*"name" : "ver"` (先頭カンマ付きエントリ)
  - **813-814行目**: パターン3: `"dependencies"\s*:\s*\{\s*"name"..."ver"\s*\}\s*,\s*` (唯一エントリ、dependenciesブロック末尾カンマ)
  - **816-817行目**: パターン4: `\s*,\s*"dependencies"\s*:\s*\{\s*"name"..."ver"\s*\}` (唯一エントリ、dependenciesブロック先頭カンマ)

**読解のコツ**: JSON形式の削除では、カンマの位置によるJSON構文エラーを防ぐために4パターンの正規表現を用意している。これはJSONの末尾カンマ問題（trailing comma）に対応するための設計。

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

```
uninstallDependency(UninstallRequest) [source/served/commands/dub.d:607]
    |
    +-- activeInstance [source/served/types.d:471]
    |
    +-- reportProgress(...) [source/served/utils/progress.d:53]
    |
    +-- removeDependency(instance, name) [source/served/commands/dub.d:778]
    |       |
    |       +-- fs.exists(sdl) / fs.exists(json) [ファイル存在確認]
    |       +-- fs.readText / splitLines [ファイル読込]
    |       +-- [SDL] 行削除（配列スライス結合）
    |       +-- [JSON] replaceFirst(regex(...)) x 4パターン
    |       +-- fs.write [ファイル書き込み]
    |
    +-- DubComponent.selectAndDownloadMissing() [workspace-d API]
    |
    +-- updateImports(UpdateImportsParams(false)) [source/served/commands/dub.d:213]
    |       |
    |       +-- DubComponent.update.getYield
    |       +-- DCDComponent.refreshImports()
    |       +-- backgroundIndex()
    |
    +-- reportProgress(完了)
```

### データフロー図

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

UninstallRequest           -->  uninstallDependency()              -->  レシピファイル編集
  name: string                   |                                       パッケージ状態更新
                                 +-- removeDependency (行/エントリ削除)  インポートパス更新
                                 +-- selectAndDownloadMissing            進捗通知
                                 +-- updateImports
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| dub.d | `source/served/commands/dub.d` | ソース | uninstallDependency（606-621行目）、removeDependency（778-823行目） |
| protoext.d | `source/served/lsp/protoext.d` | ソース | UninstallRequest構造体定義（119-123行目） |
| progress.d | `source/served/utils/progress.d` | ソース | reportProgress関数 |
| types.d | `source/served/types.d` | ソース | activeInstanceプロパティ、backend定義 |
| index.d | `source/served/commands/index.d` | ソース | backgroundIndex関数（updateImports経由で呼出） |
