# 機能設計書 12-Handlebars/Glimmer パース

## 概要

本ドキュメントは、PrettierにおけるHandlebars/Glimmerテンプレートファイルのパース機能について記述する。

### 本機能の処理概要

**業務上の目的・背景**：Handlebarsは、Ember.jsをはじめとするJavaScriptフレームワークで広く使用されるテンプレートエンジンである。GlimmerはEmber.jsの次世代レンダリングエンジンであり、Handlebars互換の構文を持つ。テンプレートファイルのフォーマットを統一することで、フロントエンド開発におけるコードの可読性向上、チーム間でのコーディングスタイルの一貫性維持を実現する。

**機能の利用シーン**：
- Ember.jsアプリケーションのテンプレートファイル（.hbs）の編集時
- Handlebarsテンプレートを使用するNode.jsアプリケーションの開発時
- Glimmerコンポーネントのテンプレート作成時
- Mustache互換テンプレートの整形時

**主要な処理内容**：
1. @glimmer/syntaxパーサーを使用してHandlebarsテンプレートをASTにパース
2. Front Matter（YAMLヘッダー）の検出と処理
3. パースエラーの位置情報抽出とユーザーフレンドリーなエラーメッセージ生成
4. バックスラッシュエスケープの補正処理
5. ASTノードへのoffset情報の付加

**関連システム・外部連携**：
- @glimmer/syntax パーサーライブラリを内部的に使用
- linguist-languages による言語定義の取得
- VS Codeとの互換性（vscodeLanguageIds: "handlebars"）
- Front Matter処理との連携

**権限による制御**：なし（全ユーザーが利用可能）

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 2 | Playground | API連携 | Ember/Handlebarsテンプレートをパース |
| 5 | About - Prettier概要 | 補助機能 | 対応言語としてHandlebars/Glimmerパースを説明 |

## 機能種別

パース処理（テンプレートからAST変換）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| text | string | Yes | パース対象のHandlebarsテンプレートテキスト | - |

### 入力データソース

- ファイルシステムからの.hbsファイル読み込み
- エディタからのテキスト入力（API経由）
- 標準入力からのテンプレートテキスト

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| ast | Template | パース結果のAST（Template型） |
| ast.body | Array | テンプレート本体のノード配列 |
| ast.body[].loc | SourceLocation | 各ノードの位置情報（start/end offsetを含む） |

### 出力先

- Prettier内部のフォーマット処理パイプライン
- デバッグAPI（__debug.parse）経由でのAST出力

## 処理フロー

### 処理シーケンス

```
1. parse関数がテンプレートテキストを受け取る
   └─ parseFrontMatterでYAMLヘッダーを検出・分離
2. @glimmer/syntaxのpreprocessでテンプレートをパース
   └─ glimmerPrettierParsePluginでカスタム処理を適用
3. glimmerPrettierParsePluginのAll visitorで全ノードを処理
   └─ setOffsetでoffset情報を付加
   └─ addBackslashでエスケープ文字を補正
4. パースエラー発生時のエラー処理
   └─ getErrorLocationでエラー位置を抽出
   └─ getErrorMessageでユーザーフレンドリーなメッセージを生成
5. Front Matterがある場合はASTに追加
   └─ FrontMatterノードをbody配列の先頭に挿入
6. 最終的なASTを返却
```

### フローチャート

