# 機能設計書 33-joinオプション

## 概要

本ドキュメントは、CoffeeScript CLIの`--join`（`-j`）オプションの機能設計を記述する。このオプションは複数のCoffeeScriptソースファイルをコンパイル前に結合する機能を提供する。**なお、本オプションは非推奨（deprecated）であり、将来のバージョンで削除予定である。**

### 本機能の処理概要

**業務上の目的・背景**：複数のCoffeeScriptファイルを単一のJavaScriptファイルにバンドルする必要がある場合に、ソースコード結合を行う。ただし、ES6モジュールやモジュールバンドラー（Webpack、Rollup等）の普及により、本機能の必要性は低下している。ファイル間でローカル変数を共有する必要がある特殊なケースでのみ使用が想定される。

**機能の利用シーン**：
- レガシープロジェクトで複数のCoffeeScriptファイルを単一ファイルに結合したい場合
- モジュールバンドラーを導入せずにシンプルなファイル結合を行いたい場合
- ファイル間でローカル変数を共有する必要がある場合（非推奨パターン）

**主要な処理内容**：
1. `--join`オプションで出力先ファイルパスを指定
2. 複数のソースファイルを順番に読み込み
3. 各ファイルの内容をsourceCode配列に格納
4. すべてのファイル読み込み完了後、改行で結合
5. 結合したコードを単一のスクリプトとしてコンパイル
6. コンパイル結果を指定されたファイルに出力

**関連システム・外部連携**：ファイルシステム（fs）を使用してファイルの読み書きを行う。

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

## 関連画面

本機能はCLI機能のため、画面との直接的な関連はない。

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | - |

## 機能種別

ファイル結合 / コンパイル（非推奨）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| --join / -j | String | No | 結合後の出力先ファイルパス | 有効なファイルパスであること |
| source files | String[] | Yes | 結合するソースファイル群 | 存在するCoffeeScriptファイルであること |

### 入力データソース

コマンドライン引数で指定されたソースファイル群

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| compiledJS | String | 結合・コンパイルされたJavaScriptコード |

### 出力先

`--join`オプションで指定されたファイルパス

## 処理フロー

### 処理シーケンス

```
1. コマンドライン引数の解析
   └─ opts.joinに出力先パスを格納
2. 非推奨警告の出力
   └─ console.errorで警告メッセージを表示
3. 出力パスの解決
   └─ path.resolveで絶対パスに変換
4. 各ソースファイルの処理
   └─ compilePath経由でcompileScriptが呼ばれる
5. compileScript内でのjoin処理判定
   └─ opts.joinがあり、ファイルパスがopts.joinと異なる場合
6. Literate判定と変換
   └─ ファイルがLiterateの場合はinvertLiterateで変換
7. ソースコードの格納
   └─ sourceCode配列の該当インデックスに格納
8. compileJoin関数の呼び出し
   └─ すべてのファイル読み込み完了をチェック
9. 100msの待機後にコンパイル実行
   └─ sourceCode.join('\n')で結合
10. 結合コードのコンパイルと出力
    └─ compileScript(opts.join, 結合コード, opts.join)
```

### フローチャート

```mermaid
flowchart TD
    A[coffee --join bundle.js a.coffee b.coffee] --> B[非推奨警告を出力]
    B --> C[opts.join = path.resolve 'bundle.js']
    C --> D[for source in opts.arguments]
    D --> E[compilePath source]
    E --> F[compileScript file, code, base]
    F --> G{opts.join あり?}
    G -->|No| H[通常コンパイル]
    G -->|Yes| I{file === opts.join?}
    I -->|Yes| H
    I -->|No| J{Literate ファイル?}
    J -->|Yes| K[invertLiterate で変換]
    J -->|No| L[そのまま使用]
    K --> M[sourceCode配列に格納]
    L --> M
    M --> N[compileJoin 呼び出し]
    N --> O{全ファイル読み込み完了?}
    O -->|No| P[処理継続]
    P --> D
    O -->|Yes| Q[clearTimeout joinTimeout]
    Q --> R[100ms 待機]
    R --> S[sourceCode.join '\n' で結合]
    S --> T[compileScript opts.join, 結合コード, opts.join]
    T --> U[writeJs でファイル出力]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-33-01 | 非推奨警告 | --joinオプション使用時は非推奨警告を出力する | --join指定時 |
| BR-33-02 | ファイル順序維持 | ソースファイルは引数で指定された順序で結合される | 常時 |
| BR-33-03 | Literate自動変換 | Literate CoffeeScriptファイルは結合前にinvertLiterateで変換 | .litcoffee, .coffee.md拡張子 |
| BR-33-04 | 結合待機 | すべてのファイル読み込み完了後、100ms待機してから結合 | --watch併用時の安定化 |
| BR-33-05 | 出力ファイル除外 | 出力先ファイルはソースファイルとして処理されない | file === opts.join |

### 計算ロジック

**ファイル結合ロジック（compileJoin関数内）**:
```coffeescript
sourceCode.join('\n')  # 各ファイルの内容を改行で結合
```

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

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

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| ENOENT | ファイルエラー | 指定されたソースファイルが存在しない | ファイルパスを確認する |
| SYNTAX_ERROR | SyntaxError | 結合後のコードに構文エラーがある | 各ファイルの構文を確認する |
| - | 出力エラー | 出力先ディレクトリが存在しない/書き込み権限がない | 出力パスを確認する |

### リトライ仕様

リトライは行わない。

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

該当なし。

## パフォーマンス要件

- すべてのファイルをメモリ上に読み込むため、非常に大きなプロジェクトでは注意が必要
- `--watch`オプション併用時は、100msのデバウンスで再コンパイルを最適化

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

- 出力先ファイルパスの検証が行われないため、意図しないパスへの書き込みに注意

## 備考

**非推奨の理由と代替手段**：

本オプションは非推奨であり、将来のバージョンで削除予定である。代替手段として以下が推奨される：

```bash
# 非推奨（--join使用）
$ coffee --compile --join bundle.js -- a.coffee b.coffee c.coffee

