@markuplint/vue-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/SKILL.md ADDED
@@ -0,0 +1,137 @@
1
+ ---
2
+ description: Maintenance tasks for @markuplint/vue-parser
3
+ globs:
4
+ - packages/@markuplint/vue-parser/src/**/*.ts
5
+ alwaysApply: false
6
+ ---
7
+
8
+ # vue-parser-maintenance
9
+
10
+ Perform maintenance tasks for `@markuplint/vue-parser`: add Vue directives, modify element type
11
+ detection, update vue-eslint-parser version support, and fix comment injection.
12
+
13
+ ## Input
14
+
15
+ `$ARGUMENTS` specifies the task. Supported tasks:
16
+
17
+ | Task | Description |
18
+ | ------------------------------- | ------------------------------------------------------------- |
19
+ | `add-directive` | Add or modify a Vue directive in directivePatterns (vue-spec) |
20
+ | `modify-element-type-detection` | Update detectElementType() matchers |
21
+ | `update-vue-version-support` | Handle vue-eslint-parser API changes |
22
+ | `fix-comment-injection` | Fix template comment injection in flattenNodes() |
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, directive processing, and element type detection
34
+ - `src/parser.ts` -- VueParser class (source of truth for all override methods)
35
+
36
+ ## Task: add-directive
37
+
38
+ Add or modify a Vue directive in `directivePatterns` (`@markuplint/vue-spec`). Follow recipe #1 in `docs/maintenance.md`.
39
+
40
+ ### Step 1: Understand the directive
41
+
42
+ 1. Open `@markuplint/vue-spec` (`packages/@markuplint/vue-spec/src/index.ts`) and find the `directivePatterns` array
43
+ 2. Identify where in the priority chain the new directive should be processed
44
+ 3. Determine whether the directive needs `potentialName`, `isDirective`, or `isDynamicValue`
45
+
46
+ ### Step 2: Add the directive pattern
47
+
48
+ 1. Create a new regex block following the existing pattern (scoped block with destructuring)
49
+ 2. Use `attr.name.raw.match()` to extract the directive prefix and value
50
+ 3. Set the appropriate metadata:
51
+ - `potentialName` — maps the directive to an equivalent HTML attribute name
52
+ - `isDirective` — marks as a Vue-only directive with no HTML equivalent
53
+ - `isDynamicValue` — indicates the attribute value is a JavaScript expression
54
+ 4. Check if the attribute should be in `duplicatableAttrs` (e.g., class, style)
55
+
56
+ ### Step 3: Verify
57
+
58
+ 1. Build: `yarn build --scope @markuplint/vue-parser`
59
+ 2. Add test cases to `src/index.spec.ts` covering:
60
+ - The directive with its full form (e.g., `v-bind:prop`)
61
+ - The shorthand form if applicable (e.g., `:prop`)
62
+ - Edge cases with modifiers (e.g., `.stop`, `.lazy`)
63
+ 3. Test: `yarn test --scope @markuplint/vue-parser`
64
+
65
+ ## Task: modify-element-type-detection
66
+
67
+ Update `detectElementType()` matchers. Follow recipe #2 in `docs/maintenance.md`.
68
+
69
+ ### Step 1: Understand current matchers
70
+
71
+ 1. Read the `detectElementType()` method in `src/parser.ts`
72
+ 2. Review the matcher array: string literals for built-in components, regex for PascalCase
73
+
74
+ ### Step 2: Make the change
75
+
76
+ 1. To add a new built-in component: add the component name as a string to the matcher array
77
+ 2. To add a new pattern: add a regex to the matcher array
78
+ 3. Components matching any entry return `'authored'` element type
79
+
80
+ ### Step 3: Verify
81
+
82
+ 1. Build: `yarn build --scope @markuplint/vue-parser`
83
+ 2. Add test cases to the `elementType` test block in `src/index.spec.ts`
84
+ 3. Test: `yarn test --scope @markuplint/vue-parser`
85
+
86
+ ## Task: update-vue-version-support
87
+
88
+ Handle vue-eslint-parser API changes. Follow recipe #3 in `docs/maintenance.md`.
89
+
90
+ ### Step 1: Understand the change
91
+
92
+ 1. Read `src/vue-parser/index.ts` — the wrapper around vue-eslint-parser
93
+ 2. Check the vue-eslint-parser changelog for breaking changes
94
+ 3. Verify the `ASTNode` and `ASTComment` type exports still match
95
+
96
+ ### Step 2: Make the change
97
+
98
+ 1. Update `vueParse()` if the `parse()` API signature changed
99
+ 2. Update type exports (`ASTNode`, `ASTComment`, `VueTokens`) if AST types changed
100
+ 3. Update `tokenize()` in `src/parser.ts` if `templateBody` structure changed
101
+
102
+ ### Step 3: Verify
103
+
104
+ 1. Build: `yarn build --scope @markuplint/vue-parser`
105
+ 2. Test: `yarn test --scope @markuplint/vue-parser`
106
+ 3. Test with real Vue SFC files to ensure correct parsing
107
+
108
+ ## Task: fix-comment-injection
109
+
110
+ Fix template comment injection in `flattenNodes()`. Follow recipe #4 in `docs/maintenance.md`.
111
+
112
+ ### Step 1: Understand the issue
113
+
114
+ 1. Read the `flattenNodes()` method in `src/parser.ts`
115
+ 2. Understand the two-pass approach: first flatten, then inject comments
116
+ 3. Check how `this.state.comments` is populated in `tokenize()`
117
+
118
+ ### Step 2: Fix the injection logic
119
+
120
+ 1. Verify the comment range check: `lastOffset <= comment.range[0] && comment.range[1] <= node.startOffset`
121
+ 2. Verify `this.visitComment()` is called with the correct `isBogus` flag
122
+ 3. Verify `this.appendChild()` correctly attaches the comment to the parent
123
+
124
+ ### Step 3: Verify
125
+
126
+ 1. Build: `yarn build --scope @markuplint/vue-parser`
127
+ 2. Test with Vue templates containing HTML comments (`<!-- -->`), bogus comments (`<!...>`), and mixed content
128
+ 3. Test: `yarn test --scope @markuplint/vue-parser`
129
+
130
+ ## Rules
131
+
132
+ 1. **Use vue-eslint-parser** for all template parsing — never parse Vue templates manually.
133
+ 2. **Use `potentialName`** for directives that map to HTML attributes (e.g., `@click` -> `onclick`).
134
+ 3. **Use `isDirective: true`** for directives with no HTML equivalent (e.g., `v-if`, `v-for`).
135
+ 4. **Test with `nodeListToDebugMaps`** — this is the standard assertion pattern for parser tests.
136
+ 5. **Add JSDoc comments** to all new public methods and properties.
137
+ 6. **Preserve directive priority order** in `directivePatterns` — `.prop` shorthand before `v-bind` with modifiers before `v-bind`/`:` before `v-on`/`@` before `v-model` before `v-slot`/`#` before generic `v-`.
@@ -0,0 +1,203 @@
1
+ # メンテナンスガイド
2
+
3
+ ## コマンド
4
+
5
+ | コマンド | 説明 |
6
+ | ------------------------------------------- | ---------------------- |
7
+ | `yarn build --scope @markuplint/vue-parser` | このパッケージをビルド |
8
+ | `yarn dev --scope @markuplint/vue-parser` | ウォッチモードでビルド |
9
+ | `yarn clean --scope @markuplint/vue-parser` | ビルド成果物を削除 |
10
+ | `yarn test --scope @markuplint/vue-parser` | テストを実行 |
11
+
12
+ ## テスト
13
+
14
+ テストファイルは `*.spec.ts` の命名規則に従い、`src/` ディレクトリに配置されています:
15
+
16
+ | テストファイル | カバレッジ |
17
+ | --------------- | ------------------------------------------------------------------------------ |
18
+ | `index.spec.ts` | VueParser 統合テスト(パース、ディレクティブ、名前空間、要素タイプ、コメント) |
19
+
20
+ 主なテストパターンでは `nodeListToDebugMaps` を使用したスナップショット形式のアサーションを行います:
21
+
22
+ ```ts
23
+ import { nodeListToDebugMaps } from '@markuplint/parser-utils';
24
+ import { parser } from '@markuplint/vue-parser';
25
+
26
+ const doc = parser.parse('<template><div class="foo">text</div></template>');
27
+ const debugMaps = nodeListToDebugMaps(doc.nodeList, true);
28
+ expect(debugMaps).toStrictEqual([
29
+ // 期待されるデバッグ出力
30
+ ]);
31
+ ```
32
+
33
+ 属性メタデータのテスト(ディレクティブ、potentialName、isDynamicValue):
34
+
35
+ ```ts
36
+ const doc = parser.parse('<template><div v-bind:title="val"></div></template>');
37
+ expect(doc.nodeList[0].attributes[0].potentialName).toBe('title');
38
+ expect(doc.nodeList[0].attributes[0].isDynamicValue).toBeTruthy();
39
+ ```
40
+
41
+ 要素タイプのテスト:
42
+
43
+ ```ts
44
+ const doc = parser.parse('<template><MyComponent/></template>');
45
+ expect(doc.nodeList[0].elementType).toBe('authored');
46
+ ```
47
+
48
+ ## レシピ
49
+
50
+ ### 1. Vue ディレクティブの追加・変更
51
+
52
+ 1. `@markuplint/vue-spec`(`packages/@markuplint/vue-spec/src/index.ts`)を開き、`directivePatterns` 配列を確認する
53
+ 2. 優先チェーン内の正しい位置を特定:
54
+ - `v-on` / `@`(イベントバインディング) — 最初
55
+ - `v-bind` / `:`(プロパティバインディング) — 2番目
56
+ - `v-model` — 3番目
57
+ - `v-slot` / `#` — 4番目
58
+ - 汎用 `v-*` — 最後(キャッチオール)
59
+ 3. 正規表現パターンを持つ新しいスコープブロックを作成:
60
+ ```ts
61
+ {
62
+ const [, directive, name] = attr.name.raw.match(/^(v-newdir:|shorthand)(.+)$/i) ?? [];
63
+ if (directive && name) {
64
+ return {
65
+ ...attr,
66
+ potentialName: name, // HTML 属性にマッピングする場合
67
+ isDirective: true as const, // Vue 専用の場合
68
+ isDynamicValue: true as const, // 値が JavaScript の場合
69
+ };
70
+ }
71
+ }
72
+ ```
73
+ 4. ビルド: `yarn build --scope @markuplint/vue-parser`
74
+ 5. `src/index.spec.ts` にテストを追加:
75
+ - 完全形: `v-newdir:value`
76
+ - 省略形(該当する場合)
77
+ - 修飾子付き(該当する場合)
78
+ 6. テスト: `yarn test --scope @markuplint/vue-parser`
79
+
80
+ ### 2. 要素タイプ検出の変更
81
+
82
+ 1. `src/parser.ts` を読み、`detectElementType()` メソッドを見つける
83
+ 2. マッチャー配列は以下をサポート:
84
+ - **文字列リテラル** — 完全一致(例: `'Transition'`、`'component'`、`'slot'`)
85
+ - **RegExp** — パターンマッチ(例: PascalCase 用の `/^[A-Z]/`)
86
+ 3. 新しい Vue 組み込みコンポーネントを追加するには:
87
+ ```ts
88
+ detectElementType(nodeName: string) {
89
+ return super.detectElementType(nodeName, [
90
+ // Built-in components
91
+ 'Transition',
92
+ 'TransitionGroup',
93
+ 'KeepAlive',
94
+ 'Teleport',
95
+ 'Suspense',
96
+ 'NewBuiltIn', // <-- ここに追加
97
+ // Special elements
98
+ 'component',
99
+ 'slot',
100
+ // Backward compatibility
101
+ /^[A-Z]/,
102
+ ]);
103
+ }
104
+ ```
105
+ 4. ビルドとテスト: `yarn build --scope @markuplint/vue-parser && yarn test --scope @markuplint/vue-parser`
106
+ 5. `src/index.spec.ts` の `elementType` テストブロックにテストケースを追加
107
+
108
+ ### 3. vue-eslint-parser バージョンサポートの更新
109
+
110
+ 1. `src/vue-parser/index.ts` を読む — vue-eslint-parser のラッパー
111
+ 2. vue-eslint-parser のリリースノートで破壊的変更を確認
112
+ 3. 主な統合ポイント:
113
+ - `VueESLintParser.parse(vueTemplate, { parser: false })` — メインのパース呼び出し
114
+ - `ast.templateBody?.children` — テンプレートの子ノード
115
+ - `ast.templateBody?.comments` — テンプレートのコメント
116
+ - `VueESLintParser.AST.VElement` / `VText` / `VExpressionContainer` — ノードタイプ
117
+ 4. AST 型が変更された場合、型エクスポートを更新:
118
+ ```ts
119
+ export type ASTNode =
120
+ | VueESLintParser.AST.VElement
121
+ | VueESLintParser.AST.VText
122
+ | VueESLintParser.AST.VExpressionContainer;
123
+ ```
124
+ 5. ビルドとテスト: `yarn build --scope @markuplint/vue-parser && yarn test --scope @markuplint/vue-parser`
125
+
126
+ ### 4. テンプレートコメント注入の修正
127
+
128
+ 1. `src/parser.ts` の `flattenNodes()` メソッドを読む
129
+ 2. コメント注入のロジック:
130
+ - コメントは `tokenize()` 中に `this.state.comments` に格納される
131
+ - フラット化中、隣接するノードペアごとにコメントがその間にあるか確認
132
+ - 範囲チェック: `lastOffset <= comment.range[0] && comment.range[1] <= node.startOffset`
133
+ 3. よくある問題:
134
+ - コメントが注入されない: 範囲チェックがギャップを正しくカバーしているか確認
135
+ - コメントが間違った位置にある: `lastOffset` の計算を確認(`prevNode?.endOffset ?? node.parentNode?.endOffset ?? 0`)
136
+ - ボーガスコメント検出: `betweenComment.type === 'HTMLBogusComment'` を検証
137
+ 4. ビルドとテスト: `yarn build --scope @markuplint/vue-parser && yarn test --scope @markuplint/vue-parser`
138
+
139
+ ## 上流影響チェックリスト
140
+
141
+ 上流パッケージへの変更がこのパッケージに影響を与える可能性があります:
142
+
143
+ | パッケージ | 影響 |
144
+ | -------------------------- | ------------------------------------------------------------------------ |
145
+ | `@markuplint/parser-utils` | 基底 `Parser` クラスの変更が全オーバーライドメソッドに影響する可能性あり |
146
+ | `@markuplint/ml-ast` | AST 型の変更が nodeize() の戻り値の型の更新を必要とする可能性あり |
147
+ | `vue-eslint-parser` | AST 構造の変更が tokenize()、nodeize()、型の更新を必要とする可能性あり |
148
+
149
+ 上流パッケージが変更された場合、以下を実行:
150
+
151
+ ```shell
152
+ yarn test --scope @markuplint/vue-parser
153
+ ```
154
+
155
+ ## トラブルシューティング
156
+
157
+ ### Vue ディレクティブが認識されない
158
+
159
+ **症状:** `v-custom` のような Vue ディレクティブが `isDirective: true` としてマークされず、通常の HTML 属性として扱われる。
160
+
161
+ **原因:** `directivePatterns`(`@markuplint/vue-spec` で定義)でディレクティブパターンがマッチしないか、新しいパターンが汎用 `v-*` キャッチオールの後に配置されている。
162
+
163
+ **解決策:**
164
+
165
+ 1. ディレクティブブロックの正規表現パターンを確認 — 完全な属性名にマッチすることを確認
166
+ 2. パターンが `directivePatterns` 配列内の汎用 `v-*` キャッチオールの前に配置されていることを確認
167
+ 3. 返却オブジェクトに `isDirective: true as const` が含まれていることを検証
168
+
169
+ ### テンプレートコメントが AST に含まれていない
170
+
171
+ **症状:** Vue テンプレート内の HTML コメント(`<!-- ... -->`)がパースされたノードリストに存在しない。
172
+
173
+ **原因:** `flattenNodes()` でコメントが注入されていないか、`tokenize()` でキャプチャされていない。
174
+
175
+ **解決策:**
176
+
177
+ 1. `tokenize()` を確認 — `ast.templateBody?.comments` が `this.state.comments` に格納されていることを検証
178
+ 2. `flattenNodes()` を確認 — 範囲チェックロジックが隣接ノード間のコメントを見つけていることを検証
179
+ 3. 特定のコメント位置でテストケースを追加し、`nodeListToDebugMaps` 出力を確認
180
+
181
+ ### PascalCase コンポーネントが 'authored' として検出されない
182
+
183
+ **症状:** `<MyComponent>` のようなコンポーネントが `'authored'` ではなく `elementType: 'html'` となる。
184
+
185
+ **原因:** `detectElementType()` 内の `/^[A-Z]/` 正規表現がマッチしていないか、マッチャー配列が誤って構成されている。
186
+
187
+ **解決策:**
188
+
189
+ 1. コンポーネント名が大文字で始まることを確認
190
+ 2. `/^[A-Z]/` 正規表現がマッチャー配列に存在することを検証
191
+ 3. `<component>` や `<slot>` のような小文字の Vue 組み込みは正規表現ではなく文字列でマッチされることに注意
192
+
193
+ ### SyntaxError が正しく報告されない
194
+
195
+ **症状:** Vue テンプレートの構文エラーが `ParserError` を生成する代わりにプロセスをクラッシュさせる。
196
+
197
+ **原因:** `parseError()` メソッドがエラーをキャッチしていないか、エラーオブジェクトに `lineNumber`/`column` プロパティがない。
198
+
199
+ **解決策:**
200
+
201
+ 1. `parseError()` を確認 — `instanceof SyntaxError` と `'lineNumber' in error` のチェックを検証
202
+ 2. vue-eslint-parser のエラーには `lineNumber`(1ベース)と `column`(0ベース)が含まれる
203
+ 3. フォールバックの `super.parseError(error)` は非 SyntaxError ケースを処理する
@@ -0,0 +1,203 @@
1
+ # Maintenance Guide
2
+
3
+ ## Commands
4
+
5
+ | Command | Description |
6
+ | ------------------------------------------- | ---------------------- |
7
+ | `yarn build --scope @markuplint/vue-parser` | Build this package |
8
+ | `yarn dev --scope @markuplint/vue-parser` | Watch mode build |
9
+ | `yarn clean --scope @markuplint/vue-parser` | Remove build artifacts |
10
+ | `yarn test --scope @markuplint/vue-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
+ | `index.spec.ts` | VueParser integration tests (parsing, directives, namespaces, element types, comments) |
19
+
20
+ The primary testing pattern uses `nodeListToDebugMaps` for snapshot-style assertions:
21
+
22
+ ```ts
23
+ import { nodeListToDebugMaps } from '@markuplint/parser-utils';
24
+ import { parser } from '@markuplint/vue-parser';
25
+
26
+ const doc = parser.parse('<template><div class="foo">text</div></template>');
27
+ const debugMaps = nodeListToDebugMaps(doc.nodeList, true);
28
+ expect(debugMaps).toStrictEqual([
29
+ // expected debug output
30
+ ]);
31
+ ```
32
+
33
+ To test attribute metadata (directives, potentialName, isDynamicValue):
34
+
35
+ ```ts
36
+ const doc = parser.parse('<template><div v-bind:title="val"></div></template>');
37
+ expect(doc.nodeList[0].attributes[0].potentialName).toBe('title');
38
+ expect(doc.nodeList[0].attributes[0].isDynamicValue).toBeTruthy();
39
+ ```
40
+
41
+ To test element types:
42
+
43
+ ```ts
44
+ const doc = parser.parse('<template><MyComponent/></template>');
45
+ expect(doc.nodeList[0].elementType).toBe('authored');
46
+ ```
47
+
48
+ ## Recipes
49
+
50
+ ### 1. Adding or Modifying a Vue Directive
51
+
52
+ 1. Open `@markuplint/vue-spec` (`packages/@markuplint/vue-spec/src/index.ts`) and find the `directivePatterns` array
53
+ 2. Identify the correct position in the priority chain:
54
+ - `v-on` / `@` (event binding) — first
55
+ - `v-bind` / `:` (property binding) — second
56
+ - `v-model` — third
57
+ - `v-slot` / `#` — fourth
58
+ - Generic `v-*` — last (catch-all)
59
+ 3. Create a new scoped block with a regex pattern:
60
+ ```ts
61
+ {
62
+ const [, directive, name] = attr.name.raw.match(/^(v-newdir:|shorthand)(.+)$/i) ?? [];
63
+ if (directive && name) {
64
+ return {
65
+ ...attr,
66
+ potentialName: name, // if it maps to an HTML attribute
67
+ isDirective: true as const, // if it's Vue-only
68
+ isDynamicValue: true as const, // if value is JavaScript
69
+ };
70
+ }
71
+ }
72
+ ```
73
+ 4. Build: `yarn build --scope @markuplint/vue-parser`
74
+ 5. Add tests to `src/index.spec.ts`:
75
+ - Full form: `v-newdir:value`
76
+ - Shorthand form if applicable
77
+ - With modifiers if applicable
78
+ 6. Test: `yarn test --scope @markuplint/vue-parser`
79
+
80
+ ### 2. Modifying Element Type Detection
81
+
82
+ 1. Read `src/parser.ts` and find the `detectElementType()` method
83
+ 2. The matcher array supports:
84
+ - **String literals** — exact match (e.g., `'Transition'`, `'component'`, `'slot'`)
85
+ - **RegExp** — pattern match (e.g., `/^[A-Z]/` for PascalCase)
86
+ 3. To add a new Vue built-in component:
87
+ ```ts
88
+ detectElementType(nodeName: string) {
89
+ return super.detectElementType(nodeName, [
90
+ // Built-in components
91
+ 'Transition',
92
+ 'TransitionGroup',
93
+ 'KeepAlive',
94
+ 'Teleport',
95
+ 'Suspense',
96
+ 'NewBuiltIn', // <-- add here
97
+ // Special elements
98
+ 'component',
99
+ 'slot',
100
+ // Backward compatibility
101
+ /^[A-Z]/,
102
+ ]);
103
+ }
104
+ ```
105
+ 4. Build and test: `yarn build --scope @markuplint/vue-parser && yarn test --scope @markuplint/vue-parser`
106
+ 5. Add test cases to the `elementType` test block in `src/index.spec.ts`
107
+
108
+ ### 3. Updating vue-eslint-parser Version Support
109
+
110
+ 1. Read `src/vue-parser/index.ts` — this is the wrapper around vue-eslint-parser
111
+ 2. Check the vue-eslint-parser release notes for breaking changes
112
+ 3. Key integration points:
113
+ - `VueESLintParser.parse(vueTemplate, { parser: false })` — main parse call
114
+ - `ast.templateBody?.children` — template child nodes
115
+ - `ast.templateBody?.comments` — template comments
116
+ - `VueESLintParser.AST.VElement` / `VText` / `VExpressionContainer` — node types
117
+ 4. Update type exports if AST types changed:
118
+ ```ts
119
+ export type ASTNode =
120
+ | VueESLintParser.AST.VElement
121
+ | VueESLintParser.AST.VText
122
+ | VueESLintParser.AST.VExpressionContainer;
123
+ ```
124
+ 5. Build and test: `yarn build --scope @markuplint/vue-parser && yarn test --scope @markuplint/vue-parser`
125
+
126
+ ### 4. Fixing Template Comment Injection
127
+
128
+ 1. Read the `flattenNodes()` method in `src/parser.ts`
129
+ 2. The comment injection logic:
130
+ - Comments are stored in `this.state.comments` during `tokenize()`
131
+ - During flattening, for each pair of adjacent nodes, the method checks if a comment falls between them
132
+ - The range check: `lastOffset <= comment.range[0] && comment.range[1] <= node.startOffset`
133
+ 3. Common issues:
134
+ - Comment not injected: check that the range check covers the gap correctly
135
+ - Comment at wrong position: check `lastOffset` calculation (`prevNode?.endOffset ?? node.parentNode?.endOffset ?? 0`)
136
+ - Bogus comment detection: verify `betweenComment.type === 'HTMLBogusComment'`
137
+ 4. Build and test: `yarn build --scope @markuplint/vue-parser && yarn test --scope @markuplint/vue-parser`
138
+
139
+ ## Upstream Impact Checklist
140
+
141
+ Changes to upstream packages can affect this package:
142
+
143
+ | Package | Impact |
144
+ | -------------------------- | ----------------------------------------------------------------------------- |
145
+ | `@markuplint/parser-utils` | Base `Parser` class changes may affect all override methods |
146
+ | `@markuplint/ml-ast` | AST type changes may require updates to nodeize() return types |
147
+ | `vue-eslint-parser` | AST structure changes may require updates to tokenize(), nodeize(), and types |
148
+
149
+ When upstream packages change, run:
150
+
151
+ ```shell
152
+ yarn test --scope @markuplint/vue-parser
153
+ ```
154
+
155
+ ## Troubleshooting
156
+
157
+ ### Vue directive is not recognized
158
+
159
+ **Symptom:** A Vue directive like `v-custom` is treated as a regular HTML attribute instead of being marked as `isDirective: true`.
160
+
161
+ **Cause:** The directive pattern does not match in `directivePatterns` (defined in `@markuplint/vue-spec`), or the new pattern is placed after the generic `v-*` catch-all.
162
+
163
+ **Solution:**
164
+
165
+ 1. Check the regex pattern in your directive block — ensure it matches the full attribute name
166
+ 2. Ensure the pattern is placed before the generic `v-*` catch-all in the `directivePatterns` array
167
+ 3. Verify the return object includes `isDirective: true as const`
168
+
169
+ ### Template comments are missing from the AST
170
+
171
+ **Symptom:** HTML comments (`<!-- ... -->`) in the Vue template are not present in the parsed node list.
172
+
173
+ **Cause:** The comments are not being injected during `flattenNodes()`, or they are not captured during `tokenize()`.
174
+
175
+ **Solution:**
176
+
177
+ 1. Check `tokenize()` — verify `ast.templateBody?.comments` is being stored in `this.state.comments`
178
+ 2. Check `flattenNodes()` — verify the range check logic finds the comment between adjacent nodes
179
+ 3. Add a test case with the specific comment position and check `nodeListToDebugMaps` output
180
+
181
+ ### PascalCase component not detected as 'authored'
182
+
183
+ **Symptom:** A component like `<MyComponent>` has `elementType: 'html'` instead of `'authored'`.
184
+
185
+ **Cause:** The `/^[A-Z]/` regex in `detectElementType()` is not matching, or the matcher array is misconfigured.
186
+
187
+ **Solution:**
188
+
189
+ 1. Check that the component name starts with an uppercase letter
190
+ 2. Verify the `/^[A-Z]/` regex is still present in the matcher array
191
+ 3. Note that lowercase Vue built-ins like `<component>` and `<slot>` are matched by string, not regex
192
+
193
+ ### SyntaxError not properly reported
194
+
195
+ **Symptom:** Vue template syntax errors crash the process instead of producing a `ParserError`.
196
+
197
+ **Cause:** The `parseError()` method is not catching the error, or the error object does not have `lineNumber`/`column` properties.
198
+
199
+ **Solution:**
200
+
201
+ 1. Check `parseError()` — verify the `instanceof SyntaxError` and `'lineNumber' in error` checks
202
+ 2. vue-eslint-parser errors include `lineNumber` (1-based) and `column` (0-based)
203
+ 3. The fallback `super.parseError(error)` handles non-SyntaxError cases
package/lib/index.d.ts CHANGED
@@ -1 +1,7 @@
1
+ /**
2
+ * @module
3
+ * Vue Single File Component (SFC) template parser for markuplint. Provides a parser
4
+ * that transforms Vue template syntax into markuplint's AST using the vue-eslint-parser,
5
+ * handling Vue-specific directives such as `v-bind`, `v-on`, `v-model`, and `v-slot`.
6
+ */
1
7
  export { parser } from './parser.js';
