@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,203 +0,0 @@
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`、`client:load`)、ショートハンド属性(`{prop}`)など、Astro 固有の構文を処理します。
6
-
7
- ## ディレクトリ構成
8
-
9
- ```
10
- src/
11
- ├── index.ts — parser インスタンスを再エクスポート
12
- ├── parser.ts — Parser<Node> を拡張する AstroParser クラス
13
- ├── astro-parser.ts — astro-eslint-parser ラッパーと型の再エクスポート
14
- ├── detect-block-behavior.ts — .map()/.filter() のブロック動作検出
15
- ├── component-scanner.ts — pretenders 自動スキャン用コンポーネントスキャナー(サブパスエクスポート)
16
- ├── parser.spec.ts — AstroParser 統合テスト
17
- ├── astro-parser.spec.ts — astro-eslint-parser ラッパーテスト
18
- └── component-scanner.spec.ts — コンポーネントスキャナーのテスト
19
- ```
20
-
21
- ## アーキテクチャ図
22
-
23
- ```mermaid
24
- flowchart TD
25
- subgraph upstream ["上流"]
26
- mlAst["@markuplint/ml-ast\n(AST 型定義)"]
27
- parserUtils["@markuplint/parser-utils\n(抽象 Parser クラス)"]
28
- astroEslintParser["astro-eslint-parser\n(Astro トークナイザ)"]
29
- astroCompiler["@astrojs/compiler\n(AST 型定義)"]
30
- end
31
-
32
- subgraph pkg ["@markuplint/astro-parser"]
33
- astroParser["AstroParser\nextends Parser‹Node›"]
34
- astroParseFn["astroParse()\nastro-eslint-parser ラッパー"]
35
- detectBlock["detectBlockBehavior()\n.map()/.filter() 検出"]
36
- compScanner["componentScanner\n(サブパス: ./component-scanner)"]
37
- end
38
-
39
- subgraph downstream ["下流"]
40
- mlCore["@markuplint/ml-core\n(MLASTDocument → MLDOM)"]
41
- pretenders["@markuplint/pretenders\n(自動スキャン)"]
42
- end
43
-
44
- mlAst -->|"AST 型"| astroParser
45
- parserUtils -->|"Parser 基底クラス"| astroParser
46
- astroEslintParser -->|"parseTemplate()"| astroParseFn
47
- astroCompiler -->|"Node 型"| astroParseFn
48
- astroParseFn -->|"RootNode.children"| astroParser
49
- detectBlock -->|"blockBehavior"| astroParser
50
- astroParser -->|"MLASTDocument を生成"| mlCore
51
- astroParser -->|"parse()"| compScanner
52
- compScanner -->|"ComponentScanResult"| pretenders
53
- ```
54
-
55
- ## AstroParser クラス
56
-
57
- ### 継承関係
58
-
59
- ```
60
- Parser<Node> (@markuplint/parser-utils)
61
- └── AstroParser (このパッケージ)
62
- ```
63
-
64
- ### コンストラクタ
65
-
66
- コンストラクタは Astro 固有のオプションで基底 `Parser` を設定します:
67
-
68
- | オプション | 値 | 用途 |
69
- | ---------------------- | ------------ | ---------------------------------------------------------------------------- |
70
- | `endTagType` | `'xml'` | Astro は XML のように明示的な閉じタグを使用 |
71
- | `selfCloseType` | `'html+xml'` | HTML void 要素と XML スタイルの自己閉じ(`<Component />`)の両方を受け入れる |
72
- | `tagNameCaseSensitive` | `true` | コンポーネント(`<MyComp>`)と HTML 要素(`<div>`)を区別 |
73
-
74
- ### オーバーライドメソッド
75
-
76
- | メソッド | 用途 |
77
- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
78
- | `tokenize()` | `astroParse()` を呼び出して Astro AST を取得し、`{ ast: rootNode.children, isFragment: true }` を返す |
79
- | `nodeize()` | Astro AST ノードを markuplint ノードに変換。ノードタイプ(frontmatter, doctype, text, comment, element, expression)で振り分け |
80
- | `afterFlattenNodes()` | `{ exposeInvalidNode: false }` で親に委譲 |
81
- | `visitElement()` | `parseCodeFragment()` で `namelessFragment: true` として生の HTML フラグメントをパースし、終了タグ処理で親に委譲 |
82
- | `visitChildren()` | 親に委譲した後、予期しない兄弟ノードが残っていないことをアサート |
83
- | `visitAttr()` | 波括弧式の値、ショートハンド属性、テンプレートディレクティブを処理 |
84
- | `detectElementType()` | `/^[A-Z]/` パターンでコンポーネントと HTML 要素を検出(大文字始まりの名前はコンポーネント) |
85
-
86
- ## フロントマター処理
87
-
88
- Astro コンポーネントは `---` で区切られたフロントマターブロックを含むことができます:
89
-
90
- ```astro
91
- ---
92
- const name = "World";
93
- ---
94
- <div>{name}</div>
95
- ```
96
-
97
- `astro-eslint-parser` は `type: 'frontmatter'` のノードを生成します。パーサーはこれを `nodeName: 'Frontmatter'` かつ `isFragment: false` の **psblock**(疑似ブロック)に変換します。区切り文字 `---` を含むブロック全体が単一の不透明ノードとしてキャプチャされます。フロントマター内のコンテンツは HTML としてパースされません。
98
-
99
- ## 式の処理
100
-
101
- Astro の式(`{expression}`)は Astro AST で `type: 'expression'` ノードとして表現されます。パーサーはこれらを **MustacheTag** psblock ノードに変換します。
102
-
103
- ### 単純な式
104
-
105
- `{name}` のような単純な式は単一のテキスト子ノードを持ちます。式全体が `isFragment: true` の1つの MustacheTag psblock として出力されます。
106
-
107
- ### HTML を含むネストされた式
108
-
109
- 式が HTML 要素を含む場合(例: `{list.map(item => <li>{item}</li>)}`)、パーサーは複数のノードに分割します:
110
-
111
- 1. **開始式フラグメント**: `{list.map(item => ` — 子ノードを含む MustacheTag psblock。式が `.map()` または `.filter()` 呼び出しを含む場合(`detectBlockBehavior()` で検出)、開始フラグメントにはそれぞれ `blockBehavior: { type: 'each' }` または `{ type: 'if' }` が設定される
112
- 2. **ネストされた HTML 要素**: `<li>{item}</li>` — 通常の要素として処理
113
- 3. **終了式フラグメント**: `)}` — `isFragment: false` の別の MustacheTag psblock。開始フラグメントに `blockBehavior` がある場合、終了フラグメントには `blockBehavior: { type: 'end' }` が設定される
114
-
115
- 分割ロジックは式の children 配列で `firstChild !== lastChild` かどうかを確認します。該当する場合:
116
-
117
- - 式の開始から最初の子の終了までの領域が開始フラグメントになる
118
- - 最後の子の開始から式の終了までの領域が終了フラグメントになる
119
- - 間の子は開始フラグメントの psblock 内で通常通り訪問される
120
-
121
- ## 属性処理
122
-
123
- ### クォートセット
124
-
125
- `visitAttr()` メソッドは式の値用に波括弧を含むカスタムクォートセットを使用します:
126
-
127
- | 開始 | 終了 | タイプ |
128
- | ---- | ---- | -------- |
129
- | `"` | `"` | `string` |
130
- | `'` | `'` | `string` |
131
- | `{` | `}` | `script` |
132
-
133
- ### ショートハンド属性
134
-
135
- 属性トークンが `{` で始まる場合(例: `{prop}`)、パーサーは `startState: AttrState.BeforeValue` を設定し、名前のパースをスキップして直接値の抽出に進みます。結果の属性は:
136
-
137
- - `name.raw` = `''`(空)
138
- - `value.raw` = `prop`
139
- - `potentialName` = `prop`(値から推論)
140
- - `isDynamicValue` = `true`
141
-
142
- ### テンプレートディレクティブ
143
-
144
- Astro テンプレートディレクティブは `name:modifier` 構文を使用します。パーサーは正規表現 `/^([^:]+):([^:]+)$/` でこれらを検出します:
145
-
146
- | ディレクティブプレフィックス | `potentialName` | `isDirective` | 動作 |
147
- | ---------------------------- | --------------- | ------------- | ----------------------------------------------------- |
148
- | `class:` | `'class'` | `false` | 標準の `class` 属性にマッピング |
149
- | `client:` | — | `true` | Astro クライアントディレクティブ(load, idle 等) |
150
- | `server:` | — | `true` | Astro サーバーディレクティブ(defer) |
151
- | `set:` | — | `true` | コンテンツディレクティブ(html, text) |
152
- | `is:` | — | `true` | プロパティディレクティブ(inline, raw) |
153
- | `define:` | — | `true` | スタイルディレクティブ(vars) |
154
- | `transition:` | — | `true` | View Transition ディレクティブ(animate, name) |
155
- | _(その他すべて)_ | — | `true` | キャッチオール: すべての `prefix:name` パターンに適用 |
156
-
157
- `class:` プレフィックスは特別扱いで、`potentialName: 'class'` を取得するため、`class` 属性に対する markuplint ルールが適用されます。その他のコロン区切りプレフィックスは `default` ケースに該当し `isDirective: true` を取得します。これはフレームワーク固有であり標準 HTML 属性として検証すべきでないことを markuplint に伝えます。
158
-
159
- ### 動的な値
160
-
161
- 開始クォートが `{` の属性はすべて `isDynamicValue: true` を取得します。以下に適用されます:
162
-
163
- - 明示的な動的値: `prop={value}`
164
- - ショートハンド属性: `{prop}`
165
- - ネストされた式: `style={{ a: b }}`
166
-
167
- ## jsx-parser との比較
168
-
169
- | 機能 | `astro-parser` | `jsx-parser` |
170
- | ------------------------------ | -------------------------------------- | ------------------------------------------------- |
171
- | **トークナイザ** | `astro-eslint-parser` | TypeScript ESTree(`@typescript-eslint/parser`) |
172
- | **フロントマター** | サポート(`---...---` psblock) | 該当なし |
173
- | **式の構文** | `{expr}` を MustacheTag psblock として | `{expr}` を JSXExpressionContainer psblock として |
174
- | **テンプレートディレクティブ** | `class:list`、`set:html` 等 | 該当なし |
175
- | **名前空間管理** | 基底 `Parser` に委譲 | html-parser の `getNamespace()` に委譲 |
176
- | **コンポーネント検出** | `/^[A-Z]/` パターン | `/^[A-Z]/` パターン |
177
- | **自己閉じタイプ** | `html+xml` | デフォルト(XML のみ) |
178
- | **booleanish 属性** | 未設定 | `booleanish: true` |
179
- | **名前なしフラグメント** | `<>...</>` サポート | `<>...</>` サポート |
180
- | **スプレッド属性** | 基底パーサーで処理 | カスタム `visitSpreadAttr()` で IDL ルックアップ |
181
-
182
- ## バージョン互換性
183
-
184
- パースチェーンは以下に依存します:
185
-
186
- ```
187
- astro-eslint-parser → @astrojs/compiler → Astro 構文サポート
188
- ```
189
-
190
- `astro-eslint-parser` は `parseTemplate()` を提供するランタイム依存です。`@astrojs/compiler` は AST 型定義(`Node`、`RootNode`、`ElementNode` 等)にのみ使用される開発依存です。`astro-eslint-parser` を更新する際は、`@astrojs/compiler` 開発依存も `astro-eslint-parser` が内部で使用するバージョンに合わせて更新する必要があります。
191
-
192
- ## 主要ソースファイル
193
-
194
- | ファイル | 用途 |
195
- | ---------------------- | ------------------------------------------------------------------------------------------------------------- |
196
- | `parser.ts` | `AstroParser` クラス — 全オーバーライドメソッドと名前空間スコーピング |
197
- | `astro-parser.ts` | `astroParse()` ラッパー — `astro-eslint-parser` に委譲し、診断を `ParserError` に変換 |
198
- | `index.ts` | 公開 API — シングルトン `parser` インスタンスを再エクスポート |
199
- | `component-scanner.ts` | `@markuplint/pretenders` 自動スキャン用コンポーネントスキャナー(サブパスエクスポート `./component-scanner`) |
200
-
201
- ## ドキュメントマップ
202
-
203
- - [メンテナンスガイド](docs/maintenance.ja.md) -- コマンド、レシピ、トラブルシューティング
package/ARCHITECTURE.md DELETED
@@ -1,203 +0,0 @@
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`, `client:load`), and shorthand attributes (`{prop}`).
6
-
7
- ## Directory Structure
8
-
9
- ```
10
- src/
11
- ├── index.ts — Re-exports parser instance
12
- ├── parser.ts — AstroParser class extending Parser<Node>
13
- ├── astro-parser.ts — astro-eslint-parser wrapper and type re-exports
14
- ├── detect-block-behavior.ts — Detects .map()/.filter() for block behavior
15
- ├── component-scanner.ts — Component scanner for pretenders auto scan (subpath export)
16
- ├── parser.spec.ts — AstroParser integration tests
17
- ├── astro-parser.spec.ts — astro-eslint-parser wrapper tests
18
- └── component-scanner.spec.ts — Tests for component scanner
19
- ```
20
-
21
- ## Architecture Diagram
22
-
23
- ```mermaid
24
- flowchart TD
25
- subgraph upstream ["Upstream"]
26
- mlAst["@markuplint/ml-ast\n(AST types)"]
27
- parserUtils["@markuplint/parser-utils\n(Abstract Parser class)"]
28
- astroEslintParser["astro-eslint-parser\n(Astro tokenizer)"]
29
- astroCompiler["@astrojs/compiler\n(AST types)"]
30
- end
31
-
32
- subgraph pkg ["@markuplint/astro-parser"]
33
- astroParser["AstroParser\nextends Parser‹Node›"]
34
- astroParseFn["astroParse()\nastro-eslint-parser wrapper"]
35
- detectBlock["detectBlockBehavior()\n.map()/.filter() detection"]
36
- compScanner["componentScanner\n(subpath: ./component-scanner)"]
37
- end
38
-
39
- subgraph downstream ["Downstream"]
40
- mlCore["@markuplint/ml-core\n(MLASTDocument → MLDOM)"]
41
- pretenders["@markuplint/pretenders\n(auto scan)"]
42
- end
43
-
44
- mlAst -->|"AST types"| astroParser
45
- parserUtils -->|"Parser base class"| astroParser
46
- astroEslintParser -->|"parseTemplate()"| astroParseFn
47
- astroCompiler -->|"Node types"| astroParseFn
48
- astroParseFn -->|"RootNode.children"| astroParser
49
- detectBlock -->|"blockBehavior"| astroParser
50
- astroParser -->|"produces MLASTDocument"| mlCore
51
- astroParser -->|"parse()"| compScanner
52
- compScanner -->|"ComponentScanResult"| pretenders
53
- ```
54
-
55
- ## AstroParser Class
56
-
57
- ### Inheritance
58
-
59
- ```
60
- Parser<Node> (from @markuplint/parser-utils)
61
- └── AstroParser (this package)
62
- ```
63
-
64
- ### Constructor
65
-
66
- The constructor configures the base `Parser` with Astro-specific options:
67
-
68
- | Option | Value | Purpose |
69
- | ---------------------- | ------------ | ---------------------------------------------------------------------------- |
70
- | `endTagType` | `'xml'` | Astro uses explicit closing tags like XML |
71
- | `selfCloseType` | `'html+xml'` | Accepts both HTML void elements and XML-style self-closing (`<Component />`) |
72
- | `tagNameCaseSensitive` | `true` | Distinguishes components (`<MyComp>`) from HTML elements (`<div>`) |
73
-
74
- ### Override Methods
75
-
76
- | Method | Purpose |
77
- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
78
- | `tokenize()` | Calls `astroParse()` to get the Astro AST, returns `{ ast: rootNode.children, isFragment: true }` |
79
- | `nodeize()` | Converts Astro AST nodes to markuplint nodes, dispatching by node type (frontmatter, doctype, text, comment, element, expression) |
80
- | `afterFlattenNodes()` | Delegates to parent with `{ exposeInvalidNode: false }` |
81
- | `visitElement()` | Parses the raw HTML fragment via `parseCodeFragment()` with `namelessFragment: true`, then delegates to parent with end tag handling |
82
- | `visitChildren()` | Delegates to parent, then asserts no unexpected sibling nodes remain |
83
- | `visitAttr()` | Handles curly-brace expression values, shorthand attributes, and template directives |
84
- | `detectElementType()` | Detects component vs HTML element using `/^[A-Z]/` pattern (capitalized names are components) |
85
-
86
- ## Frontmatter Handling
87
-
88
- Astro components can include a frontmatter block delimited by `---`:
89
-
90
- ```astro
91
- ---
92
- const name = "World";
93
- ---
94
- <div>{name}</div>
95
- ```
96
-
97
- 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.
98
-
99
- ## Expression Handling
100
-
101
- Astro expressions (`{expression}`) are represented as `type: 'expression'` nodes in the Astro AST. The parser converts these to **MustacheTag** psblock nodes.
102
-
103
- ### Simple Expressions
104
-
105
- A simple expression like `{name}` has a single text child. The entire expression is emitted as one MustacheTag psblock with `isFragment: true`.
106
-
107
- ### Nested Expressions with HTML
108
-
109
- When an expression contains HTML elements (e.g., `{list.map(item => <li>{item}</li>)}`), the parser splits it into multiple nodes:
110
-
111
- 1. **Opening expression fragment**: `{list.map(item => ` — a MustacheTag psblock containing the child nodes. If the expression contains a `.map()` or `.filter()` call (detected by `detectBlockBehavior()`), the opening fragment receives `blockBehavior: { type: 'each' }` or `{ type: 'if' }` respectively
112
- 2. **Nested HTML elements**: `<li>{item}</li>` — processed as normal elements
113
- 3. **Closing expression fragment**: `)}` — a separate MustacheTag psblock with `isFragment: false`. If the opening fragment had a `blockBehavior`, the closing fragment receives `blockBehavior: { type: 'end' }`
114
-
115
- The splitting logic checks whether `firstChild !== lastChild` in the expression's children array. If so:
116
-
117
- - The region from the expression start to the first child's end becomes the opening fragment
118
- - The region from the last child's start to the expression end becomes the closing fragment
119
- - The children between are visited normally within the opening fragment's psblock
120
-
121
- ## Attribute Processing
122
-
123
- ### Quote Set
124
-
125
- The `visitAttr()` method uses a custom quote set that includes curly braces for expression values:
126
-
127
- | Start | End | Type |
128
- | ----- | --- | -------- |
129
- | `"` | `"` | `string` |
130
- | `'` | `'` | `string` |
131
- | `{` | `}` | `script` |
132
-
133
- ### Shorthand Attributes
134
-
135
- 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:
136
-
137
- - `name.raw` = `''` (empty)
138
- - `value.raw` = `prop`
139
- - `potentialName` = `prop` (inferred from value)
140
- - `isDynamicValue` = `true`
141
-
142
- ### Template Directives
143
-
144
- Astro template directives use the `name:modifier` syntax. The parser detects these with the regex `/^([^:]+):([^:]+)$/`:
145
-
146
- | Directive prefix | `potentialName` | `isDirective` | Behavior |
147
- | ---------------- | --------------- | ------------- | ----------------------------------------------- |
148
- | `class:` | `'class'` | `false` | Maps to standard `class` attribute |
149
- | `client:` | — | `true` | Astro client directive (load, idle, visible...) |
150
- | `server:` | — | `true` | Astro server directive (defer) |
151
- | `set:` | — | `true` | Content directive (html, text) |
152
- | `is:` | — | `true` | Property directive (inline, raw) |
153
- | `define:` | — | `true` | Style directive (vars) |
154
- | `transition:` | — | `true` | View Transition directive (animate, name) |
155
- | _(any other)_ | — | `true` | Catch-all: any `prefix:name` pattern |
156
-
157
- The `class:` prefix is special-cased: it gets `potentialName: 'class'` so markuplint rules for the `class` attribute apply. All other colon-separated prefixes hit the `default` case and get `isDirective: true`, which tells markuplint they are framework-specific and should not be validated as standard HTML attributes.
158
-
159
- ### Dynamic Values
160
-
161
- Any attribute whose start quote is `{` gets `isDynamicValue: true`. This applies to:
162
-
163
- - Explicit dynamic values: `prop={value}`
164
- - Shorthand attributes: `{prop}`
165
- - Nested expressions: `style={{ a: b }}`
166
-
167
- ## Comparison with jsx-parser
168
-
169
- | Feature | `astro-parser` | `jsx-parser` |
170
- | ------------------------- | ------------------------------- | ----------------------------------------------- |
171
- | **Tokenizer** | `astro-eslint-parser` | TypeScript ESTree (`@typescript-eslint/parser`) |
172
- | **Frontmatter** | Supported (`---...---` psblock) | Not applicable |
173
- | **Expression syntax** | `{expr}` as MustacheTag psblock | `{expr}` as JSXExpressionContainer psblock |
174
- | **Template directives** | `class:list`, `set:html`, etc. | Not applicable |
175
- | **Namespace management** | Delegates to base `Parser` | Delegates to `getNamespace()` from html-parser |
176
- | **Component detection** | `/^[A-Z]/` pattern | `/^[A-Z]/` pattern |
177
- | **Self-close type** | `html+xml` | Default (XML-only) |
178
- | **Booleanish attributes** | Not configured | `booleanish: true` |
179
- | **Nameless fragments** | `<>...</>` supported | `<>...</>` supported |
180
- | **Spread attributes** | Handled by base parser | Custom `visitSpreadAttr()` with IDL lookup |
181
-
182
- ## Version Compatibility
183
-
184
- The parsing chain depends on:
185
-
186
- ```
187
- astro-eslint-parser → @astrojs/compiler → Astro syntax support
188
- ```
189
-
190
- `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.
191
-
192
- ## Key Source Files
193
-
194
- | File | Purpose |
195
- | ---------------------- | -------------------------------------------------------------------------------------------------- |
196
- | `parser.ts` | `AstroParser` class — all override methods and namespace scoping |
197
- | `astro-parser.ts` | `astroParse()` wrapper — delegates to `astro-eslint-parser`, converts diagnostics to `ParserError` |
198
- | `index.ts` | Public API — re-exports the singleton `parser` instance |
199
- | `component-scanner.ts` | Component scanner for `@markuplint/pretenders` auto scan (subpath export `./component-scanner`) |
200
-
201
- ## Documentation Map
202
-
203
- - [Maintenance Guide](docs/maintenance.md) -- Commands, recipes, and troubleshooting
package/SKILL.md DELETED
@@ -1,133 +0,0 @@
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
- | `update-component-scanner` | Update component-scanner for pretenders auto scan |
23
-
24
- If omitted, defaults to `add-directive`.
25
-
26
- ## Reference
27
-
28
- Before executing any task, read `docs/maintenance.md` (or `docs/maintenance.ja.md`)
29
- for the full guide. The recipes there are the source of truth for procedures.
30
-
31
- Also read:
32
-
33
- - `ARCHITECTURE.md` -- Package overview, attribute processing, directive handling
34
- - `src/parser.ts` -- AstroParser class (source of truth for override methods)
35
- - `src/astro-parser.ts` -- astro-eslint-parser wrapper
36
-
37
- ## Task: add-directive
38
-
39
- Add a new Astro template directive. Follow recipe #1 in `docs/maintenance.md`.
40
-
41
- ### Step 1: Understand the directive pattern
42
-
43
- 1. Read `src/parser.ts` — the `visitAttr()` method
44
- 2. Identify the regex: `/^([^:]+):([^:]+)$/`
45
- 3. Understand the `switch (lowerCaseDirectiveName)` block
46
-
47
- ### Step 2: Add the directive case
48
-
49
- 1. Add a new `case` in the switch for the directive prefix
50
- 2. Decide whether it maps to a `potentialName` (like `class:list` → `class`) or is a pure directive (`isDirective: true`)
51
- 3. If it maps to a standard HTML attribute, set `potentialName` to the attribute name
52
- 4. If it is Astro-specific, set `isDirective = true`
53
-
54
- ### Step 3: Verify
55
-
56
- 1. Build: `yarn build --scope @markuplint/astro-parser`
57
- 2. Add test cases to `src/parser.spec.ts` using `nodeListToDebugMaps`
58
- 3. Test: `yarn test --scope @markuplint/astro-parser`
59
-
60
- ## Task: modify-namespace-scoping
61
-
62
- Modify the SVG/XHTML namespace scoping logic. Follow recipe #2 in `docs/maintenance.md`.
63
-
64
- ### Step 1: Understand the current logic
65
-
66
- 1. Namespace resolution is handled by the base `Parser` class from `@markuplint/parser-utils`
67
- 2. The Astro parser does **not** override namespace logic — there is no `#updateScopeNS()` method
68
- 3. Any namespace changes require modifications in the base `Parser` class or adding an override in `AstroParser`
69
-
70
- ### Step 2: Make the change
71
-
72
- 1. If adding namespace handling to the Astro parser, override the relevant method from the base `Parser`
73
- 2. For new namespaces (e.g., MathML), add a condition checking `originNode.name`
74
- 3. Ensure the namespace URI constant is correct
75
-
76
- ### Step 3: Verify
77
-
78
- 1. Build: `yarn build --scope @markuplint/astro-parser`
79
- 2. Add namespace test cases to `src/parser.spec.ts`
80
- 3. Test: `yarn test --scope @markuplint/astro-parser`
81
-
82
- ## Task: update-expression-handling
83
-
84
- Update expression splitting or MustacheTag handling. Follow recipe #3 in `docs/maintenance.md`.
85
-
86
- ### Step 1: Understand the current logic
87
-
88
- 1. Read `src/parser.ts` — the `case 'expression'` block in `nodeize()`
89
- 2. Understand the splitting logic: `firstChild !== lastChild` check
90
- 3. Understand how opening and closing fragments are created
91
-
92
- ### Step 2: Make the change
93
-
94
- 1. Modify the splitting logic in the `expression` case
95
- 2. Ensure `startExpressionRaw` and `startExpressionStartLine`/`startExpressionStartCol` are correctly set
96
- 3. Ensure the closing fragment location is correctly calculated from `lastChild`
97
-
98
- ### Step 3: Verify
99
-
100
- 1. Build: `yarn build --scope @markuplint/astro-parser`
101
- 2. Test with expressions containing nested HTML (e.g., `{list.map(item => <li>{item}</li>)}`)
102
- 3. Test: `yarn test --scope @markuplint/astro-parser`
103
-
104
- ## Task: update-component-scanner
105
-
106
- Update `src/component-scanner.ts` when Astro slot syntax or frontmatter handling changes.
107
-
108
- ### When to update
109
-
110
- - New slot-like syntax is added to Astro
111
- - Frontmatter delimiter handling needs to change
112
- - The `extractComponentInfo` shared logic needs a fix (also update vue-parser and svelte-parser)
113
-
114
- ### Step 1: Make the change
115
-
116
- 1. Read `src/component-scanner.ts`
117
- 2. Modify `detectSlots()` for new slot patterns, or `extractAstroFrontmatter()` for frontmatter changes
118
- 3. If modifying `extractComponentInfo()`, apply the same change to all three parsers (vue, svelte, astro)
119
-
120
- ### Step 2: Verify
121
-
122
- 1. Update tests in `src/component-scanner.spec.ts`
123
- 2. Build: `yarn build --scope @markuplint/astro-parser`
124
- 3. Test: `npx vitest run packages/@markuplint/astro-parser/src/component-scanner.spec.ts`
125
- 4. Run pretenders integration tests: `npx vitest run packages/@markuplint/pretenders`
126
-
127
- ## Rules
128
-
129
- 1. **Delegate tokenization to astro-eslint-parser** — never parse Astro syntax manually; always use `astroParse()`.
130
- 2. **Use `potentialName` for attribute mapping** — when a directive maps to a standard HTML attribute, set `potentialName` instead of modifying the attribute name.
131
- 3. **Test with `nodeListToDebugMaps`** — all parser tests should use `nodeListToDebugMaps` for snapshot-style assertions that verify positions, names, and types.
132
- 4. **Maintain `scopeNS` state** — namespace scoping must be updated before node type dispatch in `nodeize()`.
133
- 5. **Add JSDoc comments** to all new public methods and properties.