# 推奨（cat + --stdio使用）
$ cat a.coffee b.coffee c.coffee | coffee --compile --stdio > bundle.js
```

ファイル間でローカル変数を共有する必要がある場合は、モジュールシステム（ES6 import/export）の使用を検討すべきである。

---

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

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

### 推奨読解順序

#### Step 1: オプション定義と非推奨警告を理解する

まず、--joinオプションの定義と非推奨警告の実装を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | command.coffee | `src/command.coffee` | SWITCHESでの-j/--joinオプション定義 |
| 1-2 | command.coffee | `src/command.coffee` | run関数内での非推奨警告出力 |

**主要処理フロー**:
- **41行目**: `-j`/`--join [FILE]`オプションの定義
- **121-136行目**: 非推奨警告の出力
  ```coffeescript
  if opts.join
    opts.join = path.resolve opts.join
    console.error '''
      The --join option is deprecated and will be removed in a future version.
      ...
    '''
  ```

#### Step 2: sourceCode配列とsources配列の役割を理解する

ファイル管理用のグローバル変数を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | command.coffee | `src/command.coffee` | グローバル変数の定義 |

**主要処理フロー**:
- **59-61行目**: グローバル変数の定義
  ```coffeescript
  sources      = []    # ソースファイルパスの配列
  sourceCode   = []    # 各ファイルのソースコードを格納する配列
  ```

#### Step 3: compileScript内でのjoin処理を理解する

コンパイル処理内でのjoin分岐を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | command.coffee | `src/command.coffee` | compileScript関数内のjoin処理 |

**主要処理フロー**:
- **218-221行目**: join処理の分岐
  ```coffeescript
  else if opts.join and task.file isnt opts.join
    task.input = helpers.invertLiterate task.input if helpers.isLiterate file
    sourceCode[sources.indexOf(task.file)] = task.input
    compileJoin()
  ```

**読解のコツ**: `task.file isnt opts.join`のチェックは、出力先ファイル自体がソースとして処理されるのを防ぐためのガード条件。

#### Step 4: compileJoin関数を理解する

結合とコンパイルの実行を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | command.coffee | `src/command.coffee` | compileJoin関数の実装 |

**主要処理フロー**:
- **263行目**: `joinTimeout = null` - タイムアウトID格納用変数
- **264-269行目**: compileJoin関数
  ```coffeescript
  compileJoin = ->
    return unless opts.join                            # joinオプションなければ終了
    unless sourceCode.some((code) -> code is null)     # 全ファイル読み込み完了チェック
      clearTimeout joinTimeout                          # 前回のタイムアウトをクリア
      joinTimeout = wait 100, ->                        # 100ms待機
        compileScript opts.join, sourceCode.join('\n'), opts.join  # 結合してコンパイル
  ```

**読解のコツ**: `sourceCode.some((code) -> code is null)`は、まだ読み込まれていないファイルがあるかをチェックしている。すべてのファイルが読み込まれるまでコンパイルは実行されない。

#### Step 5: Literate CoffeeScript対応を理解する

Literateファイルの変換処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | helpers.coffee | `src/helpers.coffee` | isLiterate関数とinvertLiterate関数 |

**主要処理フロー**:
- helpers.coffee **207行目**: `isLiterate = (file) -> /\.(litcoffee|coffee\.md)$/.test file`
- helpers.coffee **67-92行目**: `invertLiterate`関数 - Markdownからコードブロックを抽出

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

```
exports.run (command.coffee:72)
    │
    ├─ parseOptions (command.coffee:445)
    │      └─ opts.join に出力ファイルパスを格納
    │
    ├─ 非推奨警告出力 (command.coffee:121-136)
    │      └─ console.error(警告メッセージ)
    │
    ├─ for source in opts.arguments (command.coffee:137-139)
    │      └─ compilePath(source, yes, source)
    │             │
    │             └─ compileScript(file, code, base) (command.coffee:202)
    │                    │
    │                    ├─ opts.join && file !== opts.join
    │                    │      ├─ helpers.isLiterate(file)
    │                    │      │      └─ helpers.invertLiterate(input)
    │                    │      ├─ sourceCode[index] = input
    │                    │      └─ compileJoin()
    │                    │
    │                    └─ compileJoin (command.coffee:264)
    │                           ├─ sourceCode.some(null) チェック
    │                           ├─ wait(100, ...)
    │                           └─ compileScript(opts.join, sourceCode.join('\n'), opts.join)
    │
    └─ writeJs(base, sourcePath, js, jsPath) (command.coffee:407)
```

### データフロー図

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

a.coffee ──読込──▶ sourceCode[0] = "..."
                          │
b.coffee ──読込──▶ sourceCode[1] = "..."
                          │
c.coffee ──読込──▶ sourceCode[2] = "..."
                          │
                          ▼
               sourceCode.some(null) === false
                          │
                          ▼
               100ms wait (デバウンス)
                          │
                          ▼
               sourceCode.join('\n')
                          │
                          ▼
               CoffeeScript.compile(結合コード)
                          │
                          ▼
               writeJs ───────────────────▶ bundle.js
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| command.coffee | `src/command.coffee` | ソース | CLI本体、compileJoin関数、compileScript関数 |
| helpers.coffee | `src/helpers.coffee` | ソース | isLiterate関数、invertLiterate関数 |
| coffeescript.coffee | `src/coffeescript.coffee` | ソース | compile関数（コンパイル本体） |
