# 機能設計書 38-jest-resolve

## 概要

本ドキュメントは、jest-resolveパッケージの機能設計を記載する。jest-resolveは、Jestテストフレームワークにおけるモジュールパスの解決機能を提供し、Node.jsのモジュール解決アルゴリズムをベースに、Jestの設定（moduleNameMapper、modulePaths等）を反映したカスタム解決を行う。

### 本機能の処理概要

jest-resolveは、テスト実行時にimport/requireされるモジュールのパスを解決するライブラリである。

**業務上の目的・背景**：テスト実行時には、テスト対象のモジュールや依存モジュールを正しく読み込む必要がある。Node.jsの標準モジュール解決アルゴリズムに加えて、Jestの設定（moduleNameMapper、modulePaths、moduleDirectories等）を反映した柔軟なモジュール解決が求められる。また、Hasteモジュール（React Nativeなどで使用）のサポートや、PnP（Yarn Plug'n'Play）対応も必要である。

**機能の利用シーン**：
- テストファイル内でのimport/require文の解決
- jest.mockでのモジュールパス解決
- moduleNameMapperによるエイリアス解決
- 依存関係グラフの構築時

**主要な処理内容**：
1. Resolverクラス: モジュール解決の中心クラス
2. findNodeModule/findNodeModuleAsync: Node.jsモジュールの検索
3. resolveModule/resolveModuleAsync: モジュールパスの完全解決
4. resolveStubModuleName: moduleNameMapperによるマッピング処理
5. shouldLoadAsEsm: ESMとしてロードすべきかの判定
6. ModuleNotFoundError: モジュール未発見エラーの生成

**関連システム・外部連携**：
- jest-haste-map: Hasteモジュールマップの提供
- jest-util: ユーティリティ関数（tryRealpath等）
- jest-validate: 設定検証
- unrs-resolver: 高速なモジュール解決エンジン
- jest-pnp-resolver: Yarn PnP対応

**権限による制御**：特になし

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | CLI出力 | エラー表示 | モジュール未発見時のエラーメッセージ表示 |

## 機能種別

モジュール解決 / パス解決 / 基盤ユーティリティ

## 入力仕様

### 入力パラメータ

#### Resolverコンストラクタ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| moduleMap | IModuleMap | Yes | jest-haste-mapからのモジュールマップ | - |
| options | ResolverConfig | Yes | 解決オプション | - |

#### ResolverConfig

| パラメータ名 | 型 | 必須 | 説明 | デフォルト値 |
|-------------|-----|-----|------|-------------|
| defaultPlatform | string \| null | No | デフォルトプラットフォーム（ios, android等） | undefined |
| extensions | Array<string> | Yes | 拡張子リスト | - |
| hasCoreModules | boolean | No | Nodeコアモジュールを解決するか | true |
| moduleDirectories | Array<string> | No | モジュールディレクトリ | ['node_modules'] |
| moduleNameMapper | Array<ModuleNameMapperConfig> | No | モジュール名マッピング | null |
| modulePaths | Array<string> | No | 追加検索パス | undefined |
| platforms | Array<string> | No | プラットフォームリスト | undefined |
| resolver | string \| null | No | カスタムリゾルバーパス | null |
| rootDir | string | Yes | プロジェクトルートディレクトリ | - |

#### resolveModule

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| from | string | Yes | 解決元のファイルパス | - |
| moduleName | string | Yes | 解決対象のモジュール名 | - |
| options | ResolveModuleConfig | No | 解決オプション | - |

#### ResolveModuleConfig

| パラメータ名 | 型 | 必須 | 説明 | デフォルト値 |
|-------------|-----|-----|------|-------------|
| conditions | Array<string> | No | エクスポート条件 | undefined |
| skipNodeResolution | boolean | No | Node解決をスキップ | false |
| paths | Array<string> | No | 追加検索パス | undefined |

### 入力データソース

- jest-config: 設定オブジェクト
- jest-haste-map: モジュールマップ
- ファイルシステム: モジュールファイル

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| 解決済みパス | string | モジュールの絶対パス |
| ModuleNotFoundError | Error | モジュール未発見時の例外 |

### 出力先

関数の戻り値として返却

## 処理フロー

### 処理シーケンス

```
1. resolveModule/resolveModuleAsyncの呼び出し
   └─ fromパスとmoduleNameを受け取る
2. resolveStubModuleName
   └─ moduleNameMapperでマッピング処理
   └─ マッチした場合はマッピング先のモジュールを解決
3. resolveModuleFromDirIfExists
   └─ キャッシュチェック
   └─ Hasteモジュールチェック（getModule）
   └─ Nodeモジュール解決（findNodeModule）
   └─ Hasteパッケージ解決
4. findNodeModule/findNodeModuleAsync
   └─ カスタムリゾルバーがあれば使用
   └─ なければdefaultResolverを使用
5. defaultResolver
   └─ コアモジュールチェック（isBuiltin）
   └─ PnP環境チェック
   └─ unrs-resolverで解決
6. モジュールが見つからない場合
   └─ ModuleNotFoundErrorをスロー
```

