@markuplint/astro-parser 4.6.21 → 4.6.23
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 +5 -1
- 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 +2 -0
- package/lib/detect-block-behavior.js +12 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.js +7 -0
- package/lib/parser.d.ts +43 -0
- package/lib/parser.js +43 -0
- package/package.json +5 -5
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# @markuplint/astro-parser
|
|
2
|
+
|
|
3
|
+
## 概要
|
|
4
|
+
|
|
5
|
+
`@markuplint/astro-parser` は markuplint における Astro コンポーネントファイル(`.astro`)のパーサーです。`astro-eslint-parser`(`@astrojs/compiler` をラップ)を使用して Astro ソースコードをトークン化し、その結果の AST を markuplint の統一 AST 形式(`MLASTDocument`)に変換します。フロントマターブロック(`---...---`)、式コンテナ(`{expression}`)、テンプレートディレクティブ(例: `class:list`、`set:html`)、ショートハンド属性(`{prop}`)、名前空間対応の要素解決(XHTML vs SVG)など、Astro 固有の構文を処理します。
|
|
6
|
+
|
|
7
|
+
## ディレクトリ構成
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
src/
|
|
11
|
+
├── index.ts — parser インスタンスを再エクスポート
|
|
12
|
+
├── parser.ts — Parser<Node, State> を拡張する AstroParser クラス
|
|
13
|
+
├── astro-parser.ts — astro-eslint-parser ラッパーと型の再エクスポート
|
|
14
|
+
├── parser.spec.ts — AstroParser 統合テスト
|
|
15
|
+
└── astro-parser.spec.ts — astro-eslint-parser ラッパーテスト
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## アーキテクチャ図
|
|
19
|
+
|
|
20
|
+
```mermaid
|
|
21
|
+
flowchart TD
|
|
22
|
+
subgraph upstream ["上流"]
|
|
23
|
+
mlAst["@markuplint/ml-ast\n(AST 型定義)"]
|
|
24
|
+
parserUtils["@markuplint/parser-utils\n(抽象 Parser クラス)"]
|
|
25
|
+
astroEslintParser["astro-eslint-parser\n(Astro トークナイザ)"]
|
|
26
|
+
astroCompiler["@astrojs/compiler\n(AST 型定義)"]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
subgraph pkg ["@markuplint/astro-parser"]
|
|
30
|
+
astroParser["AstroParser\nextends Parser‹Node, State›"]
|
|
31
|
+
astroParseFn["astroParse()\nastro-eslint-parser ラッパー"]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
subgraph downstream ["下流"]
|
|
35
|
+
mlCore["@markuplint/ml-core\n(MLASTDocument → MLDOM)"]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
mlAst -->|"AST 型"| astroParser
|
|
39
|
+
parserUtils -->|"Parser 基底クラス"| astroParser
|
|
40
|
+
astroEslintParser -->|"parseTemplate()"| astroParseFn
|
|
41
|
+
astroCompiler -->|"Node 型"| astroParseFn
|
|
42
|
+
astroParseFn -->|"RootNode.children"| astroParser
|
|
43
|
+
astroParser -->|"MLASTDocument を生成"| mlCore
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## AstroParser クラス
|
|
47
|
+
|
|
48
|
+
### 継承関係
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
Parser<Node, State> (@markuplint/parser-utils)
|
|
52
|
+
└── AstroParser (このパッケージ)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### コンストラクタ
|
|
56
|
+
|
|
57
|
+
コンストラクタは Astro 固有のオプションで基底 `Parser` を設定します:
|
|
58
|
+
|
|
59
|
+
| オプション | 値 | 用途 |
|
|
60
|
+
| ---------------------- | ------------ | ---------------------------------------------------------------------------- |
|
|
61
|
+
| `endTagType` | `'xml'` | Astro は XML のように明示的な閉じタグを使用 |
|
|
62
|
+
| `selfCloseType` | `'html+xml'` | HTML void 要素と XML スタイルの自己閉じ(`<Component />`)の両方を受け入れる |
|
|
63
|
+
| `tagNameCaseSensitive` | `true` | コンポーネント(`<MyComp>`)と HTML 要素(`<div>`)を区別 |
|
|
64
|
+
|
|
65
|
+
### State 型
|
|
66
|
+
|
|
67
|
+
パーサーは `State` 型を通じて内部状態を管理します:
|
|
68
|
+
|
|
69
|
+
| フィールド | 型 | 用途 |
|
|
70
|
+
| ---------- | -------- | --------------------------------------------------------------- |
|
|
71
|
+
| `scopeNS` | `string` | 現在の名前空間 URI、デフォルトは `http://www.w3.org/1999/xhtml` |
|
|
72
|
+
|
|
73
|
+
`scopeNS` 状態は `#updateScopeNS()` によってパーサーが要素を走査する際に更新され、`<svg>` 要素内で SVG 名前空間に切り替わり、`<foreignObject>` 内で XHTML に戻ります。
|
|
74
|
+
|
|
75
|
+
### オーバーライドメソッド
|
|
76
|
+
|
|
77
|
+
| メソッド | 用途 |
|
|
78
|
+
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
79
|
+
| `tokenize()` | `astroParse()` を呼び出して Astro AST を取得し、`{ ast: rootNode.children, isFragment: true }` を返す |
|
|
80
|
+
| `nodeize()` | Astro AST ノードを markuplint ノードに変換。ノードタイプ(frontmatter, doctype, text, comment, element, expression)で振り分け |
|
|
81
|
+
| `afterFlattenNodes()` | `{ exposeInvalidNode: false }` で親に委譲 |
|
|
82
|
+
| `visitElement()` | `parseCodeFragment()` で `namelessFragment: true` として生の HTML フラグメントをパースし、名前空間と終了タグ処理で親に委譲 |
|
|
83
|
+
| `visitChildren()` | 親に委譲した後、予期しない兄弟ノードが残っていないことをアサート |
|
|
84
|
+
| `visitAttr()` | 波括弧式の値、ショートハンド属性、テンプレートディレクティブを処理 |
|
|
85
|
+
| `detectElementType()` | `/^[A-Z]/` パターンでコンポーネントと HTML 要素を検出(大文字始まりの名前はコンポーネント) |
|
|
86
|
+
|
|
87
|
+
## フロントマター処理
|
|
88
|
+
|
|
89
|
+
Astro コンポーネントは `---` で区切られたフロントマターブロックを含むことができます:
|
|
90
|
+
|
|
91
|
+
```astro
|
|
92
|
+
---
|
|
93
|
+
const name = "World";
|
|
94
|
+
---
|
|
95
|
+
<div>{name}</div>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`astro-eslint-parser` は `type: 'frontmatter'` のノードを生成します。パーサーはこれを `nodeName: 'Frontmatter'` かつ `isFragment: false` の **psblock**(疑似ブロック)に変換します。区切り文字 `---` を含むブロック全体が単一の不透明ノードとしてキャプチャされます。フロントマター内のコンテンツは HTML としてパースされません。
|
|
99
|
+
|
|
100
|
+
## 式の処理
|
|
101
|
+
|
|
102
|
+
Astro の式(`{expression}`)は Astro AST で `type: 'expression'` ノードとして表現されます。パーサーはこれらを **MustacheTag** psblock ノードに変換します。
|
|
103
|
+
|
|
104
|
+
### 単純な式
|
|
105
|
+
|
|
106
|
+
`{name}` のような単純な式は単一のテキスト子ノードを持ちます。式全体が `isFragment: true` の1つの MustacheTag psblock として出力されます。
|
|
107
|
+
|
|
108
|
+
### HTML を含むネストされた式
|
|
109
|
+
|
|
110
|
+
式が HTML 要素を含む場合(例: `{list.map(item => <li>{item}</li>)}`)、パーサーは複数のノードに分割します:
|
|
111
|
+
|
|
112
|
+
1. **開始式フラグメント**: `{list.map(item => ` — 子ノードを含む MustacheTag psblock
|
|
113
|
+
2. **ネストされた HTML 要素**: `<li>{item}</li>` — 通常の要素として処理
|
|
114
|
+
3. **終了式フラグメント**: `)}` — `isFragment: false` の別の MustacheTag psblock
|
|
115
|
+
|
|
116
|
+
分割ロジックは式の children 配列で `firstChild !== lastChild` かどうかを確認します。該当する場合:
|
|
117
|
+
|
|
118
|
+
- 式の開始から最初の子の終了までの領域が開始フラグメントになる
|
|
119
|
+
- 最後の子の開始から式の終了までの領域が終了フラグメントになる
|
|
120
|
+
- 間の子は開始フラグメントの psblock 内で通常通り訪問される
|
|
121
|
+
|
|
122
|
+
## 名前空間スコーピング
|
|
123
|
+
|
|
124
|
+
`#updateScopeNS()` プライベートメソッドは、パーサーが要素を走査する際に名前空間コンテキストを管理します:
|
|
125
|
+
|
|
126
|
+
| 条件 | アクション |
|
|
127
|
+
| ----------------------------------------------------- | ---------------------------------------------------- |
|
|
128
|
+
| 現在の名前空間が XHTML で、ノードが `<svg>` 要素 | `scopeNS` を `http://www.w3.org/2000/svg` に切り替え |
|
|
129
|
+
| 現在の名前空間が SVG で、親ノードが `<foreignObject>` | `scopeNS` を `http://www.w3.org/1999/xhtml` に戻す |
|
|
130
|
+
|
|
131
|
+
これは `nodeize()` のノードタイプ switch の前に呼び出されるため、すべての子ノードが正しい名前空間を継承します。名前空間は `visitElement()` 内で `overwriteProps: { namespace: this.state.scopeNS }` を通じて要素に適用されます。
|
|
132
|
+
|
|
133
|
+
名前空間解決の例:
|
|
134
|
+
|
|
135
|
+
```html
|
|
136
|
+
<div>
|
|
137
|
+
<!-- XHTML -->
|
|
138
|
+
<svg>
|
|
139
|
+
<!-- SVG -->
|
|
140
|
+
<text />
|
|
141
|
+
<!-- SVG -->
|
|
142
|
+
<foreignObject>
|
|
143
|
+
<!-- SVG -->
|
|
144
|
+
<div />
|
|
145
|
+
<!-- XHTML(リセット) -->
|
|
146
|
+
</foreignObject>
|
|
147
|
+
</svg>
|
|
148
|
+
</div>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## 属性処理
|
|
152
|
+
|
|
153
|
+
### クォートセット
|
|
154
|
+
|
|
155
|
+
`visitAttr()` メソッドは式の値用に波括弧を含むカスタムクォートセットを使用します:
|
|
156
|
+
|
|
157
|
+
| 開始 | 終了 | タイプ |
|
|
158
|
+
| ---- | ---- | -------- |
|
|
159
|
+
| `"` | `"` | `string` |
|
|
160
|
+
| `'` | `'` | `string` |
|
|
161
|
+
| `{` | `}` | `script` |
|
|
162
|
+
|
|
163
|
+
### ショートハンド属性
|
|
164
|
+
|
|
165
|
+
属性トークンが `{` で始まる場合(例: `{prop}`)、パーサーは `startState: AttrState.BeforeValue` を設定し、名前のパースをスキップして直接値の抽出に進みます。結果の属性は:
|
|
166
|
+
|
|
167
|
+
- `name.raw` = `''`(空)
|
|
168
|
+
- `value.raw` = `prop`
|
|
169
|
+
- `potentialName` = `prop`(値から推論)
|
|
170
|
+
- `isDynamicValue` = `true`
|
|
171
|
+
|
|
172
|
+
### テンプレートディレクティブ
|
|
173
|
+
|
|
174
|
+
Astro テンプレートディレクティブは `name:modifier` 構文を使用します。パーサーは正規表現 `/^([^:]+):([^:]+)$/` でこれらを検出します:
|
|
175
|
+
|
|
176
|
+
| ディレクティブ | `potentialName` | `isDirective` | 動作 |
|
|
177
|
+
| -------------- | --------------- | ------------- | -------------------------------------- |
|
|
178
|
+
| `class:list` | `'class'` | `undefined` | 標準の `class` 属性にマッピング |
|
|
179
|
+
| `set:html` | `undefined` | `true` | Astro 固有ディレクティブとして扱われる |
|
|
180
|
+
| `set:text` | `undefined` | `true` | Astro 固有ディレクティブとして扱われる |
|
|
181
|
+
| `is:raw` | `undefined` | `true` | Astro 固有ディレクティブとして扱われる |
|
|
182
|
+
| `transition:*` | `undefined` | `true` | Astro 固有ディレクティブとして扱われる |
|
|
183
|
+
|
|
184
|
+
`class` ディレクティブは特別で、`potentialName: 'class'` を取得するため、`class` 属性に対する markuplint ルールが適用されます。その他すべてのディレクティブは `isDirective: true` を取得し、フレームワーク固有であり標準 HTML 属性として検証すべきでないことを markuplint に伝えます。
|
|
185
|
+
|
|
186
|
+
### 動的な値
|
|
187
|
+
|
|
188
|
+
開始クォートが `{` の属性はすべて `isDynamicValue: true` を取得します。以下に適用されます:
|
|
189
|
+
|
|
190
|
+
- 明示的な動的値: `prop={value}`
|
|
191
|
+
- ショートハンド属性: `{prop}`
|
|
192
|
+
- ネストされた式: `style={{ a: b }}`
|
|
193
|
+
|
|
194
|
+
## jsx-parser との比較
|
|
195
|
+
|
|
196
|
+
| 機能 | `astro-parser` | `jsx-parser` |
|
|
197
|
+
| ------------------------------ | -------------------------------------- | ------------------------------------------------- |
|
|
198
|
+
| **トークナイザ** | `astro-eslint-parser` | TypeScript ESTree(`@typescript-eslint/parser`) |
|
|
199
|
+
| **フロントマター** | サポート(`---...---` psblock) | 該当なし |
|
|
200
|
+
| **式の構文** | `{expr}` を MustacheTag psblock として | `{expr}` を JSXExpressionContainer psblock として |
|
|
201
|
+
| **テンプレートディレクティブ** | `class:list`、`set:html` 等 | 該当なし |
|
|
202
|
+
| **名前空間管理** | `#updateScopeNS()` で手動管理 | html-parser の `getNamespace()` に委譲 |
|
|
203
|
+
| **コンポーネント検出** | `/^[A-Z]/` パターン | `/^[A-Z]/` パターン |
|
|
204
|
+
| **自己閉じタイプ** | `html+xml` | デフォルト(XML のみ) |
|
|
205
|
+
| **booleanish 属性** | 未設定 | `booleanish: true` |
|
|
206
|
+
| **名前なしフラグメント** | `<>...</>` サポート | `<>...</>` サポート |
|
|
207
|
+
| **スプレッド属性** | 基底パーサーで処理 | カスタム `visitSpreadAttr()` で IDL ルックアップ |
|
|
208
|
+
|
|
209
|
+
## バージョン互換性
|
|
210
|
+
|
|
211
|
+
パースチェーンは以下に依存します:
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
astro-eslint-parser → @astrojs/compiler → Astro 構文サポート
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`astro-eslint-parser` は `parseTemplate()` を提供するランタイム依存です。`@astrojs/compiler` は AST 型定義(`Node`、`RootNode`、`ElementNode` 等)にのみ使用される開発依存です。`astro-eslint-parser` を更新する際は、`@astrojs/compiler` 開発依存も `astro-eslint-parser` が内部で使用するバージョンに合わせて更新する必要があります。
|
|
218
|
+
|
|
219
|
+
## 主要ソースファイル
|
|
220
|
+
|
|
221
|
+
| ファイル | 用途 |
|
|
222
|
+
| ----------------- | ------------------------------------------------------------------------------------- |
|
|
223
|
+
| `parser.ts` | `AstroParser` クラス — 全オーバーライドメソッドと名前空間スコーピング |
|
|
224
|
+
| `astro-parser.ts` | `astroParse()` ラッパー — `astro-eslint-parser` に委譲し、診断を `ParserError` に変換 |
|
|
225
|
+
| `index.ts` | 公開 API — シングルトン `parser` インスタンスを再エクスポート |
|
|
226
|
+
|
|
227
|
+
## ドキュメントマップ
|
|
228
|
+
|
|
229
|
+
- [メンテナンスガイド](docs/maintenance.ja.md) -- コマンド、レシピ、トラブルシューティング
|
package/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# @markuplint/astro-parser
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`@markuplint/astro-parser` is a parser for Astro component files (`.astro`) in markuplint. It uses `astro-eslint-parser` (which wraps `@astrojs/compiler`) to tokenize Astro source code, then converts the resulting AST into markuplint's unified AST format (`MLASTDocument`). The parser handles Astro-specific syntax including frontmatter blocks (`---...---`), expression containers (`{expression}`), template directives (e.g., `class:list`, `set:html`), shorthand attributes (`{prop}`), and namespace-aware element resolution (XHTML vs SVG).
|
|
6
|
+
|
|
7
|
+
## Directory Structure
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
src/
|
|
11
|
+
├── index.ts — Re-exports parser instance
|
|
12
|
+
├── parser.ts — AstroParser class extending Parser<Node, State>
|
|
13
|
+
├── astro-parser.ts — astro-eslint-parser wrapper and type re-exports
|
|
14
|
+
├── parser.spec.ts — AstroParser integration tests
|
|
15
|
+
└── astro-parser.spec.ts — astro-eslint-parser wrapper tests
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Architecture Diagram
|
|
19
|
+
|
|
20
|
+
```mermaid
|
|
21
|
+
flowchart TD
|
|
22
|
+
subgraph upstream ["Upstream"]
|
|
23
|
+
mlAst["@markuplint/ml-ast\n(AST types)"]
|
|
24
|
+
parserUtils["@markuplint/parser-utils\n(Abstract Parser class)"]
|
|
25
|
+
astroEslintParser["astro-eslint-parser\n(Astro tokenizer)"]
|
|
26
|
+
astroCompiler["@astrojs/compiler\n(AST types)"]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
subgraph pkg ["@markuplint/astro-parser"]
|
|
30
|
+
astroParser["AstroParser\nextends Parser‹Node, State›"]
|
|
31
|
+
astroParseFn["astroParse()\nastro-eslint-parser wrapper"]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
subgraph downstream ["Downstream"]
|
|
35
|
+
mlCore["@markuplint/ml-core\n(MLASTDocument → MLDOM)"]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
mlAst -->|"AST types"| astroParser
|
|
39
|
+
parserUtils -->|"Parser base class"| astroParser
|
|
40
|
+
astroEslintParser -->|"parseTemplate()"| astroParseFn
|
|
41
|
+
astroCompiler -->|"Node types"| astroParseFn
|
|
42
|
+
astroParseFn -->|"RootNode.children"| astroParser
|
|
43
|
+
astroParser -->|"produces MLASTDocument"| mlCore
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## AstroParser Class
|
|
47
|
+
|
|
48
|
+
### Inheritance
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
Parser<Node, State> (from @markuplint/parser-utils)
|
|
52
|
+
└── AstroParser (this package)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Constructor
|
|
56
|
+
|
|
57
|
+
The constructor configures the base `Parser` with Astro-specific options:
|
|
58
|
+
|
|
59
|
+
| Option | Value | Purpose |
|
|
60
|
+
| ---------------------- | ------------ | ---------------------------------------------------------------------------- |
|
|
61
|
+
| `endTagType` | `'xml'` | Astro uses explicit closing tags like XML |
|
|
62
|
+
| `selfCloseType` | `'html+xml'` | Accepts both HTML void elements and XML-style self-closing (`<Component />`) |
|
|
63
|
+
| `tagNameCaseSensitive` | `true` | Distinguishes components (`<MyComp>`) from HTML elements (`<div>`) |
|
|
64
|
+
|
|
65
|
+
### State Type
|
|
66
|
+
|
|
67
|
+
The parser maintains internal state through the `State` type:
|
|
68
|
+
|
|
69
|
+
| Field | Type | Purpose |
|
|
70
|
+
| --------- | -------- | ----------------------------------------------------------------- |
|
|
71
|
+
| `scopeNS` | `string` | Current namespace URI, defaults to `http://www.w3.org/1999/xhtml` |
|
|
72
|
+
|
|
73
|
+
The `scopeNS` state is updated by `#updateScopeNS()` as the parser traverses elements, switching to SVG namespace inside `<svg>` elements and back to XHTML inside `<foreignObject>`.
|
|
74
|
+
|
|
75
|
+
### Override Methods
|
|
76
|
+
|
|
77
|
+
| Method | Purpose |
|
|
78
|
+
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
79
|
+
| `tokenize()` | Calls `astroParse()` to get the Astro AST, returns `{ ast: rootNode.children, isFragment: true }` |
|
|
80
|
+
| `nodeize()` | Converts Astro AST nodes to markuplint nodes, dispatching by node type (frontmatter, doctype, text, comment, element, expression) |
|
|
81
|
+
| `afterFlattenNodes()` | Delegates to parent with `{ exposeInvalidNode: false }` |
|
|
82
|
+
| `visitElement()` | Parses the raw HTML fragment via `parseCodeFragment()` with `namelessFragment: true`, then delegates to parent with namespace and end tag handling |
|
|
83
|
+
| `visitChildren()` | Delegates to parent, then asserts no unexpected sibling nodes remain |
|
|
84
|
+
| `visitAttr()` | Handles curly-brace expression values, shorthand attributes, and template directives |
|
|
85
|
+
| `detectElementType()` | Detects component vs HTML element using `/^[A-Z]/` pattern (capitalized names are components) |
|
|
86
|
+
|
|
87
|
+
## Frontmatter Handling
|
|
88
|
+
|
|
89
|
+
Astro components can include a frontmatter block delimited by `---`:
|
|
90
|
+
|
|
91
|
+
```astro
|
|
92
|
+
---
|
|
93
|
+
const name = "World";
|
|
94
|
+
---
|
|
95
|
+
<div>{name}</div>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The `astro-eslint-parser` produces a node with `type: 'frontmatter'`. The parser converts this to a **psblock** (pseudo-block) with `nodeName: 'Frontmatter'` and `isFragment: false`. The entire `---...---` block including delimiters is captured as a single opaque node. Content inside the frontmatter is not parsed as HTML.
|
|
99
|
+
|
|
100
|
+
## Expression Handling
|
|
101
|
+
|
|
102
|
+
Astro expressions (`{expression}`) are represented as `type: 'expression'` nodes in the Astro AST. The parser converts these to **MustacheTag** psblock nodes.
|
|
103
|
+
|
|
104
|
+
### Simple Expressions
|
|
105
|
+
|
|
106
|
+
A simple expression like `{name}` has a single text child. The entire expression is emitted as one MustacheTag psblock with `isFragment: true`.
|
|
107
|
+
|
|
108
|
+
### Nested Expressions with HTML
|
|
109
|
+
|
|
110
|
+
When an expression contains HTML elements (e.g., `{list.map(item => <li>{item}</li>)}`), the parser splits it into multiple nodes:
|
|
111
|
+
|
|
112
|
+
1. **Opening expression fragment**: `{list.map(item => ` — a MustacheTag psblock containing the child nodes
|
|
113
|
+
2. **Nested HTML elements**: `<li>{item}</li>` — processed as normal elements
|
|
114
|
+
3. **Closing expression fragment**: `)}` — a separate MustacheTag psblock with `isFragment: false`
|
|
115
|
+
|
|
116
|
+
The splitting logic checks whether `firstChild !== lastChild` in the expression's children array. If so:
|
|
117
|
+
|
|
118
|
+
- The region from the expression start to the first child's end becomes the opening fragment
|
|
119
|
+
- The region from the last child's start to the expression end becomes the closing fragment
|
|
120
|
+
- The children between are visited normally within the opening fragment's psblock
|
|
121
|
+
|
|
122
|
+
## Namespace Scoping
|
|
123
|
+
|
|
124
|
+
The `#updateScopeNS()` private method manages namespace context as the parser traverses elements:
|
|
125
|
+
|
|
126
|
+
| Condition | Action |
|
|
127
|
+
| ------------------------------------------------------------- | ------------------------------------------------------- |
|
|
128
|
+
| Current namespace is XHTML and node is `<svg>` element | Switch `scopeNS` to `http://www.w3.org/2000/svg` |
|
|
129
|
+
| Current namespace is SVG and parent node is `<foreignObject>` | Switch `scopeNS` back to `http://www.w3.org/1999/xhtml` |
|
|
130
|
+
|
|
131
|
+
This is called at the beginning of `nodeize()` before the node type switch, so all child nodes inherit the correct namespace. The namespace is applied to elements via `overwriteProps: { namespace: this.state.scopeNS }` in `visitElement()`.
|
|
132
|
+
|
|
133
|
+
Example namespace resolution:
|
|
134
|
+
|
|
135
|
+
```html
|
|
136
|
+
<div>
|
|
137
|
+
<!-- XHTML -->
|
|
138
|
+
<svg>
|
|
139
|
+
<!-- SVG -->
|
|
140
|
+
<text />
|
|
141
|
+
<!-- SVG -->
|
|
142
|
+
<foreignObject>
|
|
143
|
+
<!-- SVG -->
|
|
144
|
+
<div />
|
|
145
|
+
<!-- XHTML (reset) -->
|
|
146
|
+
</foreignObject>
|
|
147
|
+
</svg>
|
|
148
|
+
</div>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Attribute Processing
|
|
152
|
+
|
|
153
|
+
### Quote Set
|
|
154
|
+
|
|
155
|
+
The `visitAttr()` method uses a custom quote set that includes curly braces for expression values:
|
|
156
|
+
|
|
157
|
+
| Start | End | Type |
|
|
158
|
+
| ----- | --- | -------- |
|
|
159
|
+
| `"` | `"` | `string` |
|
|
160
|
+
| `'` | `'` | `string` |
|
|
161
|
+
| `{` | `}` | `script` |
|
|
162
|
+
|
|
163
|
+
### Shorthand Attributes
|
|
164
|
+
|
|
165
|
+
When an attribute token starts with `{` (e.g., `{prop}`), the parser sets `startState: AttrState.BeforeValue`, which skips name parsing and goes directly to value extraction. The resulting attribute has:
|
|
166
|
+
|
|
167
|
+
- `name.raw` = `''` (empty)
|
|
168
|
+
- `value.raw` = `prop`
|
|
169
|
+
- `potentialName` = `prop` (inferred from value)
|
|
170
|
+
- `isDynamicValue` = `true`
|
|
171
|
+
|
|
172
|
+
### Template Directives
|
|
173
|
+
|
|
174
|
+
Astro template directives use the `name:modifier` syntax. The parser detects these with the regex `/^([^:]+):([^:]+)$/`:
|
|
175
|
+
|
|
176
|
+
| Directive | `potentialName` | `isDirective` | Behavior |
|
|
177
|
+
| -------------- | --------------- | ------------- | ----------------------------------- |
|
|
178
|
+
| `class:list` | `'class'` | `undefined` | Maps to standard `class` attribute |
|
|
179
|
+
| `set:html` | `undefined` | `true` | Treated as Astro-specific directive |
|
|
180
|
+
| `set:text` | `undefined` | `true` | Treated as Astro-specific directive |
|
|
181
|
+
| `is:raw` | `undefined` | `true` | Treated as Astro-specific directive |
|
|
182
|
+
| `transition:*` | `undefined` | `true` | Treated as Astro-specific directive |
|
|
183
|
+
|
|
184
|
+
The `class` directive is special: it gets `potentialName: 'class'` so markuplint rules for the `class` attribute apply. All other directives get `isDirective: true`, which tells markuplint they are framework-specific and should not be validated as standard HTML attributes.
|
|
185
|
+
|
|
186
|
+
### Dynamic Values
|
|
187
|
+
|
|
188
|
+
Any attribute whose start quote is `{` gets `isDynamicValue: true`. This applies to:
|
|
189
|
+
|
|
190
|
+
- Explicit dynamic values: `prop={value}`
|
|
191
|
+
- Shorthand attributes: `{prop}`
|
|
192
|
+
- Nested expressions: `style={{ a: b }}`
|
|
193
|
+
|
|
194
|
+
## Comparison with jsx-parser
|
|
195
|
+
|
|
196
|
+
| Feature | `astro-parser` | `jsx-parser` |
|
|
197
|
+
| ------------------------- | ------------------------------- | ----------------------------------------------- |
|
|
198
|
+
| **Tokenizer** | `astro-eslint-parser` | TypeScript ESTree (`@typescript-eslint/parser`) |
|
|
199
|
+
| **Frontmatter** | Supported (`---...---` psblock) | Not applicable |
|
|
200
|
+
| **Expression syntax** | `{expr}` as MustacheTag psblock | `{expr}` as JSXExpressionContainer psblock |
|
|
201
|
+
| **Template directives** | `class:list`, `set:html`, etc. | Not applicable |
|
|
202
|
+
| **Namespace management** | Manual via `#updateScopeNS()` | Delegates to `getNamespace()` from html-parser |
|
|
203
|
+
| **Component detection** | `/^[A-Z]/` pattern | `/^[A-Z]/` pattern |
|
|
204
|
+
| **Self-close type** | `html+xml` | Default (XML-only) |
|
|
205
|
+
| **Booleanish attributes** | Not configured | `booleanish: true` |
|
|
206
|
+
| **Nameless fragments** | `<>...</>` supported | `<>...</>` supported |
|
|
207
|
+
| **Spread attributes** | Handled by base parser | Custom `visitSpreadAttr()` with IDL lookup |
|
|
208
|
+
|
|
209
|
+
## Version Compatibility
|
|
210
|
+
|
|
211
|
+
The parsing chain depends on:
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
astro-eslint-parser → @astrojs/compiler → Astro syntax support
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`astro-eslint-parser` is a runtime dependency that provides `parseTemplate()`. `@astrojs/compiler` is a dev dependency used only for AST type definitions (`Node`, `RootNode`, `ElementNode`, etc.). When updating `astro-eslint-parser`, the `@astrojs/compiler` dev dependency should also be updated to match the version that `astro-eslint-parser` uses internally.
|
|
218
|
+
|
|
219
|
+
## Key Source Files
|
|
220
|
+
|
|
221
|
+
| File | Purpose |
|
|
222
|
+
| ----------------- | -------------------------------------------------------------------------------------------------- |
|
|
223
|
+
| `parser.ts` | `AstroParser` class — all override methods and namespace scoping |
|
|
224
|
+
| `astro-parser.ts` | `astroParse()` wrapper — delegates to `astro-eslint-parser`, converts diagnostics to `ParserError` |
|
|
225
|
+
| `index.ts` | Public API — re-exports the singleton `parser` instance |
|
|
226
|
+
|
|
227
|
+
## Documentation Map
|
|
228
|
+
|
|
229
|
+
- [Maintenance Guide](docs/maintenance.md) -- Commands, recipes, and troubleshooting
|
package/CHANGELOG.md
CHANGED
|
@@ -3,13 +3,17 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
-
## [4.6.
|
|
6
|
+
## [4.6.23](https://github.com/markuplint/markuplint/compare/@markuplint/astro-parser@4.6.22...@markuplint/astro-parser@4.6.23) (2026-02-10)
|
|
7
7
|
|
|
8
8
|
**Note:** Version bump only for package @markuplint/astro-parser
|
|
9
9
|
|
|
10
|
+
## [4.6.22](https://github.com/markuplint/markuplint/compare/@markuplint/astro-parser@4.6.21...@markuplint/astro-parser@4.6.22) (2025-11-05)
|
|
10
11
|
|
|
12
|
+
**Note:** Version bump only for package @markuplint/astro-parser
|
|
11
13
|
|
|
14
|
+
## [4.6.21](https://github.com/markuplint/markuplint/compare/@markuplint/astro-parser@4.6.20...@markuplint/astro-parser@4.6.21) (2025-08-24)
|
|
12
15
|
|
|
16
|
+
**Note:** Version bump only for package @markuplint/astro-parser
|
|
13
17
|
|
|
14
18
|
## [4.6.20](https://github.com/markuplint/markuplint/compare/@markuplint/astro-parser@4.6.19...@markuplint/astro-parser@4.6.20) (2025-08-13)
|
|
15
19
|
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Maintenance tasks for @markuplint/astro-parser
|
|
3
|
+
globs:
|
|
4
|
+
- packages/@markuplint/astro-parser/src/**/*.ts
|
|
5
|
+
alwaysApply: false
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# astro-parser-maintenance
|
|
9
|
+
|
|
10
|
+
Perform maintenance tasks for `@markuplint/astro-parser`: add template directives,
|
|
11
|
+
modify namespace scoping, update expression handling, and manage astro-eslint-parser integration.
|
|
12
|
+
|
|
13
|
+
## Input
|
|
14
|
+
|
|
15
|
+
`$ARGUMENTS` specifies the task. Supported tasks:
|
|
16
|
+
|
|
17
|
+
| Task | Description |
|
|
18
|
+
| ---------------------------- | --------------------------------------------------- |
|
|
19
|
+
| `add-directive` | Add a new Astro template directive |
|
|
20
|
+
| `modify-namespace-scoping` | Modify SVG/XHTML namespace scoping logic |
|
|
21
|
+
| `update-expression-handling` | Update expression splitting or MustacheTag handling |
|
|
22
|
+
|
|
23
|
+
If omitted, defaults to `add-directive`.
|
|
24
|
+
|
|
25
|
+
## Reference
|
|
26
|
+
|
|
27
|
+
Before executing any task, read `docs/maintenance.md` (or `docs/maintenance.ja.md`)
|
|
28
|
+
for the full guide. The recipes there are the source of truth for procedures.
|
|
29
|
+
|
|
30
|
+
Also read:
|
|
31
|
+
|
|
32
|
+
- `ARCHITECTURE.md` -- Package overview, attribute processing, namespace scoping
|
|
33
|
+
- `src/parser.ts` -- AstroParser class (source of truth for override methods)
|
|
34
|
+
- `src/astro-parser.ts` -- astro-eslint-parser wrapper
|
|
35
|
+
|
|
36
|
+
## Task: add-directive
|
|
37
|
+
|
|
38
|
+
Add a new Astro template directive. Follow recipe #1 in `docs/maintenance.md`.
|
|
39
|
+
|
|
40
|
+
### Step 1: Understand the directive pattern
|
|
41
|
+
|
|
42
|
+
1. Read `src/parser.ts` — the `visitAttr()` method
|
|
43
|
+
2. Identify the regex: `/^([^:]+):([^:]+)$/`
|
|
44
|
+
3. Understand the `switch (lowerCaseDirectiveName)` block
|
|
45
|
+
|
|
46
|
+
### Step 2: Add the directive case
|
|
47
|
+
|
|
48
|
+
1. Add a new `case` in the switch for the directive prefix
|
|
49
|
+
2. Decide whether it maps to a `potentialName` (like `class:list` → `class`) or is a pure directive (`isDirective: true`)
|
|
50
|
+
3. If it maps to a standard HTML attribute, set `potentialName` to the attribute name
|
|
51
|
+
4. If it is Astro-specific, set `isDirective = true`
|
|
52
|
+
|
|
53
|
+
### Step 3: Verify
|
|
54
|
+
|
|
55
|
+
1. Build: `yarn build --scope @markuplint/astro-parser`
|
|
56
|
+
2. Add test cases to `src/parser.spec.ts` using `nodeListToDebugMaps`
|
|
57
|
+
3. Test: `yarn test --scope @markuplint/astro-parser`
|
|
58
|
+
|
|
59
|
+
## Task: modify-namespace-scoping
|
|
60
|
+
|
|
61
|
+
Modify the SVG/XHTML namespace scoping logic. Follow recipe #2 in `docs/maintenance.md`.
|
|
62
|
+
|
|
63
|
+
### Step 1: Understand the current logic
|
|
64
|
+
|
|
65
|
+
1. Read `src/parser.ts` — the `#updateScopeNS()` private method
|
|
66
|
+
2. Understand the two conditions: `<svg>` → SVG namespace, `<foreignObject>` parent → XHTML namespace
|
|
67
|
+
3. Note that `scopeNS` is applied in `visitElement()` via `overwriteProps`
|
|
68
|
+
|
|
69
|
+
### Step 2: Make the change
|
|
70
|
+
|
|
71
|
+
1. Add or modify conditions in `#updateScopeNS()`
|
|
72
|
+
2. For new namespaces (e.g., MathML), add a new condition checking `originNode.name`
|
|
73
|
+
3. Ensure the namespace URI constant is correct
|
|
74
|
+
|
|
75
|
+
### Step 3: Verify
|
|
76
|
+
|
|
77
|
+
1. Build: `yarn build --scope @markuplint/astro-parser`
|
|
78
|
+
2. Add namespace test cases to `src/parser.spec.ts`
|
|
79
|
+
3. Test: `yarn test --scope @markuplint/astro-parser`
|
|
80
|
+
|
|
81
|
+
## Task: update-expression-handling
|
|
82
|
+
|
|
83
|
+
Update expression splitting or MustacheTag handling. Follow recipe #3 in `docs/maintenance.md`.
|
|
84
|
+
|
|
85
|
+
### Step 1: Understand the current logic
|
|
86
|
+
|
|
87
|
+
1. Read `src/parser.ts` — the `case 'expression'` block in `nodeize()`
|
|
88
|
+
2. Understand the splitting logic: `firstChild !== lastChild` check
|
|
89
|
+
3. Understand how opening and closing fragments are created
|
|
90
|
+
|
|
91
|
+
### Step 2: Make the change
|
|
92
|
+
|
|
93
|
+
1. Modify the splitting logic in the `expression` case
|
|
94
|
+
2. Ensure `startExpressionRaw` and `startExpressionStartLine`/`startExpressionStartCol` are correctly set
|
|
95
|
+
3. Ensure the closing fragment location is correctly calculated from `lastChild`
|
|
96
|
+
|
|
97
|
+
### Step 3: Verify
|
|
98
|
+
|
|
99
|
+
1. Build: `yarn build --scope @markuplint/astro-parser`
|
|
100
|
+
2. Test with expressions containing nested HTML (e.g., `{list.map(item => <li>{item}</li>)}`)
|
|
101
|
+
3. Test: `yarn test --scope @markuplint/astro-parser`
|
|
102
|
+
|
|
103
|
+
## Rules
|
|
104
|
+
|
|
105
|
+
1. **Delegate tokenization to astro-eslint-parser** — never parse Astro syntax manually; always use `astroParse()`.
|
|
106
|
+
2. **Use `potentialName` for attribute mapping** — when a directive maps to a standard HTML attribute, set `potentialName` instead of modifying the attribute name.
|
|
107
|
+
3. **Test with `nodeListToDebugMaps`** — all parser tests should use `nodeListToDebugMaps` for snapshot-style assertions that verify positions, names, and types.
|
|
108
|
+
4. **Maintain `scopeNS` state** — namespace scoping must be updated before node type dispatch in `nodeize()`.
|
|
109
|
+
5. **Add JSDoc comments** to all new public methods and properties.
|
|
@@ -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,12 @@
|
|
|
1
|
+
export function detectBlockBehavior(raw) {
|
|
2
|
+
const re = /\.+\s*(?<type>map|filter)\s*\((?:function\s*\(.[^\n\r{\u2028\u2029]*\{.*return\s*$|.+=>\s*\(?\s*)/;
|
|
3
|
+
const match = raw.match(re);
|
|
4
|
+
if (!match) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
const type = match.groups?.type === 'map' ? 'each' : 'if';
|
|
8
|
+
return {
|
|
9
|
+
type,
|
|
10
|
+
expression: raw,
|
|
11
|
+
};
|
|
12
|
+
}
|
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
|
@@ -5,6 +5,13 @@ import { Parser } from '@markuplint/parser-utils';
|
|
|
5
5
|
type State = {
|
|
6
6
|
scopeNS: string;
|
|
7
7
|
};
|
|
8
|
+
/**
|
|
9
|
+
* Parser implementation for Astro component templates.
|
|
10
|
+
* Extends the base Parser to handle Astro-specific syntax including frontmatter blocks,
|
|
11
|
+
* expression containers (`{}`), component/element/fragment types, Astro directives
|
|
12
|
+
* (e.g., `class:list`, `set:html`), shorthand attributes, and namespace-aware
|
|
13
|
+
* element resolution (XHTML vs SVG).
|
|
14
|
+
*/
|
|
8
15
|
declare class AstroParser extends Parser<Node, State> {
|
|
9
16
|
#private;
|
|
10
17
|
constructor();
|
|
@@ -12,10 +19,46 @@ declare class AstroParser extends Parser<Node, State> {
|
|
|
12
19
|
ast: Node[];
|
|
13
20
|
isFragment: boolean;
|
|
14
21
|
};
|
|
22
|
+
/**
|
|
23
|
+
* Converts an Astro AST node into markuplint node tree items.
|
|
24
|
+
* Handles frontmatter, doctype, text, comment, component/element/fragment,
|
|
25
|
+
* and expression nodes. Manages namespace scoping for SVG elements.
|
|
26
|
+
*
|
|
27
|
+
* @param originNode - The Astro AST node to convert
|
|
28
|
+
* @param parentNode - The parent node in the markuplint tree, or null for root nodes
|
|
29
|
+
* @param depth - The nesting depth of the node
|
|
30
|
+
* @returns An array of markuplint node tree items
|
|
31
|
+
*/
|
|
15
32
|
nodeize(originNode: Node, parentNode: MLASTParentNode | null, depth: number): readonly MLASTNodeTreeItem[];
|
|
16
33
|
afterFlattenNodes(nodeList: readonly MLASTNodeTreeItem[]): readonly MLASTNodeTreeItem[];
|
|
34
|
+
/**
|
|
35
|
+
* Visits an element token by first parsing the raw HTML fragment to extract
|
|
36
|
+
* the start tag, then delegating to the base visitElement with Astro-specific
|
|
37
|
+
* options including namespace scoping and nameless fragment support.
|
|
38
|
+
*
|
|
39
|
+
* @param token - The child token representing the element
|
|
40
|
+
* @param childNodes - The child Astro AST nodes within the element
|
|
41
|
+
* @returns An array of markuplint node tree items
|
|
42
|
+
*/
|
|
17
43
|
visitElement(token: ChildToken, childNodes: readonly Node[]): readonly MLASTNodeTreeItem[];
|
|
44
|
+
/**
|
|
45
|
+
* Visits child nodes and verifies that no sibling nodes with differing
|
|
46
|
+
* hierarchy levels are produced. Throws a ParserError if unexpected
|
|
47
|
+
* sibling nodes are discovered.
|
|
48
|
+
*
|
|
49
|
+
* @param children - The child Astro AST nodes to visit
|
|
50
|
+
* @param parentNode - The parent node in the markuplint tree
|
|
51
|
+
* @returns An empty array (all children are attached via the visitor)
|
|
52
|
+
*/
|
|
18
53
|
visitChildren(children: readonly Node[], parentNode: MLASTParentNode | null): never[];
|
|
54
|
+
/**
|
|
55
|
+
* Visits an attribute token, handling Astro-specific syntax including
|
|
56
|
+
* curly-brace expression values, shorthand attributes (`{name}`),
|
|
57
|
+
* and template directives (e.g., `class:list`, `set:html`).
|
|
58
|
+
*
|
|
59
|
+
* @param token - The token representing the attribute
|
|
60
|
+
* @returns The parsed attribute node with Astro-specific metadata
|
|
61
|
+
*/
|
|
19
62
|
visitAttr(token: Token): (import("@markuplint/ml-ast").MLASTSpreadAttr & {
|
|
20
63
|
__rightText?: string;
|
|
21
64
|
}) | {
|
package/lib/parser.js
CHANGED
|
@@ -6,6 +6,13 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
6
6
|
var _AstroParser_instances, _AstroParser_updateScopeNS;
|
|
7
7
|
import { AttrState, Parser, ParserError } from '@markuplint/parser-utils';
|
|
8
8
|
import { astroParse } from './astro-parser.js';
|
|
9
|
+
/**
|
|
10
|
+
* Parser implementation for Astro component templates.
|
|
11
|
+
* Extends the base Parser to handle Astro-specific syntax including frontmatter blocks,
|
|
12
|
+
* expression containers (`{}`), component/element/fragment types, Astro directives
|
|
13
|
+
* (e.g., `class:list`, `set:html`), shorthand attributes, and namespace-aware
|
|
14
|
+
* element resolution (XHTML vs SVG).
|
|
15
|
+
*/
|
|
9
16
|
class AstroParser extends Parser {
|
|
10
17
|
constructor() {
|
|
11
18
|
super({
|
|
@@ -23,6 +30,16 @@ class AstroParser extends Parser {
|
|
|
23
30
|
isFragment: true,
|
|
24
31
|
};
|
|
25
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Converts an Astro AST node into markuplint node tree items.
|
|
35
|
+
* Handles frontmatter, doctype, text, comment, component/element/fragment,
|
|
36
|
+
* and expression nodes. Manages namespace scoping for SVG elements.
|
|
37
|
+
*
|
|
38
|
+
* @param originNode - The Astro AST node to convert
|
|
39
|
+
* @param parentNode - The parent node in the markuplint tree, or null for root nodes
|
|
40
|
+
* @param depth - The nesting depth of the node
|
|
41
|
+
* @returns An array of markuplint node tree items
|
|
42
|
+
*/
|
|
26
43
|
nodeize(
|
|
27
44
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
28
45
|
originNode, parentNode, depth) {
|
|
@@ -125,6 +142,15 @@ class AstroParser extends Parser {
|
|
|
125
142
|
exposeInvalidNode: false,
|
|
126
143
|
});
|
|
127
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Visits an element token by first parsing the raw HTML fragment to extract
|
|
147
|
+
* the start tag, then delegating to the base visitElement with Astro-specific
|
|
148
|
+
* options including namespace scoping and nameless fragment support.
|
|
149
|
+
*
|
|
150
|
+
* @param token - The child token representing the element
|
|
151
|
+
* @param childNodes - The child Astro AST nodes within the element
|
|
152
|
+
* @returns An array of markuplint node tree items
|
|
153
|
+
*/
|
|
128
154
|
visitElement(token,
|
|
129
155
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
130
156
|
childNodes) {
|
|
@@ -154,6 +180,15 @@ class AstroParser extends Parser {
|
|
|
154
180
|
},
|
|
155
181
|
});
|
|
156
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* Visits child nodes and verifies that no sibling nodes with differing
|
|
185
|
+
* hierarchy levels are produced. Throws a ParserError if unexpected
|
|
186
|
+
* sibling nodes are discovered.
|
|
187
|
+
*
|
|
188
|
+
* @param children - The child Astro AST nodes to visit
|
|
189
|
+
* @param parentNode - The parent node in the markuplint tree
|
|
190
|
+
* @returns An empty array (all children are attached via the visitor)
|
|
191
|
+
*/
|
|
157
192
|
visitChildren(
|
|
158
193
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
159
194
|
children, parentNode) {
|
|
@@ -163,6 +198,14 @@ class AstroParser extends Parser {
|
|
|
163
198
|
}
|
|
164
199
|
return [];
|
|
165
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Visits an attribute token, handling Astro-specific syntax including
|
|
203
|
+
* curly-brace expression values, shorthand attributes (`{name}`),
|
|
204
|
+
* and template directives (e.g., `class:list`, `set:html`).
|
|
205
|
+
*
|
|
206
|
+
* @param token - The token representing the attribute
|
|
207
|
+
* @returns The parsed attribute node with Astro-specific metadata
|
|
208
|
+
*/
|
|
166
209
|
visitAttr(token) {
|
|
167
210
|
const attr = super.visitAttr(token, {
|
|
168
211
|
quoteSet: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markuplint/astro-parser",
|
|
3
|
-
"version": "4.6.
|
|
3
|
+
"version": "4.6.23",
|
|
4
4
|
"description": "astro parser for markuplint",
|
|
5
5
|
"repository": "git@github.com:markuplint/markuplint.git",
|
|
6
6
|
"author": "Yusuke Hirao <yusukehirao@me.com>",
|
|
@@ -21,12 +21,12 @@
|
|
|
21
21
|
"clean": "tsc --build --clean tsconfig.build.json"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@markuplint/ml-ast": "4.4.
|
|
25
|
-
"@markuplint/parser-utils": "4.8.
|
|
24
|
+
"@markuplint/ml-ast": "4.4.11",
|
|
25
|
+
"@markuplint/parser-utils": "4.8.11",
|
|
26
26
|
"astro-eslint-parser": "1.2.2"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@astrojs/compiler": "2.
|
|
29
|
+
"@astrojs/compiler": "2.13.1"
|
|
30
30
|
},
|
|
31
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "193ee7c1262bbed95424e38efdf1a8e56ff049f4"
|
|
32
32
|
}
|