@markuplint/astro-parser 5.0.0-rc.2 → 5.0.0-rc.5

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.
@@ -1,172 +0,0 @@
1
- # メンテナンスガイド
2
-
3
- ## コマンド
4
-
5
- | コマンド | 説明 |
6
- | --------------------------------------------- | ---------------------- |
7
- | `yarn build --scope @markuplint/astro-parser` | このパッケージをビルド |
8
- | `yarn dev --scope @markuplint/astro-parser` | ウォッチモードでビルド |
9
- | `yarn clean --scope @markuplint/astro-parser` | ビルド成果物を削除 |
10
- | `yarn test --scope @markuplint/astro-parser` | テストを実行 |
11
-
12
- ## テスト
13
-
14
- テストファイルは `*.spec.ts` の命名規則に従い、`src/` ディレクトリに配置されています:
15
-
16
- | テストファイル | カバレッジ |
17
- | ---------------------- | -------------------------------------------------------------------------- |
18
- | `parser.spec.ts` | AstroParser 統合テスト(フロントマター、式、属性、名前空間、フラグメント) |
19
- | `astro-parser.spec.ts` | astro-eslint-parser ラッパーテスト(生の AST 出力、属性の種類、診断) |
20
-
21
- 主なテストパターンでは `nodeListToDebugMaps` を使用したスナップショット形式のアサーションを行います:
22
-
23
- ```ts
24
- import { nodeListToDebugMaps } from '@markuplint/parser-utils';
25
- import { parser } from '@markuplint/astro-parser';
26
-
27
- const doc = parser.parse('<div class:list={["a"]}>{name}</div>');
28
- const debugMaps = nodeListToDebugMaps(doc.nodeList, true);
29
- expect(debugMaps).toStrictEqual([
30
- // 期待されるデバッグ出力
31
- ]);
32
- ```
33
-
34
- `nodeListToDebugMaps` の第2引数 `true` は出力に属性の詳細を含め、ディレクティブや動的な値の処理をテストする際に不可欠です。
35
-
36
- ## レシピ
37
-
38
- ### 1. 新しいテンプレートディレクティブの追加
39
-
40
- 1. `src/parser.ts` を読む — `visitAttr()` メソッド、特に `switch (lowerCaseDirectiveName)` ブロック
41
- 2. ディレクティブプレフィックスに新しい `case` を追加:
42
- - ディレクティブが標準 HTML 属性にマッピングされる場合(`class:list` → `class` のように)、`potentialName` を HTML 属性名に設定
43
- - ディレクティブが Astro 固有の場合(`set:html` のように)、`isDirective = true` を設定
44
- 3. 例 — `style` にマッピングする仮の `style:inline` ディレクティブを追加:
45
- ```ts
46
- case 'style': {
47
- potentialName = lowerCaseDirectiveName;
48
- break;
49
- }
50
- ```
51
- 4. ビルド: `yarn build --scope @markuplint/astro-parser`
52
- 5. `src/parser.spec.ts` にテストケースを追加:
53
- ```ts
54
- test('style:inline directive', () => {
55
- const ast = parse('<div style:inline={styles}></div>');
56
- const map = nodeListToDebugMaps(ast.nodeList, true);
57
- // potentialName: style と isDynamicValue: true を検証
58
- });
59
- ```
60
- 6. テスト: `yarn test --scope @markuplint/astro-parser`
61
-
62
- ### 2. 名前空間スコーピングの変更
63
-
64
- 名前空間の解決は現在 `@markuplint/parser-utils` の基底 `Parser` クラスが処理しています。Astro パーサーは名前空間ロジックをオーバーライドしていません。
65
-
66
- 1. カスタム名前空間処理が必要な場合は、`src/parser.ts` の `AstroParser` で該当メソッドをオーバーライド
67
- 2. 新しい名前空間(例: MathML)の場合、要素名を検出して名前空間を切り替えるオーバーライドが必要
68
- 3. ビルドとテスト: `yarn build --scope @markuplint/astro-parser && yarn test --scope @markuplint/astro-parser`
69
- 4. `src/parser.spec.ts` に名前空間テストケースを追加:
70
- ```ts
71
- test('MathML namespace', () => {
72
- const doc = parse('<div><math><mi>x</mi></math></div>');
73
- expect(doc.nodeList[1].namespace).toBe('http://www.w3.org/1998/Math/MathML');
74
- });
75
- ```
76
-
77
- ### 3. 式の処理の更新
78
-
79
- 1. `src/parser.ts` を読む — `nodeize()` 内の `case 'expression'` ブロック
80
- 2. 式の分割ロジックは以下のように動作する:
81
- - 式に複数の子がある場合(`firstChild !== lastChild`):
82
- - 開始フラグメント: 式の開始から最初の子の終了まで
83
- - 終了フラグメント: 最後の子の開始から式の終了まで
84
- - 子は開始フラグメントの psblock 内で訪問される
85
- - 式に子が1つまたは子がない場合:
86
- - 式全体が1つの MustacheTag psblock として出力される
87
- 3. 変更時の注意:
88
- - `sliceFragment()` のオフセットが開始フラグメントと終了フラグメントの両方で正しいことを確認
89
- - 終了フラグメントは `isFragment: false` である必要がある
90
- - 開始フラグメントは `isFragment: true` で、子の訪問のために `originNode.children` を渡す必要がある
91
- 4. ビルドとテスト: `yarn build --scope @markuplint/astro-parser && yarn test --scope @markuplint/astro-parser`
92
- 5. 複雑な式でテスト:
93
- ```ts
94
- test('Nested expression with HTML', () => {
95
- const ast = parse('<ul>{list.map(item => <li>{item}</li>)}</ul>');
96
- const map = nodeListToDebugMaps(ast.nodeList);
97
- // 開始 MustacheTag、ネストされた要素、終了 MustacheTag を検証
98
- });
99
- ```
100
-
101
- ## 上流影響チェックリスト
102
-
103
- 上流パッケージの変更がこのパーサーに影響を与える可能性があります:
104
-
105
- | パッケージ | 影響 |
106
- | -------------------------- | ----------------------------------------------------------- |
107
- | `@markuplint/parser-utils` | 基底 `Parser` クラスの変更は全オーバーライドメソッドに影響 |
108
- | `@markuplint/ml-ast` | AST 型の変更は `nodeize()` の戻り値の型に影響 |
109
- | `astro-eslint-parser` | パーサー出力形式の変更は `tokenize()` と `nodeize()` に影響 |
110
-
111
- `astro-eslint-parser` を更新する場合:
112
-
113
- ```shell
114
- # ランタイム依存を更新
115
- yarn upgrade astro-eslint-parser --scope @markuplint/astro-parser
116
-
117
- # 型用の開発依存を更新
118
- yarn upgrade @astrojs/compiler --scope @markuplint/astro-parser --dev
119
-
120
- # 互換性を検証
121
- yarn build --scope @markuplint/astro-parser && yarn test --scope @markuplint/astro-parser
122
- ```
123
-
124
- ## トラブルシューティング
125
-
126
- ### フロントマターが認識されない
127
-
128
- **症状:** `---...---` ブロックが Frontmatter psblock としてパースされない、またはその内容が HTML AST に漏れる。
129
-
130
- **原因:** `astro-eslint-parser` が `type: 'frontmatter'` ノードを生成していないか、ノードの位置オフセットが不正。
131
-
132
- **解決策:**
133
-
134
- 1. `src/astro-parser.spec.ts` にテストを追加し、`astroParse()` からの生の AST 出力を検証
135
- 2. フロントマターノードの `position.start.offset` と `position.end.offset` が正しいことを確認
136
- 3. `nodeize()` の `case 'frontmatter'` ブランチに到達していることを検証
137
-
138
- ### 式の分割で不正なオフセットが生成される
139
-
140
- **症状:** MustacheTag psblock ノードの開始/終了位置が不正、または式内のネストされた HTML 要素の位置がずれている。
141
-
142
- **原因:** `case 'expression'` ブランチの `sliceFragment()` 呼び出しが Astro AST の子から間違ったオフセットを使用している。
143
-
144
- **解決策:**
145
-
146
- 1. `firstChild.position?.end?.offset` と `lastChild.position?.start.offset` を確認 — これらは Astro AST の位置と正確に一致する必要がある
147
- 2. `startExpressionEndOffset` が式の開始と最初の HTML 子の間にあることを検証
148
- 3. `nodeListToDebugMaps` を使用して実際の位置と期待される位置を比較
149
-
150
- ### 名前空間が正しく適用されない
151
-
152
- **症状:** `<svg>` 内の要素が XHTML 名前空間を持つ、または `<foreignObject>` 内の要素が SVG 名前空間を持つ。
153
-
154
- **原因:** 名前空間の解決は `@markuplint/parser-utils` の基底 `Parser` クラスが処理します。Astro パーサーは名前空間ロジックをオーバーライドしていません。
155
-
156
- **解決策:**
157
-
158
- 1. 問題が `@markuplint/parser-utils` の基底 `Parser` クラスにあるかどうかを確認
159
- 2. 特定のネストパターンのテストケースを `src/parser.spec.ts` に追加して期待される動作を確認
160
- 3. 問題が上流にある場合は、基底 `Parser` の名前空間処理を調査
161
-
162
- ### テンプレートディレクティブが検出されない
163
-
164
- **症状:** `set:html={content}` のような属性に `isDirective: true` が設定されない、または `class:list` に `potentialName: 'class'` が設定されない。
165
-
166
- **原因:** 正規表現 `/^([^:]+):([^:]+)$/` がマッチしなかった、またはスイッチケースが欠落。
167
-
168
- **解決策:**
169
-
170
- 1. 属性名の形式を確認 — 正規表現はコロンが1つだけで、両側に空でない部分が必要
171
- 2. `switch (lowerCaseDirectiveName)` を確認 — ディレクティブプレフィックスがケースにマッチする必要がある
172
- 3. 新しいディレクティブプレフィックスの場合は、新しいケースを追加(レシピ #1 参照)
@@ -1,172 +0,0 @@
1
- # Maintenance Guide
2
-
3
- ## Commands
4
-
5
- | Command | Description |
6
- | --------------------------------------------- | ---------------------- |
7
- | `yarn build --scope @markuplint/astro-parser` | Build this package |
8
- | `yarn dev --scope @markuplint/astro-parser` | Watch mode build |
9
- | `yarn clean --scope @markuplint/astro-parser` | Remove build artifacts |
10
- | `yarn test --scope @markuplint/astro-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
- | `parser.spec.ts` | AstroParser integration tests (frontmatter, expressions, attributes, namespaces, fragments) |
19
- | `astro-parser.spec.ts` | astro-eslint-parser wrapper tests (raw AST output, attribute kinds, diagnostics) |
20
-
21
- The primary testing pattern uses `nodeListToDebugMaps` for snapshot-style assertions:
22
-
23
- ```ts
24
- import { nodeListToDebugMaps } from '@markuplint/parser-utils';
25
- import { parser } from '@markuplint/astro-parser';
26
-
27
- const doc = parser.parse('<div class:list={["a"]}>{name}</div>');
28
- const debugMaps = nodeListToDebugMaps(doc.nodeList, true);
29
- expect(debugMaps).toStrictEqual([
30
- // expected debug output
31
- ]);
32
- ```
33
-
34
- The second argument `true` to `nodeListToDebugMaps` includes attribute details in the output, which is essential for testing directive and dynamic value handling.
35
-
36
- ## Recipes
37
-
38
- ### 1. Adding a New Template Directive
39
-
40
- 1. Read `src/parser.ts` — the `visitAttr()` method, specifically the `switch (lowerCaseDirectiveName)` block
41
- 2. Add a new `case` for the directive prefix:
42
- - If the directive maps to a standard HTML attribute (like `class:list` → `class`), set `potentialName` to the HTML attribute name
43
- - If the directive is Astro-specific (like `set:html`), set `isDirective = true`
44
- 3. Example — adding a hypothetical `style:inline` directive that maps to `style`:
45
- ```ts
46
- case 'style': {
47
- potentialName = lowerCaseDirectiveName;
48
- break;
49
- }
50
- ```
51
- 4. Build: `yarn build --scope @markuplint/astro-parser`
52
- 5. Add test cases to `src/parser.spec.ts`:
53
- ```ts
54
- test('style:inline directive', () => {
55
- const ast = parse('<div style:inline={styles}></div>');
56
- const map = nodeListToDebugMaps(ast.nodeList, true);
57
- // Verify potentialName: style and isDynamicValue: true
58
- });
59
- ```
60
- 6. Test: `yarn test --scope @markuplint/astro-parser`
61
-
62
- ### 2. Modifying Namespace Scoping
63
-
64
- Namespace resolution is currently handled by the base `Parser` class from `@markuplint/parser-utils`. The Astro parser does not override namespace logic.
65
-
66
- 1. If you need to add custom namespace handling, you would override the relevant method in `AstroParser` in `src/parser.ts`
67
- 2. For new namespaces (e.g., MathML), the override would need to detect the element name and switch the namespace
68
- 3. Build and test: `yarn build --scope @markuplint/astro-parser && yarn test --scope @markuplint/astro-parser`
69
- 4. Add namespace test cases to `src/parser.spec.ts`:
70
- ```ts
71
- test('MathML namespace', () => {
72
- const doc = parse('<div><math><mi>x</mi></math></div>');
73
- expect(doc.nodeList[1].namespace).toBe('http://www.w3.org/1998/Math/MathML');
74
- });
75
- ```
76
-
77
- ### 3. Updating Expression Handling
78
-
79
- 1. Read `src/parser.ts` — the `case 'expression'` block in `nodeize()`
80
- 2. The expression splitting logic works as follows:
81
- - If the expression has multiple children (`firstChild !== lastChild`):
82
- - Opening fragment: from expression start to first child's end
83
- - Closing fragment: from last child's start to expression end
84
- - Children are visited within the opening fragment's psblock
85
- - If the expression has a single child or no children:
86
- - The entire expression is emitted as one MustacheTag psblock
87
- 3. When modifying:
88
- - Ensure `sliceFragment()` offsets are correct for both opening and closing fragments
89
- - The closing fragment must have `isFragment: false`
90
- - The opening fragment must have `isFragment: true` and pass `originNode.children` for child visitation
91
- 4. Build and test: `yarn build --scope @markuplint/astro-parser && yarn test --scope @markuplint/astro-parser`
92
- 5. Test with complex expressions:
93
- ```ts
94
- test('Nested expression with HTML', () => {
95
- const ast = parse('<ul>{list.map(item => <li>{item}</li>)}</ul>');
96
- const map = nodeListToDebugMaps(ast.nodeList);
97
- // Verify opening MustacheTag, nested elements, and closing MustacheTag
98
- });
99
- ```
100
-
101
- ## Upstream Impact Checklist
102
-
103
- Changes to upstream packages can affect this parser:
104
-
105
- | Package | Impact |
106
- | -------------------------- | ---------------------------------------------------------------- |
107
- | `@markuplint/parser-utils` | Base `Parser` class changes affect all override methods |
108
- | `@markuplint/ml-ast` | AST type changes affect `nodeize()` return types |
109
- | `astro-eslint-parser` | Parser output format changes affect `tokenize()` and `nodeize()` |
110
-
111
- When updating `astro-eslint-parser`:
112
-
113
- ```shell
114
- # Update the runtime dependency
115
- yarn upgrade astro-eslint-parser --scope @markuplint/astro-parser
116
-
117
- # Update the dev dependency for types
118
- yarn upgrade @astrojs/compiler --scope @markuplint/astro-parser --dev
119
-
120
- # Verify compatibility
121
- yarn build --scope @markuplint/astro-parser && yarn test --scope @markuplint/astro-parser
122
- ```
123
-
124
- ## Troubleshooting
125
-
126
- ### Frontmatter is not recognized
127
-
128
- **Symptom:** The `---...---` block is not parsed as a Frontmatter psblock, or its content leaks into the HTML AST.
129
-
130
- **Cause:** `astro-eslint-parser` may not be producing a `type: 'frontmatter'` node, or the node's position offsets are incorrect.
131
-
132
- **Solution:**
133
-
134
- 1. Add a test in `src/astro-parser.spec.ts` to verify the raw AST output from `astroParse()`
135
- 2. Check that the frontmatter node has correct `position.start.offset` and `position.end.offset`
136
- 3. Verify the `case 'frontmatter'` branch in `nodeize()` is being reached
137
-
138
- ### Expression splitting produces wrong offsets
139
-
140
- **Symptom:** MustacheTag psblock nodes have incorrect start/end positions, or nested HTML elements inside expressions are misaligned.
141
-
142
- **Cause:** The `sliceFragment()` calls in the `case 'expression'` branch are using wrong offsets from the Astro AST children.
143
-
144
- **Solution:**
145
-
146
- 1. Check `firstChild.position?.end?.offset` and `lastChild.position?.start.offset` — these must match the Astro AST positions exactly
147
- 2. Verify that `startExpressionEndOffset` falls between the expression start and the first HTML child
148
- 3. Use `nodeListToDebugMaps` to compare actual vs expected positions
149
-
150
- ### Namespace is not applied correctly
151
-
152
- **Symptom:** Elements inside `<svg>` have XHTML namespace, or elements inside `<foreignObject>` have SVG namespace.
153
-
154
- **Cause:** Namespace resolution is handled by the base `Parser` class from `@markuplint/parser-utils`. The Astro parser does not override namespace logic.
155
-
156
- **Solution:**
157
-
158
- 1. Check whether the issue is in the base `Parser` class in `@markuplint/parser-utils`
159
- 2. Add a test case with the specific nesting pattern to `src/parser.spec.ts` to confirm expected behavior
160
- 3. If the issue is upstream, investigate the base `Parser` namespace handling
161
-
162
- ### Template directive not detected
163
-
164
- **Symptom:** An attribute like `set:html={content}` does not get `isDirective: true`, or `class:list` does not get `potentialName: 'class'`.
165
-
166
- **Cause:** The regex `/^([^:]+):([^:]+)$/` did not match, or the switch case is missing.
167
-
168
- **Solution:**
169
-
170
- 1. Verify the attribute name format — the regex requires exactly one colon with non-empty parts on both sides
171
- 2. Check the `switch (lowerCaseDirectiveName)` — the directive prefix must match a case
172
- 3. If it is a new directive prefix, add a new case (see Recipe #1)