@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.
- package/ARCHITECTURE.ja.md +208 -0
- package/ARCHITECTURE.md +251 -0
- package/CHANGELOG.md +33 -2
- package/README.md +6 -0
- package/SKILL.md +126 -0
- package/docs/maintenance.ja.md +176 -0
- package/docs/maintenance.md +176 -0
- package/docs/parser-class.ja.md +655 -0
- package/docs/parser-class.md +655 -0
- package/lib/debug.js +8 -24
- package/lib/debugger.d.ts +25 -0
- package/lib/debugger.js +34 -4
- package/lib/enums.d.ts +10 -0
- package/lib/enums.js +10 -0
- package/lib/get-location.d.ts +31 -0
- package/lib/get-location.js +33 -0
- package/lib/get-namespace.d.ts +11 -0
- package/lib/get-namespace.js +38 -0
- package/lib/idl-attributes.d.ts +9 -0
- package/lib/idl-attributes.js +9 -0
- package/lib/ignore-block.js +15 -14
- package/lib/index.d.ts +2 -1
- package/lib/index.js +1 -1
- package/lib/parser-error.d.ts +16 -0
- package/lib/parser-error.js +20 -3
- package/lib/parser.d.ts +285 -7
- package/lib/parser.js +763 -551
- package/lib/script-parser.d.ts +21 -0
- package/lib/script-parser.js +17 -0
- package/lib/sort-nodes.d.ts +8 -0
- package/lib/sort-nodes.js +11 -3
- package/lib/types.d.ts +60 -3
- package/package.json +11 -10
|
@@ -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.
|