### フローチャート

```mermaid
flowchart TD
    A[resolveModule開始] --> B[resolveStubModuleName]
    B -->|マッチ| C[マッピング先を解決]
    B -->|マッチなし| D[resolveModuleFromDirIfExists]
    C --> E{解決成功?}
    D --> E
    E -->|Yes| F[解決済みパスを返却]
    E -->|No| G{Hasteモジュール?}
    G -->|Yes| H[getModule]
    G -->|No| I[findNodeModule]
    H --> J{存在?}
    I --> J
    J -->|Yes| F
    J -->|No| K{Hasteパッケージ?}
    K -->|Yes| L[パッケージパスを解決]
    K -->|No| M[ModuleNotFoundError]
    L --> N{存在?}
    N -->|Yes| F
    N -->|No| M
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | moduleNameMapperの優先 | moduleNameMapperが設定されている場合、最初にマッピング処理を行う | moduleNameMapperが設定されている場合 |
| BR-02 | Hasteモジュールの優先 | node_modules解決の前にHasteモジュールマップを検索 | 常時 |
| BR-03 | プラットフォーム拡張子 | プラットフォーム固有の拡張子（.ios.js, .android.js等）を優先的に解決 | defaultPlatformが設定されている場合 |
| BR-04 | コアモジュール判定 | Node.jsのビルトインモジュールはパス解決をスキップ | hasCoreModulesがtrue |
| BR-05 | キャッシュの活用 | 同一の解決要求はキャッシュから返却 | 常時 |
| BR-06 | PnP対応 | Yarn PnP環境ではjest-pnp-resolverを使用 | process.versions.pnpが存在 |

### 計算ロジック

**モジュールID生成**：
```typescript
// moduleType + absolutePath + mockPath + options
const id = moduleType + sep + absolutePath + sep + mockPath + sep + options;
```

**プラットフォーム拡張子の順序**：
```typescript
// 例: defaultPlatform = 'ios', extensions = ['.js', '.ts']
// 生成される拡張子順序:
// ['.ios.js', '.ios.ts', '.native.js', '.native.ts', '.js', '.ts']
```

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

該当なし

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| MODULE_NOT_FOUND | ModuleNotFoundError | モジュールが見つからない場合 | requireStackを含むエラーメッセージを生成 |
| - | Error | moduleNameMapperのマッピング先が見つからない場合 | 設定内容を含む詳細エラーメッセージを生成 |
| - | Error | カスタムリゾルバーが不正な形式の場合 | リゾルバーパスを含むエラーメッセージ |

### リトライ仕様

- 特になし（ファイルシステム操作はキャッシュされる）

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

該当なし

## パフォーマンス要件

- 複数レベルのキャッシュ（モジュールパス、モジュール名、モジュールID）
- unrs-resolverによる高速なファイルシステム走査
- ファイルシステム操作結果のキャッシュ

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

- カスタムリゾルバーはrequireで読み込まれるため、信頼できるパスのみを指定すること

## 備考

- ESM判定（shouldLoadAsEsm）はNode.js公式仕様（https://github.com/nodejs/modules/issues/393）の代替実装
- unrs-resolverはRustベースの高速リゾルバー

---

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

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

### 推奨読解順序

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

まず、型定義から理解を始める。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | types.ts | `packages/jest-resolve/src/types.ts` | ResolverConfig、ModuleNameMapperConfig型の定義 |

**主要処理フロー**:
1. **8-18行目**: ResolverConfig型（リゾルバー設定）
2. **20-23行目**: ModuleNameMapperConfig型（マッピング設定）
3. **31行目**: PackageJSON型（package.json構造）

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `packages/jest-resolve/src/index.ts` | エクスポート一覧 |

**主要処理フロー**:
1. **8行目**: Resolverクラスのインポート
2. **10-19行目**: 型のエクスポート（AsyncResolver、SyncResolver、ResolverOptions等）
3. **21行目**: utilsのエクスポート
4. **23行目**: Resolverクラスのデフォルトエクスポート

#### Step 3: メインのResolverクラスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | resolver.ts | `packages/jest-resolve/src/resolver.ts` | Resolverクラスの実装 |

**主要処理フロー**:
1. **55-83行目**: Resolverクラスのコンストラクタ
   - **56-61行目**: プライベートフィールド定義（キャッシュ等）
   - **63-83行目**: オプションの初期化とキャッシュの生成
2. **102-105行目**: clearDefaultResolverCache（キャッシュクリア）
3. **107-140行目**: findNodeModule（同期版Node.jsモジュール検索）
   - **111-118行目**: カスタムリゾルバーのロード
   - **122-133行目**: リゾルバー呼び出し
4. **142-185行目**: findNodeModuleAsync（非同期版）
5. **190-263行目**: resolveModuleFromDirIfExists
   - **202-205行目**: キャッシュチェック
   - **207-212行目**: Hasteモジュールチェック
   - **220-245行目**: Nodeモジュール解決
   - **249-260行目**: Hasteパッケージ解決
6. **344-359行目**: resolveModule（メインの解決メソッド）
   - **351行目**: resolveStubModuleNameの呼び出し
   - **352行目**: resolveModuleFromDirIfExistsの呼び出し
   - **358行目**: エラースロー
7. **457-463行目**: isCoreModule（コアモジュール判定）
8. **738-796行目**: resolveStubModuleName（moduleNameMapper処理）
   - **752-794行目**: マッピングのループ処理
   - **757行目**: 正規表現マッチの取得
   - **765-775行目**: マッピング先の解決

#### Step 4: デフォルトリゾルバーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | defaultResolver.ts | `packages/jest-resolve/src/defaultResolver.ts` | デフォルトの解決ロジック |

**主要処理フロー**:
1. **18-49行目**: ResolverOptions型（解決オプション）
2. **51-57行目**: SyncResolver、AsyncResolver、Resolver型
3. **59-64行目**: handleResolveResult（結果のエラーハンドリング）
4. **66-160行目**: baseResolver関数
   - **79-81行目**: ビルトインモジュールチェック
   - **83-85行目**: PnP環境でのjest-pnp-resolver使用
   - **87-89行目**: file://スキームの処理
   - **107-113行目**: 解決オプションの構築
   - **115-123行目**: unrs-resolverインスタンスの取得/生成
   - **155-159行目**: 解決の実行
5. **162行目**: defaultResolver（同期版エクスポート）
6. **164-167行目**: defaultAsyncResolver（非同期版エクスポート）

#### Step 5: ユーティリティ関数を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | fileWalkers.ts | `packages/jest-resolve/src/fileWalkers.ts` | ファイルシステムキャッシュ |

**主要処理フロー**:
1. **16-22行目**: getResolver/setResolver（リゾルバーインスタンス管理）
2. **24-29行目**: clearFsCache（キャッシュクリア）
3. **31-64行目**: statSyncCached（キャッシュ付きstat）
4. **66-84行目**: realpathCached（キャッシュ付きrealpath）
5. **86-99行目**: readPackageCached（キャッシュ付きpackage.json読み込み）
6. **104-125行目**: findClosestPackageJson（最寄りのpackage.json検索）

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-2 | shouldLoadAsEsm.ts | `packages/jest-resolve/src/shouldLoadAsEsm.ts` | ESM判定ロジック |

**主要処理フロー**:
1. **12-22行目**: キャッシュとクリア関数
2. **24-40行目**: cachedShouldLoadAsEsm（キャッシュ付きESM判定）
3. **43-71行目**: shouldLoadAsEsm（ESM判定ロジック）
   - **49-51行目**: .mjsは常にESM
   - **53-55行目**: .cjsは常にCJS
   - **57-59行目**: 拡張子リストによる判定
   - **61-70行目**: package.jsonのtype: "module"チェック
4. **73-93行目**: cachedPkgCheck（package.jsonチェック）

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-3 | nodeModulesPaths.ts | `packages/jest-resolve/src/nodeModulesPaths.ts` | node_modulesパス生成 |

**主要処理フロー**:
1. **18-74行目**: nodeModulesPaths（検索パス生成）
   - **22-25行目**: モジュールディレクトリの取得
   - **29行目**: basedirの絶対パス化
   - **40-46行目**: 物理パスの取得（シンボリックリンク対応）
   - **48-53行目**: 親ディレクトリの走査
   - **55-67行目**: 各ディレクトリに対するnode_modulesパス生成
2. **76-88行目**: findGlobalPaths/GlobalPaths（グローバルパス）

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-4 | ModuleNotFoundError.ts | `packages/jest-resolve/src/ModuleNotFoundError.ts` | エラークラス |

**主要処理フロー**:
1. **11-24行目**: ModuleNotFoundErrorクラス
   - **12行目**: code = 'MODULE_NOT_FOUND'
   - **13-16行目**: hint、requireStack、moduleName等のプロパティ
2. **26-49行目**: buildMessage（エラーメッセージ構築）
   - **33-42行目**: requireStackの整形
   - **44-46行目**: hintの追加
3. **51-54行目**: duckType（静的メソッド、エラーの型変換）

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

```
Resolver (resolver.ts)
    │
    ├─ resolveModule / resolveModuleAsync
    │      │
    │      ├─ resolveStubModuleName / resolveStubModuleNameAsync
    │      │      └─ moduleNameMapperによるマッピング処理
    │      │
    │      └─ resolveModuleFromDirIfExists / resolveModuleFromDirIfExistsAsync
    │             │
    │             ├─ getModule (Hasteモジュール検索)
    │             │      └─ IModuleMap.getModule (jest-haste-map)
    │             │
    │             ├─ findNodeModule / findNodeModuleAsync
    │             │      │
    │             │      └─ loadResolver
    │             │             │
    │             │             ├─ defaultResolver (defaultResolver.ts)
    │             │             │      │
    │             │             │      ├─ isBuiltin (module)
    │             │             │      ├─ pnpResolver (jest-pnp-resolver)
    │             │             │      └─ unrs-resolver
    │             │             │
    │             │             └─ カスタムリゾルバー
    │             │
    │             └─ _getHasteModulePath (Hasteパッケージ)
    │                    └─ getPackage
    │
    ├─ getModulePaths
    │      └─ nodeModulesPaths (nodeModulesPaths.ts)
    │
    ├─ isCoreModule
    │      └─ isBuiltin (module)
    │
    └─ shouldLoadAsEsm (shouldLoadAsEsm.ts)
           └─ findClosestPackageJson (fileWalkers.ts)
                  └─ readPackageCached
