# 機能設計書 49-スタックトレース変換

## 概要

本ドキュメントは、エラー時のスタックトレースをCoffeeScriptソースの行番号にマッピングするpatchStackTrace機能の設計を記載する。

### 本機能の処理概要

patchStackTrace機能は、Node.js/V8のError.prepareStackTraceをモンキーパッチして、JavaScriptのスタックトレースをCoffeeScriptソースコードの行番号・列番号に変換する機能である。コンパイル時に生成されたソースマップを使用して、エラー発生時のJavaScript行番号をCoffeeScriptの対応する行番号に逆変換する。

**業務上の目的・背景**：CoffeeScriptはJavaScriptにコンパイルされて実行されるため、エラー発生時のスタックトレースはコンパイル後のJavaScript行番号を表示する。これでは、元のCoffeeScriptコードのどこでエラーが発生したかが分かりにくい。patchStackTraceを使用することで、開発者はCoffeeScriptソースの行番号でエラー箇所を特定でき、デバッグ効率が大幅に向上する。

**機能の利用シーン**：
- CoffeeScriptで書かれたアプリケーションでエラーが発生したとき
- coffeeコマンドでスクリプトを実行中にエラーが発生したとき
- REPLでコード評価中にエラーが発生したとき
- テスト実行時にエラーが発生したとき

**主要な処理内容**：
1. Error.prepareStackTraceをカスタム関数で上書き
2. スタックフレームごとにファイル名と行番号を取得
3. ソースマップを使用してCoffeeScript行番号に変換
4. フォーマット済みスタックトレース文字列を生成

**関連システム・外部連携**：
- V8/Node.jsのError.prepareStackTrace API
- CoffeeScriptソースマップシステム
- registerモジュール（スタックトレースパッチの自動適用）

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 1 | トップページ | 補助機能 | patchStackTrace関数によるエラー時のスタックトレース行番号変換 |

## 機能種別

デバッグ支援 / エラー処理

## 入力仕様

### 入力パラメータ

patchStackTrace自体はパラメータを取らない。Error.prepareStackTraceのシグネチャ:

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| err | Error | Yes | エラーオブジェクト | Errorインスタンス |
| stack | CallSite[] | Yes | V8のCallSiteオブジェクト配列 | 配列 |

### 入力データソース

- V8エンジンからのスタックフレーム情報
- CoffeeScriptコンパイル時に生成・登録されたソースマップ

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| スタックトレース文字列 | string | CoffeeScript行番号に変換されたスタックトレース |

### 出力先

Error.stackプロパティ

## 処理フロー

### 処理シーケンス

```
1. patchStackTrace()呼び出し
   └─ Error.prepareStackTraceをカスタム関数で上書き

2. エラー発生時（Error.prepareStackTrace実行）
   └─ V8からerrorとstackを受け取る

3. 各スタックフレームの処理
   └─ frameごとにformatSourcePositionを呼び出し

4. ソースマップ検索
   └─ getSourceMapping(filename, line, column)でCoffeeScript位置を取得

5. フレーム文字列のフォーマット
   └─ ファイル名:行:列 + 関数名/メソッド名を整形

6. 結果の組み立て
   └─ "エラーメッセージ\n  at フレーム1\n  at フレーム2\n..."
```

### フローチャート

```mermaid
flowchart TD
    A[patchStackTrace呼び出し] --> B["Error.prepareStackTrace = カスタム関数"]
    B --> C[終了]

    D[エラー発生] --> E["Error.prepareStackTrace(err, stack)呼び出し"]
    E --> F["for frame in stack"]
    F --> G{frame === CoffeeScript.run?}
    G -->|Yes| H[break]
    G -->|No| I["formatSourcePosition(frame)"]
    I --> J{frame.isNative()?}
    J -->|Yes| K["fileLocation = 'native'"]
    J -->|No| L{frame.isEval()?}
    L -->|Yes| M[getEvalOrigin処理]
    L -->|No| N[getFileName()]
    M --> O["line = frame.getLineNumber()"]
    N --> O
    O --> P["getSourceMapping(filename, line, column)"]
    P --> Q{ソースマップあり?}
    Q -->|Yes| R["filename:sourceRow:sourceCol"]
    Q -->|No| S["filename:line:column"]
    R --> T[関数名/メソッド名整形]
    S --> T
    T --> U["'    at ' + formattedFrame"]
    U --> F
    H --> V["err.toString() + frames.join('\\n')"]
    V --> W[return スタックトレース文字列]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | CoffeeScript.run停止 | スタックトレースの表示はCoffeeScript.runより深いフレームで停止 | CoffeeScript.run実行時 |
| BR-02 | ソースマップキャッシュ | コンパイル時に生成されたソースマップはファイル名で参照可能 | registerCompiled呼び出し時 |
| BR-03 | 1-indexed変換 | ソースマップは0-indexedのため、表示時に1を加算 | 位置変換時 |
| BR-04 | Node.jsソースマップとの排他 | Node.jsの--enable-source-mapsが有効な場合はパッチを適用しない | register呼び出し時 |
| BR-05 | 既存パッチとの排他 | Error.prepareStackTraceが既に定義されている場合はパッチを適用しない | register呼び出し時 |

### 計算ロジック

ソースマップからの位置変換（coffeescript.coffee 307-311行目）:
```coffeescript
getSourceMapping = (filename, line, column) ->
  sourceMap = getSourceMap filename, line, column
  answer = sourceMap.sourceLocation [line - 1, column - 1] if sourceMap?
  if answer? then [answer[0] + 1, answer[1] + 1] else null