```mermaid
flowchart TD
    A[開始: parse] --> B[parseFrontMatter]
    B --> C{Front Matter存在?}
    C -->|Yes| D[contentのみ抽出]
    C -->|No| E[元テキストを使用]
    D --> F[parseGlimmer実行]
    E --> F
    F --> G{パース成功?}
    G -->|Yes| H[AST取得]
    G -->|No| I[getErrorLocation]
    I --> J[getErrorMessage]
    J --> K[createError]
    K --> L[エラースロー]
    H --> M{Front Matter存在?}
    M -->|Yes| N[FrontMatterノードをbody先頭に挿入]
    M -->|No| O[AST返却]
    N --> O
    O --> P[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-012-01 | codemodモード | @glimmer/syntaxをcodemodモードでパース（空白保持） | 常時 |
| BR-012-02 | バックスラッシュ補正 | `\{{` 形式のエスケープ済みMustacheを正しく処理 | TextNodeの直後にMustacheStatementがある場合 |
| BR-012-03 | offset情報付加 | 全ノードにloc.start.offset、loc.end.offset情報を追加 | 常時 |
| BR-012-04 | Front Matter対応 | YAMLヘッダーをFrontMatterノードとして処理 | --- で囲まれたヘッダーが存在する場合 |
| BR-012-05 | エラーメッセージ整形 | Glimmerパーサーのエラーをユーザーフレンドリーな形式に変換 | パースエラー時 |

### 計算ロジック

特になし

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

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

データベース操作なし（純粋なテキスト処理）

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ParseError | Handlebars構文エラー | エラー位置と整形されたメッセージを表示 |
| - | UnclosedElement | タグが閉じられていない | "Unclosed element `element`" |
| - | SyntaxError | 不正な構文 | パーサーからのエラーメッセージを表示 |

### リトライ仕様

パースエラーはリトライ不可（入力テキストの修正が必要）

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

トランザクション管理なし（ステートレスな処理）

## パフォーマンス要件

- 通常のテンプレートファイル（〜50KB）: 20ms以下
- 大規模テンプレートファイル（〜500KB）: 200ms以下

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

- テンプレート内のMustache式はそのままパースされ、実行されない
- XSS等のセキュリティリスクなし（フォーマット処理のみ）

## 備考

- GlimmerパーサーはEmber.jsプロジェクトの一部として開発されている
- Mustache互換構文をサポートするが、完全な互換性は保証されない

---

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

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

### 推奨読解順序

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

GlimmerのAST構造を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | loc.js | `src/language-handlebars/loc.js` | locStart/locEnd関数、位置情報の取得方法 |
| 1-2 | visitor-keys.js | `src/language-handlebars/visitor-keys.js` | ASTノードの子ノードキー定義 |

**読解のコツ**: GlimmerのASTは@glimmer/syntaxの仕様に従う。Template、ElementNode、MustacheStatement等のノードタイプがある。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.js | `src/language-handlebars/index.js` | モジュールエクスポート構造 |
| 2-2 | parser-glimmer.js | `src/language-handlebars/parser-glimmer.js` | パーサー定義とエクスポート |

**主要処理フロー**:
1. **1-5行目**: printerとlanguagesのエクスポート
2. **7行目**: parsersのエクスポート（glimmerパーサー）

#### Step 3: パース処理の詳細を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | parser-glimmer.js | `src/language-handlebars/parser-glimmer.js` | メインのパースロジック |

**主要処理フロー**:
- **1-4行目**: 依存モジュールのインポート（@glimmer/syntax, createError, parseFrontMatter）
- **20-35行目**: addBackslash関数 - エスケープ文字の補正
- **37-52行目**: setOffset関数 - offset情報の付加
- **55-63行目**: glimmerPrettierParsePlugin - カスタムパースプラグイン
- **66-69行目**: glimmerParseOptions - パースオプション（mode: "codemod"）
- **71-111行目**: parse関数 - メインのパース処理
- **113-163行目**: getErrorMessage関数 - エラーメッセージの整形
- **165-181行目**: getErrorLocation関数 - エラー位置の抽出
- **183-188行目**: glimmerパーサーオブジェクトのエクスポート

#### Step 4: プリンター処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | printer-glimmer.js | `src/language-handlebars/printer-glimmer.js` | AST to Doc変換処理 |

**主要処理フロー**:
- **37-421行目**: print関数 - ノードタイプ別の出力処理
- **40-44行目**: Block/Program/Templateの処理
- **46-79行目**: ElementNodeの処理
- **81-98行目**: BlockStatementの処理
- **102-107行目**: MustacheStatementの処理
- **117-151行目**: AttrNodeの処理
- **162-369行目**: TextNodeの処理（複雑な空白処理含む）

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

```
prettier.format(text, {parser: "glimmer"})
    │
    ├─ loadBuiltinPlugins()
    │      └─ language-handlebars/index.js
    │             ├─ parsers.glimmer
    │             └─ printers.glimmer
    │
    ├─ core.parse()
    │      └─ parsers.glimmer.parse(text)
    │             ├─ parseFrontMatter(text)
    │             ├─ parseGlimmer(text, options)
    │             │      └─ glimmerPrettierParsePlugin
    │             │             ├─ setOffset(node)
    │             │             └─ addBackslash(node)
    │             └─ FrontMatter処理
    │
    └─ core.print()
           └─ printers.glimmer.print()
                  └─ print(path, options, print)
```

### データフロー図

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

Handlebars ───▶ parseFrontMatter() ───▶ {frontMatter, content}
テンプレート              │
                         │
                         ▼
              parseGlimmer(content)
                         │
                         ├─ glimmerPrettierParsePlugin
                         │      ├─ setOffset()
                         │      └─ addBackslash()
                         │
                         ▼
              FrontMatterノード追加 ───▶ Template AST
                         │
                         └────────────▶ print() ───▶ Doc
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.js | `src/language-handlebars/index.js` | ソース | モジュールエントリーポイント |
| parser-glimmer.js | `src/language-handlebars/parser-glimmer.js` | ソース | Glimmerパーサー定義 |
| printer-glimmer.js | `src/language-handlebars/printer-glimmer.js` | ソース | Glimmerプリンター定義 |
| languages.evaluate.js | `src/language-handlebars/languages.evaluate.js` | ソース | 言語定義 |
| loc.js | `src/language-handlebars/loc.js` | ソース | 位置情報ユーティリティ |
| utilities.js | `src/language-handlebars/utilities.js` | ソース | ユーティリティ関数 |
| visitor-keys.js | `src/language-handlebars/visitor-keys.js` | ソース | ASTトラバース用キー定義 |
| get-visitor-keys.js | `src/language-handlebars/get-visitor-keys.js` | ソース | ビジターキー取得関数 |
| embed.js | `src/language-handlebars/embed.js` | ソース | 埋め込み処理 |
| massage-ast/index.js | `src/language-handlebars/massage-ast/index.js` | ソース | ASTマッサージ処理 |
| glimmer.js | `src/plugins/glimmer.js` | ソース | プラグインエクスポート |