```

### データフロー図

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

from (解決元パス) ────────▶ resolveModule()
                                  │
moduleName (モジュール名) ───▶    │
                                  │
options (解決オプション) ────▶    │
                                  │
                                  ├─ moduleNameMapperチェック
                                  │      │
                                  │      ├─ マッチ → マッピング先を解決
                                  │      │
                                  │      └─ マッチなし → 継続
                                  │
                                  ├─ キャッシュチェック
                                  │      │
                                  │      └─ キャッシュあり → 即時返却
                                  │
                                  ├─ Hasteモジュールマップ検索
                                  │      │
moduleMap (jest-haste-map) ───▶   │      └─ 存在 → パスを返却
                                  │
                                  ├─ Node.jsモジュール解決
                                  │      │
                                  │      ├─ コアモジュール判定
                                  │      │      │
                                  │      │      └─ isBuiltin → モジュール名を返却
                                  │      │
                                  │      ├─ PnP環境判定
                                  │      │      │
                                  │      │      └─ pnp → jest-pnp-resolver
                                  │      │
                                  │      └─ unrs-resolver
                                  │             │
ファイルシステム ─────────────▶   │             └─ ファイル検索
                                  │
                                  └─ Hasteパッケージ解決
                                         │
                                         └─ package.json外のHasteモジュール
                                                │
                                                ▼
                                         解決済み絶対パス
                                         (例: /project/node_modules/lodash/index.js)

エラーケース：
moduleName: 'unknown-module'
         │
         ▼
ModuleNotFoundError
  message: "Cannot find module 'unknown-module' from 'src/test.ts'"
  code: 'MODULE_NOT_FOUND'
  requireStack: ['src/test.ts', 'src/index.ts']
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.ts | `packages/jest-resolve/src/index.ts` | ソース | エントリーポイント、エクスポート |
| resolver.ts | `packages/jest-resolve/src/resolver.ts` | ソース | Resolverクラス（メイン処理） |
| defaultResolver.ts | `packages/jest-resolve/src/defaultResolver.ts` | ソース | デフォルトリゾルバー実装 |
| types.ts | `packages/jest-resolve/src/types.ts` | ソース | 型定義 |
| fileWalkers.ts | `packages/jest-resolve/src/fileWalkers.ts` | ソース | ファイルシステム操作とキャッシュ |
| shouldLoadAsEsm.ts | `packages/jest-resolve/src/shouldLoadAsEsm.ts` | ソース | ESM判定ロジック |
| nodeModulesPaths.ts | `packages/jest-resolve/src/nodeModulesPaths.ts` | ソース | node_modulesパス生成 |
| ModuleNotFoundError.ts | `packages/jest-resolve/src/ModuleNotFoundError.ts` | ソース | エラークラス |
| utils.ts | `packages/jest-resolve/src/utils.ts` | ソース | ユーティリティ関数 |
| package.json | `packages/jest-resolve/package.json` | 設定 | パッケージ定義 |