```

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

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

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | なし | patchStackTrace自体はエラーを発生させない | - |

ソースマップが見つからない場合は、変換なしでJavaScript行番号をそのまま表示する。

### リトライ仕様

該当なし

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

該当なし

## パフォーマンス要件

- スタックトレース変換はエラー発生時のみ実行
- ソースマップはメモリ上にキャッシュされているため、検索は高速
- 深いスタックトレースでは変換処理のオーバーヘッドが増加

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

- スタックトレースにはソースコードの情報（ファイル名、関数名、行番号）が含まれる
- 本番環境ではスタックトレースの露出に注意

## 備考

- patchStackTraceはsrc/coffeescript.coffeeに定義されている
- Node.js 12以降では`--enable-source-maps`フラグが利用可能で、この場合CoffeeScript独自のパッチは不要
- ソースマップはregisterCompiled関数で登録される

---

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

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

### 推奨読解順序

#### Step 1: patchStackTrace関数の全体構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | coffeescript.coffee | `src/coffeescript.coffee` | patchStackTrace関数（253-323行目） |

**主要処理フロー**:
- **256-305行目**: formatSourcePosition内部関数 - スタックフレームを文字列に整形
- **307-311行目**: getSourceMapping内部関数 - ソースマップで位置変換
- **317-323行目**: Error.prepareStackTraceの上書き

**読解のコツ**:
- formatSourcePositionはV8のCallSiteオブジェクトのメソッド（isNative, isEval, getFileName等）を使用
- getSourceMapはSourceMapモジュールからインポート（22行目）

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | coffeescript.coffee | `src/coffeescript.coffee` | formatSourcePosition（256-305行目） |

**主要処理フロー**:
- **260-261行目**: `frame.isNative()` で native関数の判定
- **263-267行目**: `frame.isEval()` でeval由来の判定
- **269行目**: `frame.getFileName()` でファイル名取得
- **271-272行目**: `frame.getLineNumber()`, `frame.getColumnNumber()` で位置取得
- **274-280行目**: ソースマップで位置変換
- **282-305行目**: 関数名/メソッド名のフォーマット

#### Step 3: getSourceMapping関数を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | coffeescript.coffee | `src/coffeescript.coffee` | getSourceMapping（307-311行目） |

**主要処理フロー**:
- **308行目**: `getSourceMap filename, line, column` でソースマップ取得
- **310行目**: `sourceMap.sourceLocation [line - 1, column - 1]` で0-indexed位置変換
- **311行目**: 結果を1-indexedに戻して返す

#### Step 4: Error.prepareStackTraceの上書きを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | coffeescript.coffee | `src/coffeescript.coffee` | Error.prepareStackTrace設定（317-323行目） |

**主要処理フロー**:
- **318行目**: `for frame in stack` でスタックフレームを走査
- **320行目**: `break if frame.getFunction() is exports.run` でCoffeeScript.runで停止
- **321行目**: `"    at #{formatSourcePosition frame, getSourceMapping}"` でフレーム整形
- **323行目**: `"#{err.toString()}\n#{frames.join '\n'}\n"` で最終結果

#### Step 5: register.coffeeでの自動適用を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | register.coffee | `src/register.coffee` | patchStackTrace呼び出し条件（9-16行目） |

**主要処理フロー**:
- **9-11行目**: `nodeSourceMapsSupportEnabled`の判定
- **14-16行目**: `unless Error.prepareStackTrace or nodeSourceMapsSupportEnabled` で条件付き適用

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

```
CoffeeScript.register() / require 'coffeescript/register'
    │
    └─ register.coffee
           │
           ├─ nodeSourceMapsSupportEnabled判定
           │
           └─ unless Error.prepareStackTrace or nodeSourceMapsSupportEnabled
                  │
                  └─ patchStackTrace() (coffeescript.coffee:253)
                         │
                         └─ Error.prepareStackTrace = (err, stack) ->
                                │
                                ├─ for frame in stack
                                │      │
                                │      └─ formatSourcePosition(frame, getSourceMapping)
                                │             │
                                │             ├─ frame.getFileName()
                                │             ├─ frame.getLineNumber()
                                │             ├─ frame.getColumnNumber()
                                │             │
                                │             └─ getSourceMapping(filename, line, column)
                                │                    │
                                │                    ├─ getSourceMap(filename)
                                │                    │
                                │                    └─ sourceMap.sourceLocation()
                                │
                                └─ "#{err.toString()}\n#{frames.join '\n'}\n"
```

### データフロー図

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

エラー発生 ──────────▶ Error.prepareStackTrace
                         │
V8 CallSite配列 ─────────┤
                         │
                         ├─▶ for frame in stack
                         │       │
                         │       ▼
                         │   frame.getFileName()
                         │   frame.getLineNumber()
                         │   frame.getColumnNumber()
                         │
ソースマップキャッシュ ──┼─▶ getSourceMap(filename)
                         │       │
                         │       ▼
                         │   sourceMap.sourceLocation()
                         │       │
                         │       ▼
                         │   CoffeeScript行番号
                         │
                         └─▶ formatSourcePosition()
                                 │
                                 ▼
                           "    at 関数名 (ファイル:行:列)"
                                 │
                                 ▼
                           error.stack ──▶ console.error/表示
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| coffeescript.coffee | `src/coffeescript.coffee` | ソース | patchStackTrace関数の実装 |
| sourcemap.litcoffee | `src/sourcemap.litcoffee` | ソース | ソースマップクラスとgetSourceMap関数 |
| register.coffee | `src/register.coffee` | ソース | patchStackTraceの自動適用 |
