@markuplint/astro-parser 4.6.22 → 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 +229 -0
- package/ARCHITECTURE.md +229 -0
- package/CHANGELOG.md +26 -2
- package/SKILL.md +109 -0
- package/docs/maintenance.ja.md +183 -0
- package/docs/maintenance.md +183 -0
- package/lib/astro-parser.d.ts +7 -0
- package/lib/astro-parser.js +7 -0
- package/lib/detect-block-behavior.d.ts +10 -0
- package/lib/detect-block-behavior.js +20 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.js +7 -0
- package/lib/parser.d.ts +47 -12
- package/lib/parser.js +72 -47
- package/package.json +9 -6
|
@@ -0,0 +1,183 @@
|
|
|
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
|
+
1. `src/parser.ts` を読む — `#updateScopeNS()` プライベートメソッド
|
|
65
|
+
2. このメソッドには2つの条件がある:
|
|
66
|
+
- XHTML → SVG: 現在の名前空間が XHTML で、ノードが `<svg>` 要素の場合
|
|
67
|
+
- SVG → XHTML: 現在の名前空間が SVG で、親が `<foreignObject>` の場合
|
|
68
|
+
3. 新しい名前空間遷移を追加する場合(例: MathML):
|
|
69
|
+
```ts
|
|
70
|
+
if (
|
|
71
|
+
parentNS === 'http://www.w3.org/1999/xhtml' &&
|
|
72
|
+
originNode.type === 'element' &&
|
|
73
|
+
originNode.name?.toLowerCase() === 'math'
|
|
74
|
+
) {
|
|
75
|
+
this.state.scopeNS = 'http://www.w3.org/1998/Math/MathML';
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
4. ビルドとテスト: `yarn build --scope @markuplint/astro-parser && yarn test --scope @markuplint/astro-parser`
|
|
79
|
+
5. `src/parser.spec.ts` に名前空間テストケースを追加:
|
|
80
|
+
```ts
|
|
81
|
+
test('MathML namespace', () => {
|
|
82
|
+
const doc = parse('<div><math><mi>x</mi></math></div>');
|
|
83
|
+
expect(doc.nodeList[1].namespace).toBe('http://www.w3.org/1998/Math/MathML');
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 3. 式の処理の更新
|
|
88
|
+
|
|
89
|
+
1. `src/parser.ts` を読む — `nodeize()` 内の `case 'expression'` ブロック
|
|
90
|
+
2. 式の分割ロジックは以下のように動作する:
|
|
91
|
+
- 式に複数の子がある場合(`firstChild !== lastChild`):
|
|
92
|
+
- 開始フラグメント: 式の開始から最初の子の終了まで
|
|
93
|
+
- 終了フラグメント: 最後の子の開始から式の終了まで
|
|
94
|
+
- 子は開始フラグメントの psblock 内で訪問される
|
|
95
|
+
- 式に子が1つまたは子がない場合:
|
|
96
|
+
- 式全体が1つの MustacheTag psblock として出力される
|
|
97
|
+
3. 変更時の注意:
|
|
98
|
+
- `sliceFragment()` のオフセットが開始フラグメントと終了フラグメントの両方で正しいことを確認
|
|
99
|
+
- 終了フラグメントは `isFragment: false` である必要がある
|
|
100
|
+
- 開始フラグメントは `isFragment: true` で、子の訪問のために `originNode.children` を渡す必要がある
|
|
101
|
+
4. ビルドとテスト: `yarn build --scope @markuplint/astro-parser && yarn test --scope @markuplint/astro-parser`
|
|
102
|
+
5. 複雑な式でテスト:
|
|
103
|
+
```ts
|
|
104
|
+
test('Nested expression with HTML', () => {
|
|
105
|
+
const ast = parse('<ul>{list.map(item => <li>{item}</li>)}</ul>');
|
|
106
|
+
const map = nodeListToDebugMaps(ast.nodeList);
|
|
107
|
+
// 開始 MustacheTag、ネストされた要素、終了 MustacheTag を検証
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## 上流影響チェックリスト
|
|
112
|
+
|
|
113
|
+
上流パッケージの変更がこのパーサーに影響を与える可能性があります:
|
|
114
|
+
|
|
115
|
+
| パッケージ | 影響 |
|
|
116
|
+
| -------------------------- | ----------------------------------------------------------- |
|
|
117
|
+
| `@markuplint/parser-utils` | 基底 `Parser` クラスの変更は全オーバーライドメソッドに影響 |
|
|
118
|
+
| `@markuplint/ml-ast` | AST 型の変更は `nodeize()` の戻り値の型に影響 |
|
|
119
|
+
| `astro-eslint-parser` | パーサー出力形式の変更は `tokenize()` と `nodeize()` に影響 |
|
|
120
|
+
|
|
121
|
+
`astro-eslint-parser` を更新する場合:
|
|
122
|
+
|
|
123
|
+
```shell
|
|
124
|
+
# ランタイム依存を更新
|
|
125
|
+
yarn upgrade astro-eslint-parser --scope @markuplint/astro-parser
|
|
126
|
+
|
|
127
|
+
# 型用の開発依存を更新
|
|
128
|
+
yarn upgrade @astrojs/compiler --scope @markuplint/astro-parser --dev
|
|
129
|
+
|
|
130
|
+
# 互換性を検証
|
|
131
|
+
yarn build --scope @markuplint/astro-parser && yarn test --scope @markuplint/astro-parser
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## トラブルシューティング
|
|
135
|
+
|
|
136
|
+
### フロントマターが認識されない
|
|
137
|
+
|
|
138
|
+
**症状:** `---...---` ブロックが Frontmatter psblock としてパースされない、またはその内容が HTML AST に漏れる。
|
|
139
|
+
|
|
140
|
+
**原因:** `astro-eslint-parser` が `type: 'frontmatter'` ノードを生成していないか、ノードの位置オフセットが不正。
|
|
141
|
+
|
|
142
|
+
**解決策:**
|
|
143
|
+
|
|
144
|
+
1. `src/astro-parser.spec.ts` にテストを追加し、`astroParse()` からの生の AST 出力を検証
|
|
145
|
+
2. フロントマターノードの `position.start.offset` と `position.end.offset` が正しいことを確認
|
|
146
|
+
3. `nodeize()` の `case 'frontmatter'` ブランチに到達していることを検証
|
|
147
|
+
|
|
148
|
+
### 式の分割で不正なオフセットが生成される
|
|
149
|
+
|
|
150
|
+
**症状:** MustacheTag psblock ノードの開始/終了位置が不正、または式内のネストされた HTML 要素の位置がずれている。
|
|
151
|
+
|
|
152
|
+
**原因:** `case 'expression'` ブランチの `sliceFragment()` 呼び出しが Astro AST の子から間違ったオフセットを使用している。
|
|
153
|
+
|
|
154
|
+
**解決策:**
|
|
155
|
+
|
|
156
|
+
1. `firstChild.position?.end?.offset` と `lastChild.position?.start.offset` を確認 — これらは Astro AST の位置と正確に一致する必要がある
|
|
157
|
+
2. `startExpressionEndOffset` が式の開始と最初の HTML 子の間にあることを検証
|
|
158
|
+
3. `nodeListToDebugMaps` を使用して実際の位置と期待される位置を比較
|
|
159
|
+
|
|
160
|
+
### 名前空間が正しく適用されない
|
|
161
|
+
|
|
162
|
+
**症状:** `<svg>` 内の要素が XHTML 名前空間を持つ、または `<foreignObject>` 内の要素が SVG 名前空間を持つ。
|
|
163
|
+
|
|
164
|
+
**原因:** `#updateScopeNS()` が要素タイプを正しく検出していないか、`scopeNS` 状態がリセットされていない。
|
|
165
|
+
|
|
166
|
+
**解決策:**
|
|
167
|
+
|
|
168
|
+
1. `originNode.type === 'element'` を確認 — 要素ノードのみが名前空間の変更をトリガー
|
|
169
|
+
2. `originNode.name?.toLowerCase()` を確認 — `svg` の比較はケースインセンシティブである必要がある
|
|
170
|
+
3. `parentNode.nodeName === 'foreignObject'` の比較を確認 — これは Astro AST の名前ではなく markuplint のノード名を使用
|
|
171
|
+
4. 特定のネストパターンのテストケースを `src/parser.spec.ts` に追加
|
|
172
|
+
|
|
173
|
+
### テンプレートディレクティブが検出されない
|
|
174
|
+
|
|
175
|
+
**症状:** `set:html={content}` のような属性に `isDirective: true` が設定されない、または `class:list` に `potentialName: 'class'` が設定されない。
|
|
176
|
+
|
|
177
|
+
**原因:** 正規表現 `/^([^:]+):([^:]+)$/` がマッチしなかった、またはスイッチケースが欠落。
|
|
178
|
+
|
|
179
|
+
**解決策:**
|
|
180
|
+
|
|
181
|
+
1. 属性名の形式を確認 — 正規表現はコロンが1つだけで、両側に空でない部分が必要
|
|
182
|
+
2. `switch (lowerCaseDirectiveName)` を確認 — ディレクティブプレフィックスがケースにマッチする必要がある
|
|
183
|
+
3. 新しいディレクティブプレフィックスの場合は、新しいケースを追加(レシピ #1 参照)
|
|
@@ -0,0 +1,183 @@
|
|
|
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
|
+
1. Read `src/parser.ts` — the `#updateScopeNS()` private method
|
|
65
|
+
2. The method has two conditions:
|
|
66
|
+
- XHTML → SVG: when current namespace is XHTML and node is a `<svg>` element
|
|
67
|
+
- SVG → XHTML: when current namespace is SVG and parent is `<foreignObject>`
|
|
68
|
+
3. To add a new namespace transition (e.g., MathML):
|
|
69
|
+
```ts
|
|
70
|
+
if (
|
|
71
|
+
parentNS === 'http://www.w3.org/1999/xhtml' &&
|
|
72
|
+
originNode.type === 'element' &&
|
|
73
|
+
originNode.name?.toLowerCase() === 'math'
|
|
74
|
+
) {
|
|
75
|
+
this.state.scopeNS = 'http://www.w3.org/1998/Math/MathML';
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
4. Build and test: `yarn build --scope @markuplint/astro-parser && yarn test --scope @markuplint/astro-parser`
|
|
79
|
+
5. Add namespace test cases to `src/parser.spec.ts`:
|
|
80
|
+
```ts
|
|
81
|
+
test('MathML namespace', () => {
|
|
82
|
+
const doc = parse('<div><math><mi>x</mi></math></div>');
|
|
83
|
+
expect(doc.nodeList[1].namespace).toBe('http://www.w3.org/1998/Math/MathML');
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 3. Updating Expression Handling
|
|
88
|
+
|
|
89
|
+
1. Read `src/parser.ts` — the `case 'expression'` block in `nodeize()`
|
|
90
|
+
2. The expression splitting logic works as follows:
|
|
91
|
+
- If the expression has multiple children (`firstChild !== lastChild`):
|
|
92
|
+
- Opening fragment: from expression start to first child's end
|
|
93
|
+
- Closing fragment: from last child's start to expression end
|
|
94
|
+
- Children are visited within the opening fragment's psblock
|
|
95
|
+
- If the expression has a single child or no children:
|
|
96
|
+
- The entire expression is emitted as one MustacheTag psblock
|
|
97
|
+
3. When modifying:
|
|
98
|
+
- Ensure `sliceFragment()` offsets are correct for both opening and closing fragments
|
|
99
|
+
- The closing fragment must have `isFragment: false`
|
|
100
|
+
- The opening fragment must have `isFragment: true` and pass `originNode.children` for child visitation
|
|
101
|
+
4. Build and test: `yarn build --scope @markuplint/astro-parser && yarn test --scope @markuplint/astro-parser`
|
|
102
|
+
5. Test with complex expressions:
|
|
103
|
+
```ts
|
|
104
|
+
test('Nested expression with HTML', () => {
|
|
105
|
+
const ast = parse('<ul>{list.map(item => <li>{item}</li>)}</ul>');
|
|
106
|
+
const map = nodeListToDebugMaps(ast.nodeList);
|
|
107
|
+
// Verify opening MustacheTag, nested elements, and closing MustacheTag
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Upstream Impact Checklist
|
|
112
|
+
|
|
113
|
+
Changes to upstream packages can affect this parser:
|
|
114
|
+
|
|
115
|
+
| Package | Impact |
|
|
116
|
+
| -------------------------- | ---------------------------------------------------------------- |
|
|
117
|
+
| `@markuplint/parser-utils` | Base `Parser` class changes affect all override methods |
|
|
118
|
+
| `@markuplint/ml-ast` | AST type changes affect `nodeize()` return types |
|
|
119
|
+
| `astro-eslint-parser` | Parser output format changes affect `tokenize()` and `nodeize()` |
|
|
120
|
+
|
|
121
|
+
When updating `astro-eslint-parser`:
|
|
122
|
+
|
|
123
|
+
```shell
|
|
124
|
+
# Update the runtime dependency
|
|
125
|
+
yarn upgrade astro-eslint-parser --scope @markuplint/astro-parser
|
|
126
|
+
|
|
127
|
+
# Update the dev dependency for types
|
|
128
|
+
yarn upgrade @astrojs/compiler --scope @markuplint/astro-parser --dev
|
|
129
|
+
|
|
130
|
+
# Verify compatibility
|
|
131
|
+
yarn build --scope @markuplint/astro-parser && yarn test --scope @markuplint/astro-parser
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Troubleshooting
|
|
135
|
+
|
|
136
|
+
### Frontmatter is not recognized
|
|
137
|
+
|
|
138
|
+
**Symptom:** The `---...---` block is not parsed as a Frontmatter psblock, or its content leaks into the HTML AST.
|
|
139
|
+
|
|
140
|
+
**Cause:** `astro-eslint-parser` may not be producing a `type: 'frontmatter'` node, or the node's position offsets are incorrect.
|
|
141
|
+
|
|
142
|
+
**Solution:**
|
|
143
|
+
|
|
144
|
+
1. Add a test in `src/astro-parser.spec.ts` to verify the raw AST output from `astroParse()`
|
|
145
|
+
2. Check that the frontmatter node has correct `position.start.offset` and `position.end.offset`
|
|
146
|
+
3. Verify the `case 'frontmatter'` branch in `nodeize()` is being reached
|
|
147
|
+
|
|
148
|
+
### Expression splitting produces wrong offsets
|
|
149
|
+
|
|
150
|
+
**Symptom:** MustacheTag psblock nodes have incorrect start/end positions, or nested HTML elements inside expressions are misaligned.
|
|
151
|
+
|
|
152
|
+
**Cause:** The `sliceFragment()` calls in the `case 'expression'` branch are using wrong offsets from the Astro AST children.
|
|
153
|
+
|
|
154
|
+
**Solution:**
|
|
155
|
+
|
|
156
|
+
1. Check `firstChild.position?.end?.offset` and `lastChild.position?.start.offset` — these must match the Astro AST positions exactly
|
|
157
|
+
2. Verify that `startExpressionEndOffset` falls between the expression start and the first HTML child
|
|
158
|
+
3. Use `nodeListToDebugMaps` to compare actual vs expected positions
|
|
159
|
+
|
|
160
|
+
### Namespace is not applied correctly
|
|
161
|
+
|
|
162
|
+
**Symptom:** Elements inside `<svg>` have XHTML namespace, or elements inside `<foreignObject>` have SVG namespace.
|
|
163
|
+
|
|
164
|
+
**Cause:** `#updateScopeNS()` is not detecting the element type correctly, or the `scopeNS` state is not being reset.
|
|
165
|
+
|
|
166
|
+
**Solution:**
|
|
167
|
+
|
|
168
|
+
1. Check that `originNode.type === 'element'` — only element nodes trigger namespace changes
|
|
169
|
+
2. Check `originNode.name?.toLowerCase()` — the comparison must be case-insensitive for `svg`
|
|
170
|
+
3. Check the `parentNode.nodeName === 'foreignObject'` comparison — this uses the markuplint node name, not the Astro AST name
|
|
171
|
+
4. Add a test case with the specific nesting pattern to `src/parser.spec.ts`
|
|
172
|
+
|
|
173
|
+
### Template directive not detected
|
|
174
|
+
|
|
175
|
+
**Symptom:** An attribute like `set:html={content}` does not get `isDirective: true`, or `class:list` does not get `potentialName: 'class'`.
|
|
176
|
+
|
|
177
|
+
**Cause:** The regex `/^([^:]+):([^:]+)$/` did not match, or the switch case is missing.
|
|
178
|
+
|
|
179
|
+
**Solution:**
|
|
180
|
+
|
|
181
|
+
1. Verify the attribute name format — the regex requires exactly one colon with non-empty parts on both sides
|
|
182
|
+
2. Check the `switch (lowerCaseDirectiveName)` — the directive prefix must match a case
|
|
183
|
+
3. If it is a new directive prefix, add a new case (see Recipe #1)
|
package/lib/astro-parser.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
import type { RootNode } from '@astrojs/compiler/types';
|
|
2
2
|
export type { RootNode, ElementNode, CustomElementNode, ComponentNode, FragmentNode, AttributeNode, Node, } from '@astrojs/compiler/types';
|
|
3
|
+
/**
|
|
4
|
+
* Parses an Astro component source string into the Astro compiler's root AST node.
|
|
5
|
+
* Delegates to astro-eslint-parser and converts any diagnostics into ParserErrors.
|
|
6
|
+
*
|
|
7
|
+
* @param code - The raw Astro component source code
|
|
8
|
+
* @returns The root AST node produced by the Astro compiler
|
|
9
|
+
*/
|
|
3
10
|
export declare function astroParse(code: string): RootNode;
|
package/lib/astro-parser.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { ParserError } from '@markuplint/parser-utils';
|
|
2
2
|
import { parseTemplate } from 'astro-eslint-parser';
|
|
3
|
+
/**
|
|
4
|
+
* Parses an Astro component source string into the Astro compiler's root AST node.
|
|
5
|
+
* Delegates to astro-eslint-parser and converts any diagnostics into ParserErrors.
|
|
6
|
+
*
|
|
7
|
+
* @param code - The raw Astro component source code
|
|
8
|
+
* @returns The root AST node produced by the Astro compiler
|
|
9
|
+
*/
|
|
3
10
|
export function astroParse(code) {
|
|
4
11
|
const { result } = parseTemplate(code);
|
|
5
12
|
if (result.diagnostics[0]) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MLASTBlockBehavior } from '@markuplint/ml-ast';
|
|
2
|
+
/**
|
|
3
|
+
* Detects the block behavior of an Astro expression by inspecting its raw
|
|
4
|
+
* source for `.map()` or `.filter()` array method calls. Maps `.map()` to
|
|
5
|
+
* `'each'` (iteration) and `.filter()` to `'if'` (conditional filtering).
|
|
6
|
+
*
|
|
7
|
+
* @param raw - The raw source text of the expression to analyze
|
|
8
|
+
* @returns The detected block behavior, or `null` if no recognized pattern is found
|
|
9
|
+
*/
|
|
10
|
+
export declare function detectBlockBehavior(raw: string): MLASTBlockBehavior | null;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects the block behavior of an Astro expression by inspecting its raw
|
|
3
|
+
* source for `.map()` or `.filter()` array method calls. Maps `.map()` to
|
|
4
|
+
* `'each'` (iteration) and `.filter()` to `'if'` (conditional filtering).
|
|
5
|
+
*
|
|
6
|
+
* @param raw - The raw source text of the expression to analyze
|
|
7
|
+
* @returns The detected block behavior, or `null` if no recognized pattern is found
|
|
8
|
+
*/
|
|
9
|
+
export function detectBlockBehavior(raw) {
|
|
10
|
+
const re = /\.+\s*(?<type>map|filter)\s*\((?:function\s*\(.[^\n\r{\u2028\u2029]*\{.*return\s*$|.+=>\s*\(?\s*)/;
|
|
11
|
+
const match = raw.match(re);
|
|
12
|
+
if (!match) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const type = match.groups?.type === 'map' ? 'each' : 'if';
|
|
16
|
+
return {
|
|
17
|
+
type,
|
|
18
|
+
expression: raw,
|
|
19
|
+
};
|
|
20
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -1 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Astro component parser for markuplint. Provides a parser that transforms Astro
|
|
4
|
+
* component syntax into markuplint's AST, handling frontmatter blocks, expression
|
|
5
|
+
* syntax (`{}`), Astro-specific directives (e.g., `class:list`, `set:html`),
|
|
6
|
+
* and namespace-aware element resolution.
|
|
7
|
+
*/
|
|
1
8
|
export { parser } from './parser.js';
|
package/lib/index.js
CHANGED
|
@@ -1 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Astro component parser for markuplint. Provides a parser that transforms Astro
|
|
4
|
+
* component syntax into markuplint's AST, handling frontmatter blocks, expression
|
|
5
|
+
* syntax (`{}`), Astro-specific directives (e.g., `class:list`, `set:html`),
|
|
6
|
+
* and namespace-aware element resolution.
|
|
7
|
+
*/
|
|
1
8
|
export { parser } from './parser.js';
|
package/lib/parser.d.ts
CHANGED
|
@@ -1,21 +1,59 @@
|
|
|
1
1
|
import type { Node } from './astro-parser.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { MLASTNodeTreeItem, MLASTParentNode } from '@markuplint/ml-ast';
|
|
3
3
|
import type { ChildToken, Token } from '@markuplint/parser-utils';
|
|
4
4
|
import { Parser } from '@markuplint/parser-utils';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Parser implementation for Astro component templates.
|
|
7
|
+
* Extends the base Parser to handle Astro-specific syntax including frontmatter blocks,
|
|
8
|
+
* expression containers (`{}`), component/element/fragment types, Astro directives
|
|
9
|
+
* (e.g., `class:list`, `set:html`), and shorthand attributes.
|
|
10
|
+
*/
|
|
11
|
+
declare class AstroParser extends Parser<Node> {
|
|
10
12
|
constructor();
|
|
11
13
|
tokenize(): {
|
|
12
14
|
ast: Node[];
|
|
13
15
|
isFragment: boolean;
|
|
14
16
|
};
|
|
17
|
+
/**
|
|
18
|
+
* Converts an Astro AST node into markuplint node tree items.
|
|
19
|
+
* Handles frontmatter, doctype, text, comment, component/element/fragment,
|
|
20
|
+
* and expression nodes.
|
|
21
|
+
*
|
|
22
|
+
* @param originNode - The Astro AST node to convert
|
|
23
|
+
* @param parentNode - The parent node in the markuplint tree, or null for root nodes
|
|
24
|
+
* @param depth - The nesting depth of the node
|
|
25
|
+
* @returns An array of markuplint node tree items
|
|
26
|
+
*/
|
|
15
27
|
nodeize(originNode: Node, parentNode: MLASTParentNode | null, depth: number): readonly MLASTNodeTreeItem[];
|
|
16
28
|
afterFlattenNodes(nodeList: readonly MLASTNodeTreeItem[]): readonly MLASTNodeTreeItem[];
|
|
29
|
+
/**
|
|
30
|
+
* Visits an element token by first parsing the raw HTML fragment to extract
|
|
31
|
+
* the start tag, then delegating to the base visitElement with Astro-specific
|
|
32
|
+
* options including nameless fragment support.
|
|
33
|
+
*
|
|
34
|
+
* @param token - The child token representing the element
|
|
35
|
+
* @param childNodes - The child Astro AST nodes within the element
|
|
36
|
+
* @returns An array of markuplint node tree items
|
|
37
|
+
*/
|
|
17
38
|
visitElement(token: ChildToken, childNodes: readonly Node[]): readonly MLASTNodeTreeItem[];
|
|
39
|
+
/**
|
|
40
|
+
* Visits child nodes and verifies that no sibling nodes with differing
|
|
41
|
+
* hierarchy levels are produced. Throws a ParserError if unexpected
|
|
42
|
+
* sibling nodes are discovered.
|
|
43
|
+
*
|
|
44
|
+
* @param children - The child Astro AST nodes to visit
|
|
45
|
+
* @param parentNode - The parent node in the markuplint tree
|
|
46
|
+
* @returns An empty array (all children are attached via the visitor)
|
|
47
|
+
*/
|
|
18
48
|
visitChildren(children: readonly Node[], parentNode: MLASTParentNode | null): never[];
|
|
49
|
+
/**
|
|
50
|
+
* Visits an attribute token, handling Astro-specific syntax including
|
|
51
|
+
* curly-brace expression values, shorthand attributes (`{name}`),
|
|
52
|
+
* and template directives (e.g., `class:list`, `set:html`).
|
|
53
|
+
*
|
|
54
|
+
* @param token - The token representing the attribute
|
|
55
|
+
* @returns The parsed attribute node with Astro-specific metadata
|
|
56
|
+
*/
|
|
19
57
|
visitAttr(token: Token): (import("@markuplint/ml-ast").MLASTSpreadAttr & {
|
|
20
58
|
__rightText?: string;
|
|
21
59
|
}) | {
|
|
@@ -38,12 +76,9 @@ declare class AstroParser extends Parser<Node, State> {
|
|
|
38
76
|
isDuplicatable: boolean;
|
|
39
77
|
uuid: string;
|
|
40
78
|
raw: string;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
endLine: number;
|
|
45
|
-
startCol: number;
|
|
46
|
-
endCol: number;
|
|
79
|
+
offset: number;
|
|
80
|
+
line: number;
|
|
81
|
+
col: number;
|
|
47
82
|
__rightText?: string;
|
|
48
83
|
};
|
|
49
84
|
/**
|