@markuplint/parser-utils 4.8.9 → 4.8.11

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,655 @@
1
+ # Parser クラスリファレンス
2
+
3
+ `Parser<Node, State>` 抽象クラスは、すべての markuplint パーサーの基盤です。生のソースコードからフラットな `MLASTNodeTreeItem[]` に至るまでの完全なパースパイプラインを定義し、特定のマークアップ言語をサポートするためにサブクラスがオーバーライドする豊富なビジターメソッドとユーティリティメソッドを提供します。
4
+
5
+ ## デザインパターン
6
+
7
+ Parser は **Template Method** パターンを使用しています。`parse()` メソッドは 11 ステップのパイプラインを統制し、各段階で protected なフックメソッドを呼び出します。サブクラスは特定のフック(主に `tokenize` と `nodeize`)をオーバーライドすることで、共通のパイプラインロジックを継承しながら言語固有の振る舞いを注入します。
8
+
9
+ ```mermaid
10
+ classDiagram
11
+ class Parser~Node State~ {
12
+ <<abstract>>
13
+ +parse(rawCode, options) MLASTDocument
14
+ +tokenize(options) Tokenized
15
+ +nodeize(originNode, parentNode, depth) MLASTNodeTreeItem[]
16
+ +beforeParse(rawCode, options) string
17
+ +afterParse(nodeList, options) MLASTNodeTreeItem[]
18
+ +visitElement(token, childNodes, options) MLASTNodeTreeItem[]
19
+ +visitText(token, options) MLASTNodeTreeItem[]
20
+ +visitComment(token, options) MLASTNodeTreeItem[]
21
+ +visitAttr(token, options) MLASTAttr
22
+ }
23
+
24
+ class HtmlParser {
25
+ +tokenize() Tokenized
26
+ +nodeize() MLASTNodeTreeItem[]
27
+ +beforeParse() string
28
+ +afterParse() MLASTNodeTreeItem[]
29
+ }
30
+
31
+ class JSXParser {
32
+ +tokenize() Tokenized
33
+ +nodeize() MLASTNodeTreeItem[]
34
+ +afterTraverse() MLASTNodeTreeItem[]
35
+ +visitAttr() MLASTAttr
36
+ }
37
+
38
+ class VueParser {
39
+ +tokenize() Tokenized
40
+ +nodeize() MLASTNodeTreeItem[]
41
+ +flattenNodes() MLASTNodeTreeItem[]
42
+ +visitAttr() MLASTAttr
43
+ }
44
+
45
+ class SvelteParser {
46
+ +tokenize() Tokenized
47
+ +nodeize() MLASTNodeTreeItem[]
48
+ +visitText() MLASTNodeTreeItem[]
49
+ +visitPsBlock() MLASTNodeTreeItem[]
50
+ }
51
+
52
+ Parser <|-- HtmlParser
53
+ Parser <|-- JSXParser
54
+ Parser <|-- VueParser
55
+ Parser <|-- SvelteParser
56
+ ```
57
+
58
+ ## 型パラメータ
59
+
60
+ | パラメータ | 制約 | デフォルト | 説明 |
61
+ | ---------- | ----------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
62
+ | `Node` | `extends {}` | `{}` | トークナイザーが生成する言語固有の AST ノード型(例: parse5 の `Node`、Svelte の `SvelteNode`) |
63
+ | `State` | `extends unknown` | `null` | 単一の `parse()` 呼び出しにわたって保持されるオプションのパーサー状態型。開始時に `defaultState` からクローンされ、終了時にリセットされる |
64
+
65
+ ## コンストラクタ / ParserOptions
66
+
67
+ ```ts
68
+ constructor(options?: ParserOptions, defaultState?: State)
69
+ ```
70
+
71
+ コンストラクタは `ParserOptions` オブジェクトとオプションのデフォルト状態値を受け取ります。
72
+
73
+ | オプション | 型 | デフォルト | 説明 |
74
+ | ---------------------- | ---------------------- | ------------------------------- | ---------------------------------------------------------------------------------------- |
75
+ | `booleanish` | `boolean` | `false` | 省略された属性値を `true` として扱う(例: JSX `<Component aria-hidden />`) |
76
+ | `endTagType` | `EndTagType` | `'omittable'` | `'xml'`: 終了タグ必須またはセルフクローズ; `'omittable'`: 省略可; `'never'`: 不要 |
77
+ | `ignoreTags` | `readonly IgnoreTag[]` | `[]` | パース前にマスクするコードブロックのパターン(例: テンプレート式) |
78
+ | `maskChar` | `string` | `'\uE000'` (MASK_CHAR) | マスクされたコードブロックを置換するために使用する文字 |
79
+ | `tagNameCaseSensitive` | `boolean` | `false` | タグ名の比較で大文字小文字を区別するか(例: JSX、Svelte) |
80
+ | `selfCloseType` | `SelfCloseType` | `'html'` | `'html'`: void 要素のみセルフクローズ; `'xml'`: スラッシュで判定; `'html+xml'`: いずれか |
81
+ | `spaceChars` | `readonly string[]` | `['\t', '\n', '\f', '\r', ' ']` | タグのパース時に空白として扱う文字 |
82
+ | `rawTextElements` | `readonly string[]` | `['style', 'script']` | 子要素をトラバースしない要素(生テキストコンテンツ) |
83
+
84
+ ## パースパイプライン
85
+
86
+ `parse()` メソッドがパイプライン全体を駆動します。
87
+
88
+ ```mermaid
89
+ flowchart TD
90
+ A["1. beforeParse()"] --> B["2. ignoreFrontMatter()"]
91
+ B --> C["3. ignoreBlock()"]
92
+ C --> D["4. tokenize()"]
93
+ D --> E["5. traverse() → nodeize()"]
94
+ E --> F["6. afterTraverse()"]
95
+ F --> G["7. flattenNodes()"]
96
+ G --> H["8. afterFlattenNodes()"]
97
+ H --> I["9. restoreNode()"]
98
+ I --> J["10. afterParse()"]
99
+ J --> K["11. Return MLASTDocument"]
100
+
101
+ style D fill:#e1f5fe
102
+ style E fill:#e1f5fe
103
+ ```
104
+
105
+ 青色でハイライトされたステップが主要なオーバーライドポイントです。
106
+
107
+ ### ステップ 1: beforeParse()
108
+
109
+ ```ts
110
+ beforeParse(rawCode: string, options?: ParseOptions): string
111
+ ```
112
+
113
+ `ParseOptions`(`offsetOffset`、`offsetLine`、`offsetColumn`)に基づいてオフセットスペースを先頭に追加します。これは埋め込みコードフラグメント(例: `.vue` ファイル内の `<template>` ブロック)の座標系を調整します。
114
+
115
+ ### ステップ 2: フロントマターの除去
116
+
117
+ `options.ignoreFrontMatter` が true の場合、`ignoreFrontMatter()` は YAML フロントマター(`---\n...\n---\n`)を検出し、改行を保持しながらスペースに置換します。フロントマターはパイプラインの最後に `#ps:front-matter` psblock ノードとして復元されます。
118
+
119
+ ### ステップ 3: 無視ブロックのマスキング
120
+
121
+ `ignoreBlock()` はソースを走査して `ignoreTags` で定義されたパターンを探し、一致するブロックを `<!...>` ボーガスコメント構文で囲まれたマスク文字に置換します。これにより、テンプレート式(例: `{{ expr }}`、`{#if}`)が HTML パースに干渉することを防ぎます。
122
+
123
+ ### ステップ 4: tokenize()
124
+
125
+ ```ts
126
+ tokenize(options?: ParseOptions): Tokenized<Node, State>
127
+ ```
128
+
129
+ **主要なオーバーライドポイント。** デフォルト実装は空の配列を返します。各パーサーはこれをオーバーライドして、言語固有のトークナイザー(parse5、vue-eslint-parser、svelte/compiler など)を呼び出し、結果の AST を返します。
130
+
131
+ ### ステップ 5: traverse() → nodeize()
132
+
133
+ ```ts
134
+ traverse(originNodes: readonly Node[], parentNode: MLASTParentNode | null, depth: number)
135
+ nodeize(originNode: Node, parentNode: MLASTParentNode | null, depth: number): readonly MLASTNodeTreeItem[]
136
+ ```
137
+
138
+ `traverse()` はトークン化されたノードを反復処理し、各ノードに対して `nodeize()` を呼び出します。**`nodeize()` は 2 番目の主要なオーバーライドポイント**です。サブクラスはビジターメソッドを使用して、言語固有の AST ノードを markuplint AST ノードに変換します。
139
+
140
+ `nodeize()` の後、`afterNodeize()` は結果のノードを現在の深さの兄弟ノードとより浅い深さの祖先ノードに分離します。
141
+
142
+ ### ステップ 6: afterTraverse()
143
+
144
+ ```ts
145
+ afterTraverse(nodeTree: readonly MLASTNodeTreeItem[]): readonly MLASTNodeTreeItem[]
146
+ ```
147
+
148
+ ノードツリーをソース位置でソートします。サブクラスはトラバース後の再構築のためにオーバーライドできます(例: JSX は式コンテナの parentId 参照を再マッピングします)。
149
+
150
+ ### ステップ 7: flattenNodes()
151
+
152
+ ```ts
153
+ flattenNodes(nodeTree: readonly MLASTNodeTreeItem[]): readonly MLASTNodeTreeItem[]
154
+ ```
155
+
156
+ 階層的なノードツリーを深さ優先で走査し、フラットでソートされたリストを生成します。重複ノードを除去します。
157
+
158
+ ### ステップ 8: afterFlattenNodes()
159
+
160
+ ```ts
161
+ afterFlattenNodes(
162
+ nodeList: readonly MLASTNodeTreeItem[],
163
+ options?: {
164
+ readonly exposeInvalidNode?: boolean; // default: true
165
+ readonly exposeWhiteSpace?: boolean; // default: true
166
+ readonly concatText?: boolean; // default: true
167
+ }
168
+ ): readonly MLASTNodeTreeItem[]
169
+ ```
170
+
171
+ 4 つのクリーンアップパスを実行します。
172
+
173
+ 1. **残留ノードの露出** — 既知のノード間の空白と無効なマークアップを発見する
174
+ 2. **孤立終了タグ → ボーガス** — 対応する開始タグがない終了タグを `invalid` ノードに変換する
175
+ 3. **テキストの結合** — 同じオフセットにある隣接する `#text` ノードをマージする
176
+ 4. **テキストのトリム** — 重複するテキストノードの境界をトリムする
177
+
178
+ ### ステップ 9: restoreNode()
179
+
180
+ `restoreNode()` はフラットなノードリストを走査し、マスク文字を元のコードに置換します。復元された各ブロックは `#ps:<type>` psblock ノードになります。属性値内のマスクされたコンテンツも復元され、`isDynamicValue` としてマークされます。
181
+
182
+ ### ステップ 10: afterParse()
183
+
184
+ ```ts
185
+ afterParse(nodeList: readonly MLASTNodeTreeItem[], options?: ParseOptions): readonly MLASTNodeTreeItem[]
186
+ ```
187
+
188
+ ステップ 1 で先頭に追加されたオフセットスペースを除去します。サブクラスはさらなる後処理を追加できます。
189
+
190
+ ### ステップ 11: 戻り値
191
+
192
+ `{ raw, nodeList, isFragment }` を含む `MLASTDocument` を返します。
193
+
194
+ ## ビジターメソッド
195
+
196
+ ### visitElement()
197
+
198
+ ```ts
199
+ visitElement(
200
+ token: ChildToken & { nodeName: string; namespace: string },
201
+ childNodes?: readonly Node[],
202
+ options?: {
203
+ createEndTagToken?: (startTag: MLASTElement) => ChildToken | null;
204
+ namelessFragment?: boolean;
205
+ overwriteProps?: Partial<MLASTElement>;
206
+ }
207
+ ): readonly MLASTNodeTreeItem[]
208
+ ```
209
+
210
+ 要素の開始タグノードを作成します。以下を処理します。
211
+
212
+ - **ゴースト要素** — `token.raw === ''` の場合、`isGhost: true` の要素を作成する(HTML における暗黙の `<head>` や `<body>` のような省略されたタグに使用)
213
+ - **セルフクローズの検出** — `selfCloseType` 設定と void 要素ステータスに基づく
214
+ - **終了タグのペアリング** — `createEndTagToken` がトークンを返す場合、終了タグを作成してペアリングする
215
+ - **名前なしフラグメント** — 空のタグ名を持つ JSX `<>...</>` フラグメント
216
+
217
+ ### visitText()
218
+
219
+ ```ts
220
+ visitText(
221
+ token: ChildToken,
222
+ options?: {
223
+ researchTags?: boolean;
224
+ invalidTagAsText?: boolean;
225
+ }
226
+ ): readonly MLASTNodeTreeItem[]
227
+ ```
228
+
229
+ テキストノードを作成します。`researchTags` が true の場合、`parseCodeFragment()` を通じてテキストを再パースし、埋め込まれた HTML タグを発見します。`invalidTagAsText` も true の場合、発見された開始タグによりコンテンツ全体が単一のテキストノードとして扱われます。
230
+
231
+ ### visitComment()
232
+
233
+ ```ts
234
+ visitComment(
235
+ token: ChildToken,
236
+ options?: { isBogus?: boolean }
237
+ ): readonly MLASTNodeTreeItem[]
238
+ ```
239
+
240
+ コメントノードを作成します。ボーガスコメント(`<!--` で始まらないもの)を自動的に検出します。`isBogus` オプションでこの検出をオーバーライドできます。
241
+
242
+ ### visitDoctype()
243
+
244
+ ```ts
245
+ visitDoctype(
246
+ token: ChildToken & { name: string; publicId: string; systemId: string }
247
+ ): readonly MLASTNodeTreeItem[]
248
+ ```
249
+
250
+ doctype 名、パブリック ID、システム ID を含むトークンから doctype ノードを作成します。
251
+
252
+ ### visitPsBlock()
253
+
254
+ ```ts
255
+ visitPsBlock(
256
+ token: ChildToken & { nodeName: string; isFragment: boolean },
257
+ childNodes?: readonly Node[],
258
+ conditionalType?: MLASTPreprocessorSpecificBlockConditionalType,
259
+ originBlockNode?: Node
260
+ ): readonly MLASTNodeTreeItem[]
261
+ ```
262
+
263
+ プリプロセッサ固有のブロックノードを作成します。`nodeName` には自動的に `#ps:` プレフィックスが付加されます(例: `#ps:if`、`#ps:each`、`#ps:front-matter`)。`visitChildren()` を通じて子ノードを再帰的にトラバースします。
264
+
265
+ ### visitAttr()
266
+
267
+ ```ts
268
+ visitAttr(
269
+ token: Token,
270
+ options?: {
271
+ quoteSet?: readonly QuoteSet[];
272
+ noQuoteValueType?: ValueType;
273
+ endOfUnquotedValueChars?: readonly string[];
274
+ startState?: AttrState;
275
+ }
276
+ ): MLASTAttr & { __rightText?: string }
277
+ ```
278
+
279
+ 生の属性文字列を、スペース、名前、等号、引用符、値の個別トークンに完全に分解された `MLASTAttr` にパースします。内部的に `attrTokenizer()` を通じて `AttrState` ステートマシンを使用します。
280
+
281
+ 生の文字列に複数の属性が含まれている場合、最初の属性のみがパースされ、残りは反復処理のために `__rightText` に返されます。
282
+
283
+ また、`visitSpreadAttr()` を通じてスプレッド属性の検出も試みます。
284
+
285
+ ### visitSpreadAttr()
286
+
287
+ ```ts
288
+ visitSpreadAttr(token: Token): MLASTSpreadAttr | null
289
+ ```
290
+
291
+ `{...expr}` パターンに一致する JSX スプレッド属性を検出します。トークンが一致しない場合は null を返します。HTML パーサーはこれをオーバーライドして常に null を返します。
292
+
293
+ ### visitChildren()
294
+
295
+ ```ts
296
+ visitChildren(
297
+ children: readonly Node[],
298
+ parentNode: MLASTParentNode | null
299
+ ): readonly MLASTNodeTreeItem[]
300
+ ```
301
+
302
+ 親の下の子ノードをトラバースします。`rawTextElements`(例: `<script>`、`<style>`)のトラバースはスキップします。祖先の深さレベルに属する兄弟ノードを返します。
303
+
304
+ ## ステートマシン
305
+
306
+ ### TagState
307
+
308
+ `#parseTag()` でのタグパース中に使用されます。
309
+
310
+ ```mermaid
311
+ stateDiagram-v2
312
+ [*] --> BeforeOpenTag
313
+ BeforeOpenTag --> FirstCharOfTagName : "<"
314
+ FirstCharOfTagName --> TagName : /[a-z]/i
315
+ FirstCharOfTagName --> FirstCharOfTagName : "/" (close tag)
316
+ FirstCharOfTagName --> AfterOpenTag : ">" (nameless)
317
+ TagName --> Attrs : whitespace
318
+ TagName --> AfterAttrs : "/"
319
+ TagName --> AfterOpenTag : ">"
320
+ Attrs --> AfterAttrs : "/" or ">"
321
+ AfterAttrs --> AfterOpenTag : ">"
322
+ AfterOpenTag --> [*]
323
+ ```
324
+
325
+ ### AttrState
326
+
327
+ `attrTokenizer()` での属性パース中に使用されます。
328
+
329
+ ```mermaid
330
+ stateDiagram-v2
331
+ [*] --> BeforeName
332
+ BeforeName --> Name : non-space, non-">"
333
+ BeforeName --> AfterValue : ">" or "/"
334
+ Name --> Equal : whitespace
335
+ Name --> BeforeValue : "="
336
+ Name --> AfterValue : ">" or "/"
337
+ Equal --> BeforeValue : "="
338
+ Equal --> AfterValue : other
339
+ BeforeValue --> Value : quote or char
340
+ Value --> AfterValue : end quote or unquoted end
341
+ AfterValue --> [*]
342
+ ```
343
+
344
+ ## トークン作成ユーティリティ
345
+
346
+ ### createToken()
347
+
348
+ ```ts
349
+ createToken(token: Token): MLASTToken;
350
+ createToken(token: string, startOffset: number, startLine: number, startCol: number): MLASTToken;
351
+ ```
352
+
353
+ 生成された UUID(8 文字)と計算された終了位置を持つ新しい `MLASTToken` を作成します。`Token` オブジェクトまたは明示的な座標を持つ生の文字列を受け取ります。
354
+
355
+ ### sliceFragment()
356
+
357
+ ```ts
358
+ sliceFragment(start: number, end?: number): Token
359
+ ```
360
+
361
+ 指定されたバイトオフセット範囲で現在の `rawCode` から `Token` を抽出し、ソース位置から行と列を計算します。
362
+
363
+ ### getOffsetsFromCode()
364
+
365
+ ```ts
366
+ getOffsetsFromCode(
367
+ startLine: number, startCol: number,
368
+ endLine: number, endCol: number
369
+ ): { offset: number; endOffset: number }
370
+ ```
371
+
372
+ 行/列の位置を現在の生のソースコード内のバイトオフセットに変換します。
373
+
374
+ ## ツリー操作
375
+
376
+ ### appendChild()
377
+
378
+ ```ts
379
+ appendChild(parentNode: MLASTParentNode | null, ...childNodes: readonly MLASTChildNode[]): void
380
+ ```
381
+
382
+ 子ノードを親に追加し、ソース位置によるソート順を維持します。子が既に存在する場合(UUID による)、その場で置換されます。
383
+
384
+ ### replaceChild()
385
+
386
+ ```ts
387
+ replaceChild(
388
+ parentNode: MLASTParentNode,
389
+ oldChildNode: MLASTChildNode,
390
+ ...replacementChildNodes: readonly MLASTChildNode[]
391
+ ): void
392
+ ```
393
+
394
+ 親の子リスト内の子ノードを 1 つ以上の置換ノードで置き換えます。
395
+
396
+ ### walk()
397
+
398
+ ```ts
399
+ walk<Node extends MLASTNodeTreeItem>(
400
+ nodeList: readonly Node[],
401
+ walker: Walker<Node>,
402
+ depth?: number
403
+ ): void
404
+ ```
405
+
406
+ ノードリストを深さ優先で走査し、各ノードに対してウォーカーコールバックを呼び出します。ウォーカーは現在のノード、順序的に前のノード、および深さを受け取ります。子ノードへの再帰は自動的に行われます。
407
+
408
+ ## 更新メソッド
409
+
410
+ ### updateLocation()
411
+
412
+ ```ts
413
+ updateLocation(
414
+ node: MLASTNodeTreeItem,
415
+ props: Partial<Pick<MLASTNodeTreeItem, 'startOffset' | 'startLine' | 'startCol' | 'depth'>>
416
+ ): void
417
+ ```
418
+
419
+ AST ノードの位置と深さのプロパティを更新し、新しい開始値から終了オフセット/行/列を再計算します。
420
+
421
+ ### updateRaw()
422
+
423
+ ```ts
424
+ updateRaw(node: MLASTToken, raw: string): void
425
+ ```
426
+
427
+ ノードの生のコードを置換し、すべての位置プロパティを適切に更新します。
428
+
429
+ ### updateElement()
430
+
431
+ ```ts
432
+ updateElement(el: MLASTElement, props: Partial<Pick<MLASTElement, 'nodeName' | 'elementType'>>): void
433
+ updateElement(el: MLASTElementCloseTag, props: Partial<Pick<MLASTElementCloseTag, 'nodeName'>>): void
434
+ ```
435
+
436
+ 要素または閉じタグノードのノード名および/または要素タイプを更新します。
437
+
438
+ ### updateAttr()
439
+
440
+ ```ts
441
+ updateAttr(
442
+ attr: MLASTHTMLAttr,
443
+ props: Partial<Pick<MLASTHTMLAttr,
444
+ 'isDynamicValue' | 'isDirective' | 'potentialName' | 'potentialValue' |
445
+ 'valueType' | 'candidate' | 'isDuplicatable'
446
+ >>
447
+ ): void
448
+ ```
449
+
450
+ 属性ノードのメタデータプロパティを更新します。ディレクティブや動的値としてマークするなどの用途に使用します。
451
+
452
+ ## 無視ブロックシステム
453
+
454
+ 無視ブロックシステムは、HTML パースの前にテンプレート式やプリプロセッサディレクティブをマスクし、パース後に復元します。
455
+
456
+ ### ライフサイクル
457
+
458
+ 1. **定義** — `ParserOptions.ignoreTags` で `IgnoreTag` パターンを定義:
459
+
460
+ ```ts
461
+ { type: 'mustache', start: '{{', end: '}}' }
462
+ { type: 'Style', start: '<style', end: '</style>' }
463
+ ```
464
+
465
+ 2. **マスク** — `ignoreBlock()` が一致箇所をボーガスコメント構文(`<!...>`)内のマスク文字に置換し、位置追跡のために改行を保持する
466
+
467
+ 3. **パース** — マスクされたコードは HTML トークン化に対して安全
468
+
469
+ 4. **復元** — `restoreNode()` がフラットなノードリストを走査し、マスクされた領域を `#ps:<type>` psblock ノードに置換する。属性値内のマスクされたコンテンツは復元され `isDynamicValue: true` としてマークされる
470
+
471
+ ### IgnoreTag の定義
472
+
473
+ ```ts
474
+ type IgnoreTag = {
475
+ readonly type: string; // #ps: プレフィックスに使用される名前
476
+ readonly start: RegExp | string; // 開始パターン
477
+ readonly end: RegExp | string; // 終了パターン
478
+ };
479
+ ```
480
+
481
+ ## 要素タイプの検出
482
+
483
+ ```ts
484
+ detectElementType(nodeName: string, defaultPattern?: ParserAuthoredElementNameDistinguishing): ElementType
485
+ ```
486
+
487
+ 要素を 3 つのタイプに分類します。
488
+
489
+ | タイプ | 説明 | 例 |
490
+ | ----------------- | ------------------------------------------------------- | --------------------------- |
491
+ | `'html'` | 標準 HTML 要素 | `div`, `span`, `input` |
492
+ | `'web-component'` | カスタム要素(仕様に従いハイフンを含む) | `my-component`, `x-button` |
493
+ | `'authored'` | フレームワークコンポーネント(authored パターンに一致) | `MyComponent`, `App.Header` |
494
+
495
+ `authoredElementName` パターンは `ParseOptions` から設定され、文字列、RegExp、関数、またはこれらの配列を指定できます。各パーサーはフレームワーク固有のデフォルトパターンを提供します(例: JSX/Svelte では `/^[A-Z]/`、Vue では PascalCase + ビルトインリスト)。
496
+
497
+ ## アクセサプロパティ
498
+
499
+ | プロパティ | 型 | 説明 |
500
+ | ---------------------- | ------------------------------------------------------ | --------------------------------------------------------------- |
501
+ | `rawCode` | `string` | パース中の現在の生のソースコード(前処理済みの場合あり) |
502
+ | `booleanish` | `boolean` | 省略された属性値を `true` として扱うかどうか |
503
+ | `endTag` | `EndTagType` | 終了タグの処理戦略 |
504
+ | `tagNameCaseSensitive` | `boolean` | タグ名の比較で大文字小文字を区別するかどうか |
505
+ | `authoredElementName` | `ParserAuthoredElementNameDistinguishing \| undefined` | authored 要素を区別するためのパターン |
506
+ | `state` | `State` | ミュータブルなパーサー状態(各 `parse()` 呼び出し後にリセット) |
507
+
508
+ ## パーサーの実装
509
+
510
+ ### 基本構造
511
+
512
+ ```ts
513
+ import { Parser } from '@markuplint/parser-utils';
514
+ import type { ParserOptions, ParseOptions, Tokenized, ChildToken } from '@markuplint/parser-utils';
515
+ import type { MLASTParentNode, MLASTNodeTreeItem } from '@markuplint/ml-ast';
516
+
517
+ // 言語固有の AST ノード型
518
+ type MyNode = {
519
+ /* ... */
520
+ };
521
+
522
+ class MyParser extends Parser<MyNode> {
523
+ constructor() {
524
+ super({
525
+ endTagType: 'xml',
526
+ tagNameCaseSensitive: true,
527
+ // ... その他のオプション
528
+ });
529
+ }
530
+
531
+ tokenize(options?: ParseOptions): Tokenized<MyNode> {
532
+ // this.rawCode を言語のパーサーでパースする
533
+ const ast = myLanguageParser(this.rawCode);
534
+ return { ast: ast.children, isFragment: true };
535
+ }
536
+
537
+ nodeize(originNode: MyNode, parentNode: MLASTParentNode | null, depth: number): readonly MLASTNodeTreeItem[] {
538
+ // ビジターメソッドを使用して各言語固有ノードを
539
+ // markuplint AST ノードに変換する
540
+ switch (originNode.type) {
541
+ case 'element':
542
+ return this.visitElement(/* ... */);
543
+ case 'text':
544
+ return this.visitText(/* ... */);
545
+ case 'comment':
546
+ return this.visitComment(/* ... */);
547
+ default:
548
+ return [];
549
+ }
550
+ }
551
+ }
552
+ ```
553
+
554
+ ### オーバーライドパターンリファレンス
555
+
556
+ | メソッド | super 呼び出し | パターン | 理由 |
557
+ | --------------------- | -------------- | ------------------------- | -------------------------------------------------------------------------- |
558
+ | `tokenize()` | **不要** | 完全置換 | デフォルトは空の配列を返す。各パーサーが独自のトークナイザーを提供 |
559
+ | `nodeize()` | **不要** | 完全置換 | デフォルトは空の配列を返す。各パーサーが独自のノード変換を提供 |
560
+ | `beforeParse()` | **必須** | super 先行 | `super.beforeParse()` がオフセットスペースの追加を処理。その後に処理を追加 |
561
+ | `afterParse()` | **必須** | super 先行 | `super.afterParse()` がオフセットスペースの除去を処理。その後に処理を追加 |
562
+ | `afterTraverse()` | 推奨 | super 先行 | `super` が位置でソート。JSX はその後に parentId の再マッピングを追加 |
563
+ | `afterFlattenNodes()` | 推奨 | ラッパー | `super` にオプションを渡してクリーンアップステップを制御 |
564
+ | `flattenNodes()` | 推奨 | super 先行 | Vue は super を呼び出した後にテンプレートコメントを注入 |
565
+ | `visitText()` | 推奨 | ラッパー | `super` にオプションを渡す。Svelte は script→psblock の後処理を行う |
566
+ | `visitComment()` | 推奨 | super 先行 | JSX は super の後に `isBogus` を `false` にオーバーライド |
567
+ | `visitPsBlock()` | 推奨 | ラッパー + バリデーション | Svelte は super の後に返却数を検証 |
568
+ | `visitChildren()` | 推奨 | ラッパー + バリデーション | Svelte は super の後に兄弟がないことを検証 |
569
+ | `visitAttr()` | **必須** | super 先行 | `super.visitAttr()` がトークン分解を実行。その後にディレクティブ処理を追加 |
570
+ | `visitSpreadAttr()` | 不要 | 完全置換 | HTML はスプレッド非対応のため `null` を返すようオーバーライド |
571
+ | `detectElementType()` | **必須** | ラッパー | フレームワーク固有のデフォルトパターンを `super` に渡す |
572
+ | `parseError()` | 推奨 | 条件付きチェーン | フレームワーク固有のエラーを先に処理し、`super` にフォールバック |
573
+ | `parse()` | 推奨 | ラッパー | Svelte はオプションを変更してから super に委譲 |
574
+
575
+ ### パターン 1: 完全置換 (tokenize, nodeize)
576
+
577
+ `super` の呼び出しは不要です。ベース実装は空の配列を返します。
578
+
579
+ ```ts
580
+ // HtmlParser より
581
+ tokenize(): Tokenized<Node, State> {
582
+ const doc = parse5.parse(this.rawCode);
583
+ return {
584
+ ast: doc.childNodes,
585
+ isFragment: false,
586
+ };
587
+ }
588
+ ```
589
+
590
+ ### パターン 2: super 先行 + 後処理 (beforeParse, afterParse, visitAttr)
591
+
592
+ 先に `super` を呼び出し、その後に処理を追加します。
593
+
594
+ ```ts
595
+ // HtmlParser より
596
+ beforeParse(rawCode: string, options?: ParseOptions) {
597
+ const code = super.beforeParse(rawCode, options);
598
+ // 追加の前処理...
599
+ return code;
600
+ }
601
+
602
+ // VueParser より
603
+ visitAttr(token: Token) {
604
+ const attr = super.visitAttr(token);
605
+ // Vue ディレクティブの省略記法を解決
606
+ if (attr.type === 'attr' && attr.name.raw.startsWith(':')) {
607
+ this.updateAttr(attr, {
608
+ potentialName: `v-bind:${attr.name.raw.slice(1)}`,
609
+ isDirective: true,
610
+ isDynamicValue: true,
611
+ });
612
+ }
613
+ return attr;
614
+ }
615
+ ```
616
+
617
+ ### パターン 3: ラッパー + オプション委譲 (afterFlattenNodes, visitText)
618
+
619
+ `super` に制御オプションを渡します。
620
+
621
+ ```ts
622
+ // JSXParser より
623
+ afterFlattenNodes(nodeList: readonly MLASTNodeTreeItem[]) {
624
+ return super.afterFlattenNodes(nodeList, {
625
+ exposeWhiteSpace: false,
626
+ exposeInvalidNode: false,
627
+ });
628
+ }
629
+
630
+ // HtmlParser より
631
+ visitText(token: ChildToken) {
632
+ return super.visitText(token, {
633
+ researchTags: true,
634
+ invalidTagAsText: true,
635
+ });
636
+ }
637
+ ```
638
+
639
+ ### パターン 4: 条件付きチェーン (parseError)
640
+
641
+ 既知のエラー形式を先に処理し、不明なエラーは `super` に委譲します。
642
+
643
+ ```ts
644
+ // JSXParser より
645
+ parseError(error: any) {
646
+ if (error.lineNumber != null && error.column != null) {
647
+ return new ParserError(error.message, {
648
+ line: error.lineNumber,
649
+ col: error.column,
650
+ raw: this.rawCode,
651
+ });
652
+ }
653
+ return super.parseError(error);
654
+ }
655
+ ```