package/lib/index.js CHANGED
@@ -1 +1,7 @@
1
+ /**
2
+ * @module
3
+ * Vue Single File Component (SFC) template parser for markuplint. Provides a parser
4
+ * that transforms Vue template syntax into markuplint's AST using the vue-eslint-parser,
5
+ * handling Vue-specific directives such as `v-bind`, `v-on`, `v-model`, and `v-slot`.
6
+ */
1
7
  export { parser } from './parser.js';
package/lib/parser.d.ts CHANGED
@@ -1,26 +1,43 @@
1
1
  import type { ASTNode, ASTComment } from './vue-parser/index.js';
2
2
  import type { MLASTParentNode, MLASTNodeTreeItem } from '@markuplint/ml-ast';
3
- import type { Token } from '@markuplint/parser-utils';
4
3
  import { ParserError, Parser } from '@markuplint/parser-utils';
5
4
  type State = {
6
5
  comments: readonly ASTComment[];
7
6
  };
7
+ /**
8
+ * Parser implementation for Vue SFC templates.
9
+ * Extends the base Parser to handle Vue elements, text nodes, expression containers,
10
+ * directives (`v-bind`, `v-on`, `v-model`, `v-slot`), and template comments.
11
+ * Recognizes Vue built-in components and PascalCase user components.
12
+ */
8
13
  declare class VueParser extends Parser<ASTNode, State> {
9
- readonly duplicatableAttrs: Set<string>;
10
14
  constructor();
11
15
  tokenize(): {
12
- ast: (import("vue-eslint-parser/ast/nodes").VElement | import("vue-eslint-parser/ast/nodes").VText | import("vue-eslint-parser/ast/nodes").VExpressionContainer)[];
13
- isFragment: boolean;
16
+ readonly ast: ASTNode[];
17
+ readonly isFragment: boolean;
14
18
  };
15
19
  parseError(error: any): ParserError;
20
+ /**
21
+ * Converts a Vue AST node into markuplint node tree items.
22
+ * Handles VText (text nodes), VExpressionContainer (template expressions),
23
+ * and VElement (elements with start/end tags and children).
24
+ *
25
+ * @param originNode - The Vue AST node to convert
26
+ * @param parentNode - The parent node in the markuplint tree, or null for root nodes
27
+ * @param depth - The nesting depth of the node
28
+ * @returns An array of markuplint node tree items
29
+ */
16
30
  nodeize(originNode: ASTNode, parentNode: MLASTParentNode | null, depth: number): readonly MLASTNodeTreeItem[];
31
+ /**
32
+ * Extends the base flattening to inject Vue template comments between sibling nodes.
33
+ * Comments from the vue-eslint-parser are inserted at the correct positions
34
+ * based on their source offsets relative to adjacent nodes.
35
+ *
36
+ * @param nodeTree - The hierarchical node tree to flatten
37
+ * @returns A flat list of nodes including interleaved comments
38
+ */
17
39
  flattenNodes(nodeTree: readonly MLASTNodeTreeItem[]): MLASTNodeTreeItem[];
18
40
  afterFlattenNodes(nodeList: readonly MLASTNodeTreeItem[]): readonly MLASTNodeTreeItem[];
19
- visitAttr(token: Token): (import("@markuplint/ml-ast").MLASTHTMLAttr & {
20
- __rightText?: string;
21
- }) | (import("@markuplint/ml-ast").MLASTSpreadAttr & {
22
- __rightText?: string;
23
- });
24
41
  /**
25
42
  * > In SFCs, it's recommended to use `PascalCase` tag names
26
43
  * > for child components to differentiate from native HTML elements.