# 通知設計書 5-missing_tag_error

## 概要

本ドキュメントは、CoffeeScriptコンパイラがペアになるべきタグ（括弧、引用符、ヒアドキュメント、正規表現など）の閉じ側が欠落している場合に発生する構文エラー「missing tag error」の通知仕様を定義する。

### 本通知の処理概要

CoffeeScriptコンパイラのレキサー（字句解析器）において、開始タグ（開き括弧、引用符開始、ヒアドキュメント開始など）に対応する終了タグが見つからない場合に、構文エラーとして通知を行う処理である。対象となるタグには、括弧類（()、[]、{}）、文字列区切り（'、"、'''、"""）、正規表現区切り（/、///）、JSX閉じ括弧（>）などが含まれる。

**業務上の目的・背景**：ペアで使用されるべき構文要素の不完全さは、プログラムの意図と異なる解釈を引き起こす。閉じタグの欠落はソースコードの構造的な問題を示しており、早期検出により開発者はバグを未然に防止できる。

**通知の送信タイミング**：以下の2つのケースで発生する：
1. tokenize()メソッドの終了時に@ends配列に未処理のペアが残っている場合（90行目）
2. matchWithInterpolations()メソッドで補間処理中に閉じ区切り文字が見つからない場合（944-945行目）

**通知の受信者**：CoffeeScriptソースコードをコンパイルしている開発者。

**通知内容の概要**：エラーメッセージとして「missing {tag}」という形式で、欠落しているタグを特定できるようにしている。

**期待されるアクション**：開発者は欠落している閉じタグを追加する。

## 通知種別

コンパイラ出力（標準エラー出力 / SyntaxError例外）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（コンパイル処理中に即座に発生） |
| 優先度 | 高（コンパイルを中断） |
| リトライ | 無（即時エラーとして処理） |

### 送信先決定ロジック

CoffeeScriptコンパイラを呼び出しているコンテキストに依存する。

## 通知テンプレート

### エラーメッセージ形式

| 項目 | 内容 |
|-----|------|
| 形式 | テキスト |

### 本文テンプレート

```
[filename]:[line]:[column]: error: missing {tag}
[source_code_line]
[marker]
```

### 添付ファイル

なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| tag | 欠落しているタグ（'、"、'''、"""、/、///、>、)、]、}、OUTDENT等） | @ends配列のtag、またはclosingDelimiter | Yes |
| filename | ソースファイル名 | コンパイルオプション | No |
| line | エラー発生行番号 | locationData.first_line | Yes |
| column | エラー発生列番号 | locationData.first_column | Yes |
| source_code_line | エラーが発生した行のソースコード | 入力ソースコード | Yes |
| marker | エラー位置を示すマーカー | 計算により生成 | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| コンパイル処理 | tokenize()終了時 | @ends配列に未処理のペアが残っている場合 | 全てのトークン処理後にペアチェック |
| コンパイル処理 | matchWithInterpolations()実行中 | 閉じ区切り文字が見つからない場合 | 文字列/正規表現の補間処理中 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| なし | 閉じタグ欠落時は必ずエラーを発生させる |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[tokenize開始] --> B[トークン処理ループ]
    B --> C{開始タグ検出?}
    C -->|Yes| D[@endsにプッシュ]
    D --> B
    C -->|No| E{終了タグ検出?}
    E -->|Yes| F[@endsからポップ]
    F --> B
    E -->|No| B
    B --> G[トークン処理完了]
    G --> H{@ends配列に残りあり?}
    H -->|Yes| I[error: missing tag]
    H -->|No| J[正常終了]
    I --> K[throwSyntaxError実行]
    K --> L[SyntaxError例外発生]
```

## データベース参照・更新仕様

### 参照テーブル一覧

該当なし

### 内部データ構造

| 構造名 | 用途 | 備考 |
|--------|------|------|
| @ends配列 | 開始タグのスタック | lexer.coffee 45行目で初期化 |
| INVERSES | 開始タグと終了タグのマッピング | rewriter.coffeeで定義 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| SyntaxError | 閉じタグが欠落している | 開発者が閉じタグを追加 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0（リトライなし） |
| リトライ間隔 | N/A |
| リトライ対象エラー | N/A |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし

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

- エラーメッセージにはソースコードの一部が含まれる

## 備考

- 文字列の場合、ネストした補間内でも閉じ引用符がチェックされる
- ヒアドキュメント（'''、"""）と通常の文字列（'、"）は別々に処理される
- 正規表現リテラル（/）とヒアレグクス（///）も同様に処理される

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | lexer.coffee | `src/lexer.coffee` | @ends配列の初期化（45行目） |
| 1-2 | lexer.coffee | `src/lexer.coffee` | @endsへのプッシュ（820行目） |

**読解のコツ**: @ends配列は開始タグをスタックとして管理し、対応する終了タグが見つかるとポップされる。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | lexer.coffee | `src/lexer.coffee` | tokenize()メソッド終了時のチェック（90行目） |

**主要処理フロー**:
1. **90行目**: @ends.pop()で残りのエンドタグを取得
2. **90行目**: 残りがあれば「missing {end.tag}」エラーを発生

#### Step 3: 補間処理内のチェックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | lexer.coffee | `src/lexer.coffee` | matchWithInterpolations()メソッド（883-947行目） |

**主要処理フロー**:
- **944-945行目**: 閉じ区切り文字が見つからない場合「missing {closingDelimiter}」エラーを発生

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

```
Lexer.tokenize()
    |
    +-- トークン処理ループ
    |       |
    |       +-- literalToken()
    |       |       |
    |       |       +-- @ends.push() (820行目)
    |       |       +-- pair() → @ends.pop() (821行目)
    |       |
    |       +-- stringToken() / regexToken()
    |               |
    |               +-- matchWithInterpolations()
    |                       |
    |                       +-- 閉じ区切り文字チェック (944-945行目)
    |                       |
    |                       +-- [欠落時] error()
    |
    +-- closeIndentation()
    |
    +-- @ends.pop() チェック (90行目)
            |
            +-- [残りあり] error()
                    |
                    +-- throwSyntaxError()
```

### データフロー図

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

ソースコード  -----> tokenize()         -----> トークンストリーム
    |                     |
    |                     v
    |               開始タグ検出
    |                     |
    |                     v
    |               @ends.push()
    |                     |
    |                     v
    |               終了タグ検出
    |                     |
    |                     v
    |               @ends.pop()
    |                     |
    |               [終了時 @ends に残りあり]
    |                     |
    |                     v
    +------------> error() + throwSyntaxError()
                          |
                          v
                   SyntaxError例外
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| lexer.coffee | `src/lexer.coffee` | ソース | @ends配列管理とmissingタグチェック |
| rewriter.coffee | `src/rewriter.coffee` | ソース | INVERSES定義（開始・終了タグのマッピング） |
| helpers.coffee | `src/helpers.coffee` | ソース | throwSyntaxError関数 |
| error_messages.coffee | `test/error_messages.coffee` | テスト | unclosed stringsテスト（309-393行目） |
