@markuplint/parser-utils 4.8.10 → 5.0.0-alpha.0

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,176 @@
1
+ # メンテナンスガイド
2
+
3
+ ## コマンド
4
+
5
+ | コマンド | 説明 |
6
+ | --------------------------------------------- | ---------------------- |
7
+ | `yarn build --scope @markuplint/parser-utils` | このパッケージをビルド |
8
+ | `yarn dev --scope @markuplint/parser-utils` | ウォッチモードでビルド |
9
+ | `yarn clean --scope @markuplint/parser-utils` | ビルド成果物を削除 |
10
+ | `yarn test --scope @markuplint/parser-utils` | テストを実行 |
11
+
12
+ ## テスト
13
+
14
+ テストファイルは `*.spec.ts` の命名規則に従い、ソースファイルと同じディレクトリに配置されています。主なテストパターンでは `nodeListToDebugMaps` を使用したスナップショット形式のアサーションを行います:
15
+
16
+ ```ts
17
+ import { nodeListToDebugMaps } from '@markuplint/parser-utils';
18
+
19
+ const doc = parser.parse('<div class="foo">text</div>');
20
+ const debugMaps = nodeListToDebugMaps(doc.nodeList, true);
21
+ expect(debugMaps).toStrictEqual([
22
+ // 期待されるデバッグ出力
23
+ ]);
24
+ ```
25
+
26
+ ### テスト用デバッグユーティリティ
27
+
28
+ - **`nodeListToDebugMaps(nodeList, withAttr?)`** — ASTノードを位置情報付きのデバッグ文字列に変換
29
+ - **`attributesToDebugMaps(attributes)`** — 属性の分解を表示(名前、等号、値、引用符)
30
+ - **`nodeTreeDebugView(nodeTree, idFilter?)`** — 深さ、親子リンク付きのツリー可視化
31
+
32
+ ## レシピ
33
+
34
+ ### 1. 新しいパーサーの作成
35
+
36
+ 1. `packages/@markuplint/` 配下に新しいパッケージを作成
37
+ 2. `Parser<YourNode, YourState>` を拡張:
38
+
39
+ ```ts
40
+ import { Parser } from '@markuplint/parser-utils';
41
+
42
+ class MyParser extends Parser<MyNode> {
43
+ constructor() {
44
+ super({
45
+ endTagType: 'xml', // または 'omittable', 'never'
46
+ tagNameCaseSensitive: true,
47
+ ignoreTags: [
48
+ // パース前にマスクするパターン
49
+ ],
50
+ });
51
+ }
52
+
53
+ tokenize() {
54
+ const ast = myLanguageParser(this.rawCode);
55
+ return { ast: ast.children, isFragment: true };
56
+ }
57
+
58
+ nodeize(originNode, parentNode, depth) {
59
+ // ビジターメソッドを使用して言語固有のノードを変換
60
+ }
61
+ }
62
+ ```
63
+
64
+ 3. `MLParserModule` としてエクスポート:
65
+
66
+ ```ts
67
+ import { MyParser } from './parser.js';
68
+ export default { parser: new MyParser() };
69
+ ```
70
+
71
+ 完全なオーバーライドパターンのリファレンスは [Parser Class Reference — Implementing a Parser](parser-class.md#implementing-a-parser) を参照してください。
72
+
73
+ ### 2. 新しいビジターメソッドの追加
74
+
75
+ ビジターメソッドは `nodeize()` から呼び出されます。新しいノードタイプのサポートを追加するには:
76
+
77
+ 1. パーサーのサブクラスに適切なASTノードを作成するメソッドを追加
78
+ 2. `nodeize()` の実装からそのメソッドを呼び出す
79
+ 3. `this.createToken()` でトークンを作成し、`this.sliceFragment()` でソースフラグメントを抽出
80
+
81
+ ### 3. IgnoreTag パターンの追加
82
+
83
+ パーサーのコンストラクタの `ignoreTags` 配列に追加:
84
+
85
+ ```ts
86
+ super({
87
+ ignoreTags: [
88
+ { type: 'mustache', start: '{{', end: '}}' },
89
+ { type: 'erb', start: '<%', end: '%>' },
90
+ { type: 'Style', start: '<style', end: '</style>' },
91
+ ],
92
+ });
93
+ ```
94
+
95
+ - `type` は復元された psblock ノード名の `#ps:` プレフィックスになる
96
+ - `start` と `end` は文字列または正規表現パターンが使用可能
97
+ - マスク文字は `maskChar` オプションでカスタマイズ可能
98
+
99
+ ### 4. 属性パースのカスタマイズ
100
+
101
+ カスタムオプションで `visitAttr()` をオーバーライド:
102
+
103
+ ```ts
104
+ visitAttr(token: Token) {
105
+ const attr = super.visitAttr(token, {
106
+ quoteSet: [
107
+ { start: '"', end: '"', type: 'string' },
108
+ { start: "'", end: "'", type: 'string' },
109
+ { start: '{', end: '}', type: 'script', parser: customParser },
110
+ ],
111
+ startState: AttrState.BeforeName,
112
+ });
113
+
114
+ // フレームワーク固有のディレクティブの後処理
115
+ if (attr.type === 'attr' && attr.name.raw.startsWith('v-')) {
116
+ this.updateAttr(attr, { isDirective: true });
117
+ }
118
+
119
+ return attr;
120
+ }
121
+ ```
122
+
123
+ ### 5. IDL属性マッピングの追加
124
+
125
+ IDL属性マップは `src/idl-attributes.ts` で定義されています。新しいマッピングを追加するには:
126
+
127
+ 1. `idlContentMap` オブジェクトにエントリを追加
128
+ 2. キーはIDLプロパティ名(camelCase)
129
+ 3. 値はコンテンツ属性名(lowercase)
130
+
131
+ ## 下流影響チェックリスト
132
+
133
+ このパッケージへの変更は、下流の6つのパーサーパッケージすべてに影響を与える可能性があります:
134
+
135
+ | パッケージ | 主な依存関係 |
136
+ | --------------------------- | --------------------------------------------------------------------- |
137
+ | `@markuplint/html-parser` | Parser 基底クラス、researchTags を使用した visitText |
138
+ | `@markuplint/jsx-parser` | Parser 基底クラス、quoteSet を使用した visitAttr、detectElementType |
139
+ | `@markuplint/vue-parser` | Parser 基底クラス、visitAttr、flattenNodes、detectElementType |
140
+ | `@markuplint/svelte-parser` | Parser 基底クラス、visitText、visitPsBlock、visitChildren、ignoreTags |
141
+ | `@markuplint/astro-parser` | Parser 基底クラス(html-parser 経由) |
142
+ | `@markuplint/pug-parser` | Parser 基底クラス |
143
+
144
+ Parser クラスを変更する際は、必ずすべてのパーサーパッケージでテストを実行してください:
145
+
146
+ ```shell
147
+ yarn test --scope @markuplint/html-parser --scope @markuplint/jsx-parser \
148
+ --scope @markuplint/vue-parser --scope @markuplint/svelte-parser \
149
+ --scope @markuplint/astro-parser --scope @markuplint/pug-parser
150
+ ```
151
+
152
+ ## トラブルシューティング
153
+
154
+ ### テンプレート式がパースエラーを引き起こす
155
+
156
+ **症状:** `<div class="{{ variable }}">` のようなコードでパースが失敗する。
157
+
158
+ **原因:** テンプレート式がHTMLパース前にマスクされていない。
159
+
160
+ **解決策:** パーサーのコンストラクタの `ignoreTags` に、式の構文に一致する `IgnoreTag` パターンを追加する。
161
+
162
+ ### 属性パースが失敗する
163
+
164
+ **症状:** `SyntaxError: Unclosed attribute value` または類似のエラーが発生する。
165
+
166
+ **原因:** 非標準の属性クォート(例: JSX式のブレース)が設定されていない。
167
+
168
+ **解決策:** `visitAttr()` をオーバーライドし、その言語の式デリミタを含むカスタム `quoteSet` を渡す。
169
+
170
+ ### フロントマターが検出されない
171
+
172
+ **症状:** YAMLフロントマターが psblock ではなくテキストノードとして表示される。
173
+
174
+ **原因:** パースオプションで `ignoreFrontMatter` が有効になっていない。
175
+
176
+ **解決策:** `parse()` を呼び出す際に `options.ignoreFrontMatter` が `true` であることを確認する。注意: Svelte はこれを明示的に無効にしています。
@@ -0,0 +1,176 @@
1
+ # Maintenance Guide
2
+
3
+ ## Commands
4
+
5
+ | Command | Description |
6
+ | --------------------------------------------- | ---------------------- |
7
+ | `yarn build --scope @markuplint/parser-utils` | Build this package |
8
+ | `yarn dev --scope @markuplint/parser-utils` | Watch mode build |
9
+ | `yarn clean --scope @markuplint/parser-utils` | Remove build artifacts |
10
+ | `yarn test --scope @markuplint/parser-utils` | Run tests |
11
+
12
+ ## Testing
13
+
14
+ Test files follow the `*.spec.ts` naming convention and are located alongside source files. The primary testing pattern uses `nodeListToDebugMaps` for snapshot-style assertions:
15
+
16
+ ```ts
17
+ import { nodeListToDebugMaps } from '@markuplint/parser-utils';
18
+
19
+ const doc = parser.parse('<div class="foo">text</div>');
20
+ const debugMaps = nodeListToDebugMaps(doc.nodeList, true);
21
+ expect(debugMaps).toStrictEqual([
22
+ // expected debug output
23
+ ]);
24
+ ```
25
+
26
+ ### Debug Utilities for Testing
27
+
28
+ - **`nodeListToDebugMaps(nodeList, withAttr?)`** — Converts AST nodes to position-annotated debug strings
29
+ - **`attributesToDebugMaps(attributes)`** — Shows attribute decomposition (name, equal, value, quotes)
30
+ - **`nodeTreeDebugView(nodeTree, idFilter?)`** — Tree visualization with depth, parent-child links
31
+
32
+ ## Recipes
33
+
34
+ ### 1. Creating a New Parser
35
+
36
+ 1. Create a new package under `packages/@markuplint/`
37
+ 2. Extend `Parser<YourNode, YourState>`:
38
+
39
+ ```ts
40
+ import { Parser } from '@markuplint/parser-utils';
41
+
42
+ class MyParser extends Parser<MyNode> {
43
+ constructor() {
44
+ super({
45
+ endTagType: 'xml', // or 'omittable', 'never'
46
+ tagNameCaseSensitive: true,
47
+ ignoreTags: [
48
+ // Patterns to mask before parsing
49
+ ],
50
+ });
51
+ }
52
+
53
+ tokenize() {
54
+ const ast = myLanguageParser(this.rawCode);
55
+ return { ast: ast.children, isFragment: true };
56
+ }
57
+
58
+ nodeize(originNode, parentNode, depth) {
59
+ // Convert language-specific nodes using visitor methods
60
+ }
61
+ }
62
+ ```
63
+
64
+ 3. Export as `MLParserModule`:
65
+
66
+ ```ts
67
+ import { MyParser } from './parser.js';
68
+ export default { parser: new MyParser() };
69
+ ```
70
+
71
+ See [Parser Class Reference — Implementing a Parser](parser-class.md#implementing-a-parser) for the full override pattern reference.
72
+
73
+ ### 2. Adding a New Visitor Method
74
+
75
+ Visitor methods are called from `nodeize()`. To add support for a new node type:
76
+
77
+ 1. Add a method to your parser subclass that creates the appropriate AST node
78
+ 2. Call the method from your `nodeize()` implementation
79
+ 3. Use `this.createToken()` to create tokens and `this.sliceFragment()` to extract source fragments
80
+
81
+ ### 3. Adding an IgnoreTag Pattern
82
+
83
+ Add to the `ignoreTags` array in your parser's constructor:
84
+
85
+ ```ts
86
+ super({
87
+ ignoreTags: [
88
+ { type: 'mustache', start: '{{', end: '}}' },
89
+ { type: 'erb', start: '<%', end: '%>' },
90
+ { type: 'Style', start: '<style', end: '</style>' },
91
+ ],
92
+ });
93
+ ```
94
+
95
+ - `type` becomes the `#ps:` prefix in the restored psblock node name
96
+ - `start` and `end` can be strings or RegExp patterns
97
+ - The mask character can be customized via `maskChar` option
98
+
99
+ ### 4. Customizing Attribute Parsing
100
+
101
+ Override `visitAttr()` with custom options:
102
+
103
+ ```ts
104
+ visitAttr(token: Token) {
105
+ const attr = super.visitAttr(token, {
106
+ quoteSet: [
107
+ { start: '"', end: '"', type: 'string' },
108
+ { start: "'", end: "'", type: 'string' },
109
+ { start: '{', end: '}', type: 'script', parser: customParser },
110
+ ],
111
+ startState: AttrState.BeforeName,
112
+ });
113
+
114
+ // Post-process for framework-specific directives
115
+ if (attr.type === 'attr' && attr.name.raw.startsWith('v-')) {
116
+ this.updateAttr(attr, { isDirective: true });
117
+ }
118
+
119
+ return attr;
120
+ }
121
+ ```
122
+
123
+ ### 5. Adding an IDL Attribute Mapping
124
+
125
+ The IDL attribute map is defined in `src/idl-attributes.ts`. To add a new mapping:
126
+
127
+ 1. Add the entry to the `idlContentMap` object
128
+ 2. The key is the IDL property name (camelCase)
129
+ 3. The value is the content attribute name (lowercase)
130
+
131
+ ## Downstream Impact Checklist
132
+
133
+ Changes to this package can affect all 6 downstream parser packages:
134
+
135
+ | Package | Key Dependencies |
136
+ | --------------------------- | --------------------------------------------------------------------- |
137
+ | `@markuplint/html-parser` | Parser base class, visitText with researchTags |
138
+ | `@markuplint/jsx-parser` | Parser base class, visitAttr with quoteSet, detectElementType |
139
+ | `@markuplint/vue-parser` | Parser base class, visitAttr, flattenNodes, detectElementType |
140
+ | `@markuplint/svelte-parser` | Parser base class, visitText, visitPsBlock, visitChildren, ignoreTags |
141
+ | `@markuplint/astro-parser` | Parser base class (via html-parser) |
142
+ | `@markuplint/pug-parser` | Parser base class |
143
+
144
+ Always run tests across all parser packages when modifying the Parser class:
145
+
146
+ ```shell
147
+ yarn test --scope @markuplint/html-parser --scope @markuplint/jsx-parser \
148
+ --scope @markuplint/vue-parser --scope @markuplint/svelte-parser \
149
+ --scope @markuplint/astro-parser --scope @markuplint/pug-parser
150
+ ```
151
+
152
+ ## Troubleshooting
153
+
154
+ ### Template expressions cause parse errors
155
+
156
+ **Symptom:** Parsing fails on code like `<div class="{{ variable }}">`.
157
+
158
+ **Cause:** Template expressions are not being masked before HTML parsing.
159
+
160
+ **Solution:** Add an `IgnoreTag` pattern to `ignoreTags` in the parser constructor that matches the expression syntax.
161
+
162
+ ### Attribute parsing fails
163
+
164
+ **Symptom:** `SyntaxError: Unclosed attribute value` or similar errors.
165
+
166
+ **Cause:** Non-standard attribute quoting (e.g., JSX expression braces) is not configured.
167
+
168
+ **Solution:** Override `visitAttr()` and pass a custom `quoteSet` that includes the language's expression delimiters.
169
+
170
+ ### Front matter not detected
171
+
172
+ **Symptom:** YAML front matter appears as text nodes instead of a psblock.
173
+
174
+ **Cause:** `ignoreFrontMatter` is not enabled in parse options.
175
+
176
+ **Solution:** Ensure `options.ignoreFrontMatter` is `true` when calling `parse()`. Note: Svelte explicitly disables this.