@markuplint/mustache-parser 4.6.22 → 4.6.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,106 @@
1
+ # @markuplint/mustache-parser
2
+
3
+ ## 概要
4
+
5
+ `@markuplint/mustache-parser` は `HtmlParser` を拡張し、Mustache および Handlebars テンプレート式を含む HTML ファイルのリントを可能にします。完全なテンプレートパーサーを実装するのではなく、`HtmlParser` の `ignoreTags` メカニズムを設定して Mustache/Handlebars 構文を不透明なブロックとして扱います。これにより、markuplint はテンプレート式をスキップしながら周囲の HTML 構造を検証できます。
6
+
7
+ Handlebars は Mustache のスーパーセットであり、同じデリミタ構文を使用するため、このパッケージは Handlebars とも互換性があります。
8
+
9
+ ## 動作原理
10
+
11
+ パーサーは `HtmlParser` のコンストラクタで3つの `ignoreTags` エントリを宣言することで動作します。トークン化の際、基底 `Parser` クラスがソース内でこれらのデリミタペアを検索し、`#ps:*`(PreprocessorSpecific)ノードとして抽出し、残りの HTML を通常通りパースします。
12
+
13
+ `ignoreTags` エントリの順序は重要です。より具体的なパターンを、より一般的なパターンの前に配置する必要があります。例えば、`{{{`(トリプルスタッシュ)は `{{`(ダブルスタッシュ)の前にマッチする必要があり、`{{!`(コメント)も `{{` の前にマッチする必要があります。
14
+
15
+ ## ignoreTags 設定
16
+
17
+ | タイプ | 開始 | 終了 | 説明 |
18
+ | -------------------- | ----- | ----- | -------------------------------------------------------- |
19
+ | `mustache-comment` | `{{!` | `}}` | Mustache コメント(`{{! comment }}`) |
20
+ | `mustache-unescaped` | `{{{` | `}}}` | エスケープなし / トリプルスタッシュ出力(`{{{ raw }}}`) |
21
+ | `mustache-tag` | `{{` | `}}` | 標準的な補間とブロックヘルパー |
22
+
23
+ マッチした式は `#ps:mustache-tag`、`#ps:mustache-unescaped`、`#ps:mustache-comment` のような名前を持つ AST ノードになります。
24
+
25
+ ## サポートされない構文
26
+
27
+ **引用符なしの属性値**内のテンプレート式はサポートされていません。これはすべてのテンプレートエンジンパーサーに共通する既知の制限です([#240](https://github.com/markuplint/markuplint/issues/240))。[ウェブサイトのドキュメント](https://markuplint.dev/docs/guides/besides-html)も参照してください。
28
+
29
+ 使用可能:
30
+
31
+ ```html
32
+ <div attr="{{ value }}"></div>
33
+ <div attr="{{ value }}"></div>
34
+ <div attr="{{ value }}-{{ value2 }}-{{ value3 }}"></div>
35
+ ```
36
+
37
+ 使用不可(引用符なし):
38
+
39
+ ```html
40
+ <div attr="{{" value }}></div>
41
+ ```
42
+
43
+ ## ディレクトリ構成
44
+
45
+ ```
46
+ src/
47
+ ├── index.ts — parser シングルトンを再エクスポート
48
+ ├── parser.ts — HtmlParser を拡張する MustacheParser クラス
49
+ └── index.spec.ts — タグ認識とノードリスト構造のテスト
50
+ ```
51
+
52
+ ## 主要ソースファイル
53
+
54
+ ### `parser.ts`
55
+
56
+ 上記3つの `ignoreTags` エントリを持つ `MustacheParser`(`HtmlParser` を拡張)を定義します。シングルトンインスタンスが `parser` としてエクスポートされます。
57
+
58
+ ### `index.ts`
59
+
60
+ `parser.ts` から `parser` をパブリック API として再エクスポートします。
61
+
62
+ ### `index.spec.ts`
63
+
64
+ テストカバレッジ:
65
+
66
+ - テキストや HTML 要素に挟まれた単一・複数の `{{ }}` タグ
67
+ - ネストされた HTML を含むブロックヘルパー(`{{#user}}...{{/user}}`)
68
+ - ベアテキスト(HTML 要素でラップされていない場合)
69
+ - 各タグタイプの正しい `nodeName`(`#ps:mustache-tag`、`#ps:mustache-unescaped`、`#ps:mustache-comment`)
70
+
71
+ ## 統合ポイント
72
+
73
+ ```mermaid
74
+ flowchart TD
75
+ subgraph upstream ["上流"]
76
+ htmlParser["@markuplint/html-parser\n(HtmlParser 基底クラス)"]
77
+ parserUtils["@markuplint/parser-utils\n(抽象 Parser, nodeListToDebugMaps)"]
78
+ end
79
+
80
+ subgraph pkg ["@markuplint/mustache-parser"]
81
+ mustacheParser["MustacheParser\nextends HtmlParser"]
82
+ end
83
+
84
+ subgraph downstream ["下流"]
85
+ mlCore["@markuplint/ml-core\n(MLASTDocument -> MLDOM)"]
86
+ config["markuplint 設定\n(parser オプション)"]
87
+ end
88
+
89
+ htmlParser -->|"継承"| mustacheParser
90
+ parserUtils -->|"テストユーティリティ"| mustacheParser
91
+ mustacheParser -->|"MLASTDocument を生成"| mlCore
92
+ config -->|"パーサーを選択"| mustacheParser
93
+ ```
94
+
95
+ ### 上流
96
+
97
+ - **`@markuplint/html-parser`** -- `MustacheParser` が拡張する基底クラス `HtmlParser` を提供。HTML パースロジック(parse5 統合、ゴースト要素、名前空間解決)はすべて継承される。
98
+
99
+ ### 下流
100
+
101
+ - **`@markuplint/ml-core`** -- このパーサーが生成する `MLASTDocument` を消費
102
+ - **markuplint 設定** -- ユーザーが markuplint 設定の `parser` オプションでこのパーサーを選択
103
+
104
+ ## ドキュメントマップ
105
+
106
+ - [メンテナンスガイド](docs/maintenance.ja.md) -- コマンド、レシピ、トラブルシューティング
@@ -0,0 +1,106 @@
1
+ # @markuplint/mustache-parser
2
+
3
+ ## Overview
4
+
5
+ `@markuplint/mustache-parser` extends `HtmlParser` to lint HTML files containing Mustache and Handlebars template expressions. Instead of implementing a full template parser, it configures `HtmlParser`'s `ignoreTags` mechanism to treat Mustache/Handlebars syntax as opaque blocks. This lets markuplint validate the surrounding HTML structure while skipping over template expressions.
6
+
7
+ The package is also compatible with Handlebars, since Handlebars is a superset of Mustache and uses the same delimiter syntax.
8
+
9
+ ## How It Works
10
+
11
+ The parser works by declaring three `ignoreTags` entries in the `HtmlParser` constructor. During tokenization, the base `Parser` class scans the source for these delimiter pairs, extracts them as `#ps:*` (PreprocessorSpecific) nodes, and parses the remaining HTML normally.
12
+
13
+ The order of `ignoreTags` entries matters: more specific patterns must appear before less specific ones. For example, `{{{` (triple-stache) must be matched before `{{` (double-stache), and `{{!` (comment) must be matched before `{{` as well.
14
+
15
+ ## ignoreTags Configuration
16
+
17
+ | Type | Start | End | Description |
18
+ | -------------------- | ----- | ----- | ------------------------------------------------ |
19
+ | `mustache-comment` | `{{!` | `}}` | Mustache comments (`{{! comment }}`) |
20
+ | `mustache-unescaped` | `{{{` | `}}}` | Unescaped / triple-stache output (`{{{ raw }}}`) |
21
+ | `mustache-tag` | `{{` | `}}` | Standard interpolation and block helpers |
22
+
23
+ Matched expressions become AST nodes with names like `#ps:mustache-tag`, `#ps:mustache-unescaped`, and `#ps:mustache-comment`.
24
+
25
+ ## Unsupported Syntaxes
26
+
27
+ Template expressions inside **unquoted attribute values** are not supported. This is a known limitation shared by all template engine parsers ([#240](https://github.com/markuplint/markuplint/issues/240)). See also the [website documentation](https://markuplint.dev/docs/guides/besides-html).
28
+
29
+ Available:
30
+
31
+ ```html
32
+ <div attr="{{ value }}"></div>
33
+ <div attr="{{ value }}"></div>
34
+ <div attr="{{ value }}-{{ value2 }}-{{ value3 }}"></div>
35
+ ```
36
+
37
+ Unavailable (unquoted):
38
+
39
+ ```html
40
+ <div attr="{{" value }}></div>
41
+ ```
42
+
43
+ ## Directory Structure
44
+
45
+ ```
46
+ src/
47
+ ├── index.ts — Re-exports the parser singleton
48
+ ├── parser.ts — MustacheParser class extending HtmlParser
49
+ └── index.spec.ts — Tests for tag recognition and node list structure
50
+ ```
51
+
52
+ ## Key Source Files
53
+
54
+ ### `parser.ts`
55
+
56
+ Defines `MustacheParser` which extends `HtmlParser` with the three `ignoreTags` entries listed above. A singleton instance is exported as `parser`.
57
+
58
+ ### `index.ts`
59
+
60
+ Re-exports `parser` from `parser.ts` as the public API.
61
+
62
+ ### `index.spec.ts`
63
+
64
+ Tests cover:
65
+
66
+ - Single and multiple `{{ }}` tags interleaved with text and HTML elements
67
+ - Block helpers (`{{#user}}...{{/user}}`) with nested HTML
68
+ - Bare text (no wrapping HTML element)
69
+ - Correct `nodeName` for each tag type (`#ps:mustache-tag`, `#ps:mustache-unescaped`, `#ps:mustache-comment`)
70
+
71
+ ## Integration Points
72
+
73
+ ```mermaid
74
+ flowchart TD
75
+ subgraph upstream ["Upstream"]
76
+ htmlParser["@markuplint/html-parser\n(HtmlParser base class)"]
77
+ parserUtils["@markuplint/parser-utils\n(Abstract Parser, nodeListToDebugMaps)"]
78
+ end
79
+
80
+ subgraph pkg ["@markuplint/mustache-parser"]
81
+ mustacheParser["MustacheParser\nextends HtmlParser"]
82
+ end
83
+
84
+ subgraph downstream ["Downstream"]
85
+ mlCore["@markuplint/ml-core\n(MLASTDocument -> MLDOM)"]
86
+ config["markuplint config\n(parser option)"]
87
+ end
88
+
89
+ htmlParser -->|"extends"| mustacheParser
90
+ parserUtils -->|"test utility"| mustacheParser
91
+ mustacheParser -->|"produces MLASTDocument"| mlCore
92
+ config -->|"selects parser"| mustacheParser
93
+ ```
94
+
95
+ ### Upstream
96
+
97
+ - **`@markuplint/html-parser`** -- Provides `HtmlParser`, the base class that `MustacheParser` extends. All HTML parsing logic (parse5 integration, ghost elements, namespace resolution) is inherited.
98
+
99
+ ### Downstream
100
+
101
+ - **`@markuplint/ml-core`** -- Consumes the `MLASTDocument` produced by this parser
102
+ - **markuplint config** -- Users select this parser via the `parser` option in their markuplint configuration
103
+
104
+ ## Documentation Map
105
+
106
+ - [Maintenance Guide](docs/maintenance.md) -- Commands, recipes, and troubleshooting
package/CHANGELOG.md CHANGED
@@ -3,13 +3,13 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
- ## [4.6.22](https://github.com/markuplint/markuplint/compare/@markuplint/mustache-parser@4.6.21...@markuplint/mustache-parser@4.6.22) (2025-11-05)
6
+ ## [4.6.23](https://github.com/markuplint/markuplint/compare/@markuplint/mustache-parser@4.6.22...@markuplint/mustache-parser@4.6.23) (2026-02-10)
7
7
 
8
8
  **Note:** Version bump only for package @markuplint/mustache-parser
9
9
 
10
+ ## [4.6.22](https://github.com/markuplint/markuplint/compare/@markuplint/mustache-parser@4.6.21...@markuplint/mustache-parser@4.6.22) (2025-11-05)
10
11
 
11
-
12
-
12
+ **Note:** Version bump only for package @markuplint/mustache-parser
13
13
 
14
14
  ## [4.6.21](https://github.com/markuplint/markuplint/compare/@markuplint/mustache-parser@4.6.20...@markuplint/mustache-parser@4.6.21) (2025-08-24)
15
15
 
package/SKILL.md ADDED
@@ -0,0 +1,100 @@
1
+ ---
2
+ description: Perform maintenance tasks for @markuplint/mustache-parser
3
+ globs:
4
+ - packages/@markuplint/mustache-parser/src/**
5
+ alwaysApply: false
6
+ ---
7
+
8
+ # mustache-parser-maintenance
9
+
10
+ Perform maintenance tasks for `@markuplint/mustache-parser`: modify ignoreTags configuration,
11
+ add new Mustache/Handlebars tag types, and update parser behavior.
12
+
13
+ ## Input
14
+
15
+ `$ARGUMENTS` specifies the task. Supported tasks:
16
+
17
+ | Task | Description |
18
+ | ------------------ | -------------------------------------------- |
19
+ | `modify-ignoretag` | Modify or add an ignoreTags entry |
20
+ | `fix-parsing` | Fix a parsing issue with Mustache/Handlebars |
21
+ | `update-tests` | Add or update test cases |
22
+
23
+ If omitted, defaults to `modify-ignoretag`.
24
+
25
+ ## Reference
26
+
27
+ Before executing any task, read `docs/maintenance.md` (or `docs/maintenance.ja.md`)
28
+ for the full guide. The recipes there are the source of truth for procedures.
29
+
30
+ Also read:
31
+
32
+ - `ARCHITECTURE.md` -- Package overview, ignoreTags configuration, and integration points
33
+ - `src/parser.ts` -- MustacheParser class (source of truth for ignoreTags)
34
+
35
+ ## Task: modify-ignoretag
36
+
37
+ Modify or add an ignoreTags entry. Follow recipe #1 in `docs/maintenance.md`.
38
+
39
+ ### Step 1: Understand the current configuration
40
+
41
+ 1. Read `src/parser.ts` and review the existing `ignoreTags` array
42
+ 2. Understand the matching order: more specific patterns must come before less specific ones
43
+
44
+ ### Step 2: Make the change
45
+
46
+ 1. Add or modify the entry in the `ignoreTags` array in the `MustacheParser` constructor
47
+ 2. Ensure correct ordering (e.g., `{{{` before `{{`, `{{!` before `{{`)
48
+ 3. Each entry needs `type`, `start`, and `end` properties
49
+
50
+ ### Step 3: Verify
51
+
52
+ 1. Build: `yarn build --scope @markuplint/mustache-parser`
53
+ 2. Add test cases to `src/index.spec.ts` for the new tag type
54
+ 3. Test: `yarn test --scope @markuplint/mustache-parser`
55
+
56
+ ## Task: fix-parsing
57
+
58
+ Fix a parsing issue with Mustache/Handlebars templates. Follow recipe #2 in `docs/maintenance.md`.
59
+
60
+ ### Step 1: Reproduce the issue
61
+
62
+ 1. Create a minimal HTML template that demonstrates the parsing problem
63
+ 2. Write a failing test case in `src/index.spec.ts`
64
+
65
+ ### Step 2: Investigate
66
+
67
+ 1. Read `src/parser.ts` to check if the issue is in the ignoreTags configuration
68
+ 2. If the issue is in the base parser, check `@markuplint/html-parser` (HtmlParser)
69
+
70
+ ### Step 3: Fix and verify
71
+
72
+ 1. Apply the fix
73
+ 2. Build: `yarn build --scope @markuplint/mustache-parser`
74
+ 3. Test: `yarn test --scope @markuplint/mustache-parser`
75
+
76
+ ## Task: update-tests
77
+
78
+ Add or update test cases. Follow recipe #3 in `docs/maintenance.md`.
79
+
80
+ ### Step 1: Understand the testing pattern
81
+
82
+ 1. Read `src/index.spec.ts` to understand the existing test structure
83
+ 2. Tests use `nodeListToDebugMaps` from `@markuplint/parser-utils` for assertions
84
+
85
+ ### Step 2: Write the tests
86
+
87
+ 1. Use `parser.parse()` to parse the template
88
+ 2. Assert against `nodeListToDebugMaps(doc.nodeList)` for node structure
89
+ 3. Assert against `doc.nodeList[n]?.nodeName` for individual node names
90
+
91
+ ### Step 3: Verify
92
+
93
+ 1. Test: `yarn test --scope @markuplint/mustache-parser`
94
+
95
+ ## Rules
96
+
97
+ 1. **Maintain ignoreTags ordering** -- more specific patterns (`{{{`, `{{!`) must appear before the general `{{` pattern.
98
+ 2. **Keep the parser minimal** -- this package only configures `ignoreTags`; HTML parsing logic belongs in `@markuplint/html-parser`.
99
+ 3. **Add JSDoc comments** to all new public exports.
100
+ 4. **Test all tag types** when modifying the ignoreTags configuration.
@@ -0,0 +1,120 @@
1
+ # メンテナンスガイド
2
+
3
+ ## コマンド
4
+
5
+ | コマンド | 説明 |
6
+ | ------------------------------------------------ | ---------------------- |
7
+ | `yarn build --scope @markuplint/mustache-parser` | このパッケージをビルド |
8
+ | `yarn dev --scope @markuplint/mustache-parser` | ウォッチモードでビルド |
9
+ | `yarn clean --scope @markuplint/mustache-parser` | ビルド成果物を削除 |
10
+ | `yarn test --scope @markuplint/mustache-parser` | テストを実行 |
11
+
12
+ ## テスト
13
+
14
+ テストファイルは `*.spec.ts` の命名規則に従い、`src/` ディレクトリに配置されています:
15
+
16
+ | テストファイル | カバレッジ |
17
+ | --------------- | ---------------------------------------------------------- |
18
+ | `index.spec.ts` | タグ認識、ノードリスト構造、ブロックヘルパー、ベアテキスト |
19
+
20
+ 主なテストパターンでは `nodeListToDebugMaps` を使用したスナップショット形式のアサーションを行います:
21
+
22
+ ```ts
23
+ import { nodeListToDebugMaps } from '@markuplint/parser-utils';
24
+ import { parser } from './parser.js';
25
+
26
+ const doc = parser.parse('<div>{{ name }}</div>');
27
+ expect(nodeListToDebugMaps(doc.nodeList)).toStrictEqual([
28
+ '[1:1]>[1:6](0,5)div: <div>',
29
+ '[1:6]>[1:15](5,14)#ps:mustache-tag: {{␣name␣}}',
30
+ '[1:15]>[1:21](14,20)div: </div>',
31
+ ]);
32
+ ```
33
+
34
+ 個別のタグタイプのアサーション:
35
+
36
+ ```ts
37
+ expect(parse('{{ any }}').nodeList[0]?.nodeName).toBe('#ps:mustache-tag');
38
+ expect(parse('{{{ any }}}').nodeList[0]?.nodeName).toBe('#ps:mustache-unescaped');
39
+ expect(parse('{{! any }}').nodeList[0]?.nodeName).toBe('#ps:mustache-comment');
40
+ ```
41
+
42
+ ## レシピ
43
+
44
+ ### 1. ignoreTags エントリの追加・変更
45
+
46
+ 1. `src/parser.ts` を読み、`MustacheParser` コンストラクタの `ignoreTags` 配列を確認
47
+ 2. エントリを追加または変更し、正しい順序を維持:
48
+ - より具体的な開始デリミタは、より一般的なものの前に配置する必要がある
49
+ - 現在の順序: `{{!` -> `{{{` -> `{{`
50
+ 3. 各エントリには `type`(文字列識別子)、`start`(開始デリミタ)、`end`(終了デリミタ)が必要
51
+ 4. ビルド: `yarn build --scope @markuplint/mustache-parser`
52
+ 5. `src/index.spec.ts` にテストケースを追加し、新しいタグタイプが正しい `#ps:*` ノード名を生成することを検証
53
+ 6. テスト: `yarn test --scope @markuplint/mustache-parser`
54
+
55
+ ### 2. パース問題の修正
56
+
57
+ 1. 最小限の再現テンプレートを作成し、`src/index.spec.ts` に失敗するテストを記述
58
+ 2. 問題の所在を特定:
59
+ - **ignoreTags 設定**(このパッケージ)-- デリミタのマッチング、順序
60
+ - **基底 HTML パーサー**(`@markuplint/html-parser`)-- HTML 構造処理
61
+ 3. 適切なパッケージで修正を適用
62
+ 4. ビルドとテスト: `yarn build --scope @markuplint/mustache-parser && yarn test --scope @markuplint/mustache-parser`
63
+ 5. 修正が `@markuplint/html-parser` にある場合: `yarn test --scope @markuplint/html-parser` も実行
64
+
65
+ ### 3. テストケースの追加
66
+
67
+ 1. `src/index.spec.ts` を読み、既存のテスト構造を理解
68
+ 2. ノードリストテストには `nodeListToDebugMaps` パターンを使用:
69
+ ```ts
70
+ const doc = parse('テンプレート文字列');
71
+ expect(nodeListToDebugMaps(doc.nodeList)).toStrictEqual([...]);
72
+ ```
73
+ 3. タグタイプテストには `nodeName` でアサート:
74
+ ```ts
75
+ expect(parse('{{ expr }}').nodeList[0]?.nodeName).toBe('#ps:mustache-tag');
76
+ ```
77
+ 4. テスト: `yarn test --scope @markuplint/mustache-parser`
78
+
79
+ ## 上流の影響
80
+
81
+ このパッケージは `@markuplint/html-parser` に依存しています。`HtmlParser` の変更(特に `ignoreTags` 処理、`visitText`、`researchTags` メカニズム)はこのパーサーに影響を与える可能性があります。
82
+
83
+ `@markuplint/html-parser` が更新された場合は、以下を実行してください:
84
+
85
+ ```shell
86
+ yarn test --scope @markuplint/mustache-parser
87
+ ```
88
+
89
+ ## トラブルシューティング
90
+
91
+ ### Mustache タグが認識されない
92
+
93
+ **症状:** `{{ name }}` のような Mustache 式が `#ps:mustache-tag` ノードではなく生テキストとして表示される。
94
+
95
+ **原因:** `ignoreTags` エントリが欠落しているか、start/end デリミタが不正。
96
+
97
+ **解決策:**
98
+
99
+ 1. `src/parser.ts` を確認 -- `ignoreTags` 配列に `start: '{{'` と `end: '}}'` のエントリが含まれていることを検証
100
+ 2. エントリの順序を確認 -- より具体的なパターンが先に配置されている必要がある
101
+
102
+ ### トリプルスタッシュがダブルスタッシュとしてパースされる
103
+
104
+ **症状:** `{{{ raw }}}` が `#ps:mustache-unescaped` ではなく `#ps:mustache-tag` ノードを生成する。
105
+
106
+ **原因:** `mustache-unescaped` エントリ(`{{{` / `}}}`)が `mustache-tag` エントリ(`{{` / `}}`)の後に配置されているため、より一般的なパターンが先にマッチする。
107
+
108
+ **解決策:**
109
+
110
+ 1. `ignoreTags` 配列内で `mustache-unescaped` エントリを `mustache-tag` エントリの前に移動
111
+
112
+ ### コメントタグが通常のタグとしてパースされる
113
+
114
+ **症状:** `{{! comment }}` が `#ps:mustache-comment` ではなく `#ps:mustache-tag` ノードを生成する。
115
+
116
+ **原因:** `mustache-comment` エントリ(`{{!` / `}}`)が `mustache-tag` エントリ(`{{` / `}}`)の後に配置されている。
117
+
118
+ **解決策:**
119
+
120
+ 1. `ignoreTags` 配列内で `mustache-comment` エントリが `mustache-tag` エントリの前にリストされていることを確認
@@ -0,0 +1,120 @@
1
+ # Maintenance Guide
2
+
3
+ ## Commands
4
+
5
+ | Command | Description |
6
+ | ------------------------------------------------ | ---------------------- |
7
+ | `yarn build --scope @markuplint/mustache-parser` | Build this package |
8
+ | `yarn dev --scope @markuplint/mustache-parser` | Watch mode build |
9
+ | `yarn clean --scope @markuplint/mustache-parser` | Remove build artifacts |
10
+ | `yarn test --scope @markuplint/mustache-parser` | Run tests |
11
+
12
+ ## Testing
13
+
14
+ Test files follow the `*.spec.ts` naming convention and are located in the `src/` directory:
15
+
16
+ | Test File | Coverage |
17
+ | --------------- | -------------------------------------------------------------- |
18
+ | `index.spec.ts` | Tag recognition, node list structure, block helpers, bare text |
19
+
20
+ The primary testing pattern uses `nodeListToDebugMaps` for snapshot-style assertions:
21
+
22
+ ```ts
23
+ import { nodeListToDebugMaps } from '@markuplint/parser-utils';
24
+ import { parser } from './parser.js';
25
+
26
+ const doc = parser.parse('<div>{{ name }}</div>');
27
+ expect(nodeListToDebugMaps(doc.nodeList)).toStrictEqual([
28
+ '[1:1]>[1:6](0,5)div: <div>',
29
+ '[1:6]>[1:15](5,14)#ps:mustache-tag: {{␣name␣}}',
30
+ '[1:15]>[1:21](14,20)div: </div>',
31
+ ]);
32
+ ```
33
+
34
+ Individual tag type assertions:
35
+
36
+ ```ts
37
+ expect(parse('{{ any }}').nodeList[0]?.nodeName).toBe('#ps:mustache-tag');
38
+ expect(parse('{{{ any }}}').nodeList[0]?.nodeName).toBe('#ps:mustache-unescaped');
39
+ expect(parse('{{! any }}').nodeList[0]?.nodeName).toBe('#ps:mustache-comment');
40
+ ```
41
+
42
+ ## Recipes
43
+
44
+ ### 1. Adding or Modifying an ignoreTags Entry
45
+
46
+ 1. Read `src/parser.ts` and review the `ignoreTags` array in the `MustacheParser` constructor
47
+ 2. Add or modify the entry, preserving correct ordering:
48
+ - More specific start delimiters must appear before less specific ones
49
+ - Current order: `{{!` -> `{{{` -> `{{`
50
+ 3. Each entry requires: `type` (string identifier), `start` (opening delimiter), `end` (closing delimiter)
51
+ 4. Build: `yarn build --scope @markuplint/mustache-parser`
52
+ 5. Add test cases to `src/index.spec.ts` to verify the new tag type produces the correct `#ps:*` node name
53
+ 6. Test: `yarn test --scope @markuplint/mustache-parser`
54
+
55
+ ### 2. Fixing a Parsing Issue
56
+
57
+ 1. Create a minimal reproducing template and write a failing test in `src/index.spec.ts`
58
+ 2. Determine whether the issue is in:
59
+ - **ignoreTags configuration** (this package) -- delimiter matching, ordering
60
+ - **Base HTML parser** (`@markuplint/html-parser`) -- HTML structure handling
61
+ 3. Apply the fix in the appropriate package
62
+ 4. Build and test: `yarn build --scope @markuplint/mustache-parser && yarn test --scope @markuplint/mustache-parser`
63
+ 5. If the fix is in `@markuplint/html-parser`, also run: `yarn test --scope @markuplint/html-parser`
64
+
65
+ ### 3. Adding Test Cases
66
+
67
+ 1. Read `src/index.spec.ts` to understand the existing test structure
68
+ 2. For node list tests, use the `nodeListToDebugMaps` pattern:
69
+ ```ts
70
+ const doc = parse('template string here');
71
+ expect(nodeListToDebugMaps(doc.nodeList)).toStrictEqual([...]);
72
+ ```
73
+ 3. For tag type tests, assert against `nodeName`:
74
+ ```ts
75
+ expect(parse('{{ expr }}').nodeList[0]?.nodeName).toBe('#ps:mustache-tag');
76
+ ```
77
+ 4. Test: `yarn test --scope @markuplint/mustache-parser`
78
+
79
+ ## Upstream Impact
80
+
81
+ This package depends on `@markuplint/html-parser`. Changes in `HtmlParser` (especially `ignoreTags` processing, `visitText`, or the `researchTags` mechanism) may affect this parser.
82
+
83
+ When `@markuplint/html-parser` is updated, run:
84
+
85
+ ```shell
86
+ yarn test --scope @markuplint/mustache-parser
87
+ ```
88
+
89
+ ## Troubleshooting
90
+
91
+ ### Mustache tags are not recognized
92
+
93
+ **Symptom:** Mustache expressions like `{{ name }}` appear as raw text instead of `#ps:mustache-tag` nodes.
94
+
95
+ **Cause:** The `ignoreTags` entry is missing or the start/end delimiters are incorrect.
96
+
97
+ **Solution:**
98
+
99
+ 1. Check `src/parser.ts` -- verify the `ignoreTags` array includes an entry with `start: '{{'` and `end: '}}'`
100
+ 2. Verify the entry ordering -- more specific patterns must appear first
101
+
102
+ ### Triple-stache parsed as double-stache
103
+
104
+ **Symptom:** `{{{ raw }}}` produces a `#ps:mustache-tag` node instead of `#ps:mustache-unescaped`.
105
+
106
+ **Cause:** The `mustache-unescaped` entry (`{{{` / `}}}`) appears after the `mustache-tag` entry (`{{` / `}}`), so the less specific pattern matches first.
107
+
108
+ **Solution:**
109
+
110
+ 1. Move the `mustache-unescaped` entry before the `mustache-tag` entry in the `ignoreTags` array
111
+
112
+ ### Comment tags parsed as regular tags
113
+
114
+ **Symptom:** `{{! comment }}` produces a `#ps:mustache-tag` node instead of `#ps:mustache-comment`.
115
+
116
+ **Cause:** The `mustache-comment` entry (`{{!` / `}}`) appears after the `mustache-tag` entry (`{{` / `}}`).
117
+
118
+ **Solution:**
119
+
120
+ 1. Ensure the `mustache-comment` entry is listed before the `mustache-tag` entry in the `ignoreTags` array
package/lib/index.d.ts CHANGED
@@ -1 +1,7 @@
1
+ /**
2
+ * @module @markuplint/mustache-parser
3
+ * Markuplint parser plugin for Mustache and Handlebars templates. Extends the standard
4
+ * HTML parser to treat Mustache/Handlebars tags, unescaped expressions, and comments
5
+ * as opaque blocks, allowing markuplint to lint the surrounding HTML structure.
6
+ */
1
7
  export { parser } from './parser.js';
package/lib/index.js CHANGED
@@ -1 +1,7 @@
1
+ /**
2
+ * @module @markuplint/mustache-parser
3
+ * Markuplint parser plugin for Mustache and Handlebars templates. Extends the standard
4
+ * HTML parser to treat Mustache/Handlebars tags, unescaped expressions, and comments
5
+ * as opaque blocks, allowing markuplint to lint the surrounding HTML structure.
6
+ */
1
7
  export { parser } from './parser.js';
package/lib/parser.d.ts CHANGED
@@ -1,6 +1,17 @@
1
1
  import { HtmlParser } from '@markuplint/html-parser';
2
+ /**
3
+ * Parser for Mustache and Handlebars templates that extends the standard HTML parser.
4
+ *
5
+ * Configures the HTML parser to recognize Mustache/Handlebars tag variants as opaque blocks:
6
+ * - `{{! ... }}` (comments)
7
+ * - `{{{ ... }}}` (unescaped / triple-stache output)
8
+ * - `{{ ... }}` (standard interpolation and block helpers)
9
+ */
2
10
  declare class MustacheParser extends HtmlParser {
3
11
  constructor();
4
12
  }
13
+ /**
14
+ * Singleton Mustache/Handlebars parser instance for use by the markuplint engine.
15
+ */
5
16
  export declare const parser: MustacheParser;
6
17
  export {};
package/lib/parser.js CHANGED
@@ -1,4 +1,12 @@
1
1
  import { HtmlParser } from '@markuplint/html-parser';
2
+ /**
3
+ * Parser for Mustache and Handlebars templates that extends the standard HTML parser.
4
+ *
5
+ * Configures the HTML parser to recognize Mustache/Handlebars tag variants as opaque blocks:
6
+ * - `{{! ... }}` (comments)
7
+ * - `{{{ ... }}}` (unescaped / triple-stache output)
8
+ * - `{{ ... }}` (standard interpolation and block helpers)
9
+ */
2
10
  class MustacheParser extends HtmlParser {
3
11
  constructor() {
4
12
  super({
@@ -22,4 +30,7 @@ class MustacheParser extends HtmlParser {
22
30
  });
23
31
  }
24
32
  }
33
+ /**
34
+ * Singleton Mustache/Handlebars parser instance for use by the markuplint engine.
35
+ */
25
36
  export const parser = new MustacheParser();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markuplint/mustache-parser",
3
- "version": "4.6.22",
3
+ "version": "4.6.23",
4
4
  "description": "The mustache template parser for markuplint",
5
5
  "repository": "git@github.com:markuplint/markuplint.git",
6
6
  "author": "Yusuke Hirao <yusukehirao@me.com>",
@@ -21,10 +21,10 @@
21
21
  "clean": "tsc --build --clean tsconfig.build.json"
22
22
  },
23
23
  "dependencies": {
24
- "@markuplint/html-parser": "4.6.22"
24
+ "@markuplint/html-parser": "4.6.23"
25
25
  },
26
26
  "devDependencies": {
27
- "@markuplint/parser-utils": "4.8.10"
27
+ "@markuplint/parser-utils": "4.8.11"
28
28
  },
29
- "gitHead": "6213ea30269ef404f030e67bbcc7fc7443ec1060"
29
+ "gitHead": "193ee7c1262bbed95424e38efdf1a8e56ff049f4"
30
30
  }