@markuplint/vue-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.
- package/CHANGELOG.md +12 -0
- package/lib/component-scanner.js +0 -9
- package/lib/parser.d.ts +22 -22
- package/lib/parser.js +30 -22
- package/lib/vue-parser/index.d.ts +5 -7
- package/lib/vue-parser/index.js +5 -4
- package/package.json +6 -6
- package/ARCHITECTURE.ja.md +0 -314
- package/ARCHITECTURE.md +0 -314
- package/SKILL.md +0 -161
- package/docs/maintenance.ja.md +0 -203
- package/docs/maintenance.md +0 -203
package/ARCHITECTURE.md
DELETED
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
# @markuplint/vue-parser
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
`@markuplint/vue-parser` is a Vue Single File Component (SFC) template parser for markuplint. It uses vue-eslint-parser to parse the `<template>` block of Vue SFCs into a vue-eslint-parser AST, then converts that AST into the unified markuplint AST format (`MLASTDocument`). The package handles Vue-specific directives (`v-bind`, `v-on`, `v-model`, `v-slot`), template expression containers (`{{ }}`), template comments, and PascalCase component detection.
|
|
6
|
-
|
|
7
|
-
## Directory Structure
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
src/
|
|
11
|
-
├── index.ts — Re-exports parser
|
|
12
|
-
├── parser.ts — VueParser class extending Parser<ASTNode, State>
|
|
13
|
-
├── component-scanner.ts — Component scanner for pretenders auto scan (subpath export)
|
|
14
|
-
├── index.spec.ts — Integration tests for VueParser
|
|
15
|
-
├── component-scanner.spec.ts — Tests for component scanner
|
|
16
|
-
└── vue-parser/
|
|
17
|
-
└── index.ts — vue-eslint-parser wrapper, ASTNode/ASTComment type exports
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Architecture Diagram
|
|
21
|
-
|
|
22
|
-
```mermaid
|
|
23
|
-
flowchart TD
|
|
24
|
-
subgraph upstream ["Upstream"]
|
|
25
|
-
mlAst["@markuplint/ml-ast\n(AST types)"]
|
|
26
|
-
parserUtils["@markuplint/parser-utils\n(Abstract Parser class)"]
|
|
27
|
-
vueEslintParser["vue-eslint-parser\n(Vue SFC tokenizer)"]
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
subgraph pkg ["@markuplint/vue-parser"]
|
|
31
|
-
vueParser["VueParser\nextends Parser‹ASTNode, State›"]
|
|
32
|
-
vueParseFn["vueParse()\nvue-eslint-parser wrapper"]
|
|
33
|
-
compScanner["componentScanner\n(subpath: ./component-scanner)"]
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
subgraph downstream ["Downstream"]
|
|
37
|
-
mlCore["@markuplint/ml-core\n(MLASTDocument → MLDOM)"]
|
|
38
|
-
pretenders["@markuplint/pretenders\n(auto scan)"]
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
mlAst -->|"AST types"| vueParser
|
|
42
|
-
parserUtils -->|"Parser base class"| vueParser
|
|
43
|
-
vueEslintParser -->|"parse()"| vueParseFn
|
|
44
|
-
vueParseFn -->|"ESLintProgram AST"| vueParser
|
|
45
|
-
|
|
46
|
-
vueParser -->|"produces MLASTDocument"| mlCore
|
|
47
|
-
vueParser -->|"parse()"| compScanner
|
|
48
|
-
compScanner -->|"ComponentScanResult"| pretenders
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## VueParser Class
|
|
52
|
-
|
|
53
|
-
### Inheritance
|
|
54
|
-
|
|
55
|
-
```
|
|
56
|
-
Parser<ASTNode, State> (from @markuplint/parser-utils)
|
|
57
|
-
└── VueParser (this package)
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### Constructor
|
|
61
|
-
|
|
62
|
-
The constructor configures the parser with two arguments:
|
|
63
|
-
|
|
64
|
-
| Argument | Value | Purpose |
|
|
65
|
-
| --------------- | ----------------------- | ------------------------------------------------------------------------ |
|
|
66
|
-
| `ParserOptions` | `{ endTagType: 'xml' }` | Vue templates use explicit closing tags (XML-style), not HTML void rules |
|
|
67
|
-
| Initial State | `{ comments: [] }` | Empty comments array, populated during `tokenize()` |
|
|
68
|
-
|
|
69
|
-
The `tagNameCaseSensitive` behavior is inherited from the base class and combined with Vue's `detectElementType` override to correctly handle PascalCase component names.
|
|
70
|
-
|
|
71
|
-
### State Type
|
|
72
|
-
|
|
73
|
-
The parser maintains internal state through the `State` type:
|
|
74
|
-
|
|
75
|
-
| Field | Type | Purpose |
|
|
76
|
-
| ---------- | ----------------------- | -------------------------------------------------------------------------------------------------- |
|
|
77
|
-
| `comments` | `readonly ASTComment[]` | Template comments extracted from vue-eslint-parser during tokenize, injected later in flattenNodes |
|
|
78
|
-
|
|
79
|
-
### Override Methods
|
|
80
|
-
|
|
81
|
-
| Method | Purpose |
|
|
82
|
-
| --------------------- | ---------------------------------------------------------------------------------------------- |
|
|
83
|
-
| `tokenize()` | Invokes vue-eslint-parser and extracts `templateBody.children` and comments |
|
|
84
|
-
| `parseError()` | Converts vue-eslint-parser `SyntaxError` (with `lineNumber`/`column`) into `ParserError` |
|
|
85
|
-
| `nodeize()` | Converts vue-eslint-parser AST nodes (VText, VElement, VExpressionContainer) to markuplint AST |
|
|
86
|
-
| `flattenNodes()` | Extends base flattening to inject template comments between sibling nodes |
|
|
87
|
-
| `afterFlattenNodes()` | Calls base with `exposeWhiteSpace: false`, `exposeInvalidNode: false`, `concatText: false` |
|
|
88
|
-
| `detectElementType()` | Detects PascalCase components and Vue built-in components |
|
|
89
|
-
|
|
90
|
-
> **Note:** The `visitAttr()` override has been removed. Vue directive handling (`v-bind`, `v-on`, `v-model`, `v-slot`, etc.) is now managed via `directivePatterns` in `@markuplint/vue-spec`.
|
|
91
|
-
|
|
92
|
-
### `duplicatableAttrs`
|
|
93
|
-
|
|
94
|
-
A `Set<string>` containing `'class'` and `'style'` -- attributes that may appear multiple times on a single element (via `v-bind:class` alongside `class`).
|
|
95
|
-
|
|
96
|
-
## tokenize()
|
|
97
|
-
|
|
98
|
-
The `tokenize()` method is the entry point for obtaining the vue-eslint-parser AST:
|
|
99
|
-
|
|
100
|
-
1. Calls `vueParse(this.rawCode)` which invokes `VueESLintParser.parse(vueTemplate, { parser: false })`
|
|
101
|
-
2. If `ast.templateBody?.comments` exists, stores them in `this.state.comments` for later injection
|
|
102
|
-
3. Returns `{ ast: ast.templateBody?.children ?? [], isFragment: true }`
|
|
103
|
-
|
|
104
|
-
The `parser: false` option tells vue-eslint-parser to skip `<script>` parsing (only the `<template>` block is relevant for markuplint). If the source has no `<template>` block or it is empty, `templateBody?.children` returns `undefined` and the parser receives an empty array.
|
|
105
|
-
|
|
106
|
-
## nodeize() Details
|
|
107
|
-
|
|
108
|
-
The `nodeize()` method dispatches based on the `originNode.type` field:
|
|
109
|
-
|
|
110
|
-
### VText -> visitText
|
|
111
|
-
|
|
112
|
-
Text nodes are sliced from the source using `this.sliceFragment(range[0], range[1])` and passed to the base `visitText()` method with depth and parentNode.
|
|
113
|
-
|
|
114
|
-
### VExpressionContainer -> visitPsBlock
|
|
115
|
-
|
|
116
|
-
Expression containers like `{{ expression }}` are converted to pseudo-block nodes via `visitPsBlock()`:
|
|
117
|
-
|
|
118
|
-
- `nodeName`: `'vue-expression-container'`
|
|
119
|
-
- `isFragment`: `false`
|
|
120
|
-
|
|
121
|
-
This treats Vue template expressions as opaque blocks in the markuplint AST rather than attempting to parse their JavaScript content.
|
|
122
|
-
|
|
123
|
-
### VElement -> visitElement
|
|
124
|
-
|
|
125
|
-
For element nodes, the method:
|
|
126
|
-
|
|
127
|
-
1. Slices the **start tag** token from `originNode.startTag.range`
|
|
128
|
-
2. Calls `visitElement()` with the element's `name` and `namespace`
|
|
129
|
-
3. Passes `originNode.children` as child nodes -- these are `templateBody.children` when the node represents the template root
|
|
130
|
-
4. Creates an end tag token factory (`createEndTagToken`) that returns `null` if the element is self-closing, otherwise slices from `originNode.endTag.range`
|
|
131
|
-
|
|
132
|
-
## flattenNodes()
|
|
133
|
-
|
|
134
|
-
The `flattenNodes()` method extends the base `Parser.flattenNodes()` to inject template comments:
|
|
135
|
-
|
|
136
|
-
1. Calls `super.flattenNodes(nodeTree)` to get the initial flat node list
|
|
137
|
-
2. Iterates through the node list, checking for comments between each pair of adjacent nodes
|
|
138
|
-
3. For each gap between `prevNode.endOffset` (or `parentNode.endOffset` for the first node) and `node.startOffset`, searches `this.state.comments` for a comment whose range falls within that gap
|
|
139
|
-
4. When a comment is found, creates it via `this.visitComment()` with `isBogus` set based on the comment's type (`HTMLBogusComment` vs standard)
|
|
140
|
-
5. Appends the comment to the parent node via `this.appendChild()`
|
|
141
|
-
|
|
142
|
-
This two-pass approach is necessary because vue-eslint-parser provides comments separately from the main node tree, and they must be interleaved at the correct positions.
|
|
143
|
-
|
|
144
|
-
## Directive Handling (directivePatterns in @markuplint/vue-spec)
|
|
145
|
-
|
|
146
|
-
Vue directive resolution is managed by `directivePatterns` defined in `@markuplint/vue-spec`, not by the parser itself. The spec declares patterns that the core engine uses to map directives to `potentialName`, `isDirective`, and `isDynamicValue` metadata.
|
|
147
|
-
|
|
148
|
-
> **Two-stage resolution:** Parser-level tests (`index.spec.ts`) show raw AST values where `isDynamicValue` and `isDirective` reflect only what the parser itself sets (e.g., curly-brace expressions). Core-level tests (`ml-core` and `rules`) show the final resolved values after `directivePatterns` are applied by `ml-core`'s `MLAttr` constructor. For example, `on:click` without a value shows `isDynamicValue: false` at the parser level, but resolves to `isDynamicValue: true` at the core level via the `directivePatterns` match.
|
|
149
|
-
|
|
150
|
-
### Quote Set
|
|
151
|
-
|
|
152
|
-
The base parser handles standard HTML quotes (`"`, `'`). Vue templates also use `{}` as implicit value delimiters for expression bindings, though the attribute value itself uses standard quoting.
|
|
153
|
-
|
|
154
|
-
### Vue Directive Processing
|
|
155
|
-
|
|
156
|
-
Directives are processed in priority order. The first matching pattern wins:
|
|
157
|
-
|
|
158
|
-
#### `v-on` / `@` (Event Binding)
|
|
159
|
-
|
|
160
|
-
- **Pattern**: `/^(v-on:|@)([^.]+)(?:\.([^.]+))?$/i`
|
|
161
|
-
- **Result**: `potentialName: 'on' + eventName.toLowerCase()`, `isDynamicValue: true`
|
|
162
|
-
- **Examples**:
|
|
163
|
-
- `@click` -> `potentialName: 'onclick'`
|
|
164
|
-
- `v-on:click.stop` -> `potentialName: 'onclick'`
|
|
165
|
-
- `@keydown.enter` -> `potentialName: 'onkeydown'`
|
|
166
|
-
|
|
167
|
-
#### `v-bind` / `:` (Property Binding)
|
|
168
|
-
|
|
169
|
-
- **Pattern**: `/^(v-bind:|:)([^.]+)(?:\.([^.]+))?$/i`
|
|
170
|
-
- **Result** (no modifier): `potentialName: propName`, `isDynamicValue: true`
|
|
171
|
-
- **Result** (`.attr` modifier): `potentialName: propName`, `isDynamicValue: true`
|
|
172
|
-
- **Result** (`.prop` / `.camel` / other modifiers): `isDirective: true`, `potentialName` set to normalized form
|
|
173
|
-
- **`isDuplicatable`**: If the bound property is in `duplicatableAttrs` (class, style), `isDuplicatable` is set to `true`
|
|
174
|
-
- **Examples**:
|
|
175
|
-
- `:data-attr` -> `potentialName: 'data-attr'`
|
|
176
|
-
- `v-bind:class` -> `potentialName: 'class'`, `isDuplicatable: true`
|
|
177
|
-
- `:title.attr` -> `potentialName: 'title'`
|
|
178
|
-
- `:foo.prop` -> `isDirective: true`
|
|
179
|
-
|
|
180
|
-
#### `v-model`
|
|
181
|
-
|
|
182
|
-
- **Pattern**: `/^(v-model)(?:\.([^.]+))?$/i`
|
|
183
|
-
- **Result**: `isDirective: true`
|
|
184
|
-
- **Examples**:
|
|
185
|
-
- `v-model` -> `isDirective: true`
|
|
186
|
-
- `v-model.lazy` -> `isDirective: true`
|
|
187
|
-
|
|
188
|
-
#### `v-slot` / `#` (Slot)
|
|
189
|
-
|
|
190
|
-
- **Pattern**: `/^(v-slot:|#)(.+)$/i`
|
|
191
|
-
- **Result**: `isDirective: true`, `potentialName: 'v-slot:' + slotName` (if different from raw name)
|
|
192
|
-
- **Examples**:
|
|
193
|
-
- `#header` -> `potentialName: 'v-slot:header'`, `isDirective: true`
|
|
194
|
-
- `v-slot:default` -> `isDirective: true`
|
|
195
|
-
|
|
196
|
-
#### Other `v-` Directives
|
|
197
|
-
|
|
198
|
-
- **Pattern**: Starts with `v-`
|
|
199
|
-
- **Result**: `isDirective: true`
|
|
200
|
-
- **Examples**: `v-if`, `v-for`, `v-show`, `v-else`, `v-else-if`, `v-pre`, `v-cloak`, `v-once`, `v-memo`, `v-html`, `v-text`
|
|
201
|
-
|
|
202
|
-
## Element Type Detection
|
|
203
|
-
|
|
204
|
-
The `detectElementType()` method calls `super.detectElementType(nodeName, matchers)` with an array of matchers for Vue-specific component detection:
|
|
205
|
-
|
|
206
|
-
| Matcher | Type | Matches |
|
|
207
|
-
| ------------------- | ------ | ------------------------------------------- |
|
|
208
|
-
| `'Transition'` | String | Vue built-in `<Transition>` component |
|
|
209
|
-
| `'TransitionGroup'` | String | Vue built-in `<TransitionGroup>` component |
|
|
210
|
-
| `'KeepAlive'` | String | Vue built-in `<KeepAlive>` component |
|
|
211
|
-
| `'Teleport'` | String | Vue built-in `<Teleport>` component |
|
|
212
|
-
| `'Suspense'` | String | Vue built-in `<Suspense>` component |
|
|
213
|
-
| `'component'` | String | Vue special element `<component :is="...">` |
|
|
214
|
-
| `'slot'` | String | Vue special element `<slot>` |
|
|
215
|
-
| `/^[A-Z]/` | RegExp | Any PascalCase tag name (user components) |
|
|
216
|
-
|
|
217
|
-
When a tag name matches any of these, `detectElementType()` returns `'authored'` (indicating a component). Otherwise, standard HTML element detection applies:
|
|
218
|
-
|
|
219
|
-
- `div`, `span`, `p` etc. -> `'html'`
|
|
220
|
-
- `x-foo`, `my-element` -> `'web-component'`
|
|
221
|
-
|
|
222
|
-
Note that `<transition>` (lowercase) does **not** match the built-in list and is treated as a standard HTML element (`'html'`), while `<Transition>` (PascalCase) is treated as `'authored'`.
|
|
223
|
-
|
|
224
|
-
## afterFlattenNodes()
|
|
225
|
-
|
|
226
|
-
The `afterFlattenNodes()` method calls the base implementation with specific options:
|
|
227
|
-
|
|
228
|
-
| Option | Value | Effect |
|
|
229
|
-
| ------------------- | ------- | -------------------------------------------------------------------- |
|
|
230
|
-
| `exposeWhiteSpace` | `false` | Whitespace-only text nodes are not exposed as separate invalid nodes |
|
|
231
|
-
| `exposeInvalidNode` | `false` | Invalid nodes are not exposed |
|
|
232
|
-
| `concatText` | `false` | Adjacent text nodes are not concatenated |
|
|
233
|
-
|
|
234
|
-
These settings reflect that Vue's template parser handles whitespace and node validity differently from raw HTML parsing.
|
|
235
|
-
|
|
236
|
-
## Version Compatibility
|
|
237
|
-
|
|
238
|
-
The vue-eslint-parser dependency supports both Vue 2 and Vue 3 template syntax. The parser does not distinguish between Vue versions at the AST level -- both produce the same `VElement`, `VText`, and `VExpressionContainer` node types. Vue 3-specific features like `<Teleport>` and `<Suspense>` are handled through element type detection rather than parser-level changes.
|
|
239
|
-
|
|
240
|
-
## Limitations
|
|
241
|
-
|
|
242
|
-
### No `blockBehavior` support for `v-if` / `v-for`
|
|
243
|
-
|
|
244
|
-
Other framework parsers (Svelte, Pug, Alpine, JSX, Astro) set `blockBehavior` on their conditional/loop constructs so that the core engine can enumerate all possible child node patterns via `conditionalChildNodes()`. The Vue parser does **not** support this. As a result, rules like `permitted-contents` cannot validate content models across `v-if`/`v-else` branches or `v-for` iterations in Vue templates.
|
|
245
|
-
|
|
246
|
-
**Why this is difficult to implement:**
|
|
247
|
-
|
|
248
|
-
In Alpine.js, conditionals and loops use a fixed pattern — `<template x-for="...">` / `<template x-if="...">` — where the `<template>` element can be cleanly converted into a PSBlock. Vue's directives work fundamentally differently:
|
|
249
|
-
|
|
250
|
-
1. **Directives attach to arbitrary elements**: `v-if`, `v-for`, `v-else`, and `v-else-if` can appear on any element (e.g., `<div v-if="...">`, `<li v-for="...">`). The element must remain a valid HTML element for attribute validation while simultaneously acting as a block for content model analysis — a dual role the current parser architecture does not support.
|
|
251
|
-
|
|
252
|
-
2. **Sibling-based branching**: `v-else` and `v-else-if` are attributes on **sibling** elements, not child constructs of a wrapper block. Building conditional groups requires cross-sibling analysis that goes beyond the current per-node `nodeize()` model.
|
|
253
|
-
|
|
254
|
-
The current Vue parser handles these directives at the attribute level only (`isDirective: true`), which suppresses attribute validation errors but does not provide structural block information to the core engine.
|
|
255
|
-
|
|
256
|
-
## Key Source Files
|
|
257
|
-
|
|
258
|
-
| File | Purpose |
|
|
259
|
-
| -------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
260
|
-
| `src/parser.ts` | VueParser class with all override methods |
|
|
261
|
-
| `src/vue-parser/index.ts` | vue-eslint-parser wrapper and type definitions (ASTNode, ASTComment) |
|
|
262
|
-
| `src/index.ts` | Module entry point, re-exports parser instance |
|
|
263
|
-
| `src/index.spec.ts` | Integration tests covering parsing, directives, namespaces |
|
|
264
|
-
| `src/component-scanner.ts` | Component scanner for `@markuplint/pretenders` auto scan (subpath export `./component-scanner`) |
|
|
265
|
-
|
|
266
|
-
## External Dependencies
|
|
267
|
-
|
|
268
|
-
| Dependency | Purpose |
|
|
269
|
-
| -------------------------- | -------------------------------------------------------------------- |
|
|
270
|
-
| `@markuplint/ml-ast` | AST type definitions (`MLASTParentNode`, `MLASTNodeTreeItem`, etc.) |
|
|
271
|
-
| `@markuplint/parser-utils` | Abstract `Parser` class, `ParserError`, `Token`, `ChildToken` |
|
|
272
|
-
| `@markuplint/html-parser` | Peer dependency (not directly imported but part of parser ecosystem) |
|
|
273
|
-
| `vue-eslint-parser` | Vue SFC template parsing (`parse`, AST types) |
|
|
274
|
-
|
|
275
|
-
## Integration Points
|
|
276
|
-
|
|
277
|
-
```mermaid
|
|
278
|
-
flowchart TD
|
|
279
|
-
subgraph upstream ["Upstream"]
|
|
280
|
-
mlAst["@markuplint/ml-ast\n(AST types)"]
|
|
281
|
-
parserUtils["@markuplint/parser-utils\n(Parser base class)"]
|
|
282
|
-
vueEslintParser["vue-eslint-parser\n(Vue SFC tokenizer)"]
|
|
283
|
-
end
|
|
284
|
-
|
|
285
|
-
subgraph pkg ["@markuplint/vue-parser"]
|
|
286
|
-
vueParser["VueParser"]
|
|
287
|
-
compScanner["componentScanner\n(./component-scanner)"]
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
subgraph downstream ["Downstream"]
|
|
291
|
-
mlCore["@markuplint/ml-core\n(MLASTDocument → MLDOM)"]
|
|
292
|
-
pretenders["@markuplint/pretenders\n(auto scan)"]
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
upstream -->|"types, parsing"| vueParser
|
|
296
|
-
vueParser -->|"produces MLASTDocument"| mlCore
|
|
297
|
-
vueParser -->|"parse()"| compScanner
|
|
298
|
-
compScanner -->|"ComponentScanResult"| pretenders
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
### Upstream
|
|
302
|
-
|
|
303
|
-
- **`@markuplint/ml-ast`** -- AST type definitions used throughout the parser
|
|
304
|
-
- **`@markuplint/parser-utils`** -- Abstract `Parser` class that `VueParser` extends, plus `ParserError` and utility types
|
|
305
|
-
- **`vue-eslint-parser`** -- The underlying Vue SFC parser that performs template tokenization and tree construction
|
|
306
|
-
|
|
307
|
-
### Downstream
|
|
308
|
-
|
|
309
|
-
- **`@markuplint/ml-core`** -- Consumes the `MLASTDocument` produced by `VueParser` and constructs the MLDOM for rule evaluation
|
|
310
|
-
- **`@markuplint/pretenders`** -- Dynamically imports `./component-scanner` to extract root element, attributes, and slot information for auto scan
|
|
311
|
-
|
|
312
|
-
## Documentation Map
|
|
313
|
-
|
|
314
|
-
- [Maintenance Guide](docs/maintenance.md) -- Commands, recipes, and troubleshooting
|
package/SKILL.md
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
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
|
-
| `update-component-scanner` | Update component-scanner for pretenders auto scan |
|
|
24
|
-
|
|
25
|
-
If omitted, defaults to `add-directive`.
|
|
26
|
-
|
|
27
|
-
## Reference
|
|
28
|
-
|
|
29
|
-
Before executing any task, read `docs/maintenance.md` (or `docs/maintenance.ja.md`)
|
|
30
|
-
for the full guide. The recipes there are the source of truth for procedures.
|
|
31
|
-
|
|
32
|
-
Also read:
|
|
33
|
-
|
|
34
|
-
- `ARCHITECTURE.md` -- Package overview, directive processing, and element type detection
|
|
35
|
-
- `src/parser.ts` -- VueParser class (source of truth for all override methods)
|
|
36
|
-
|
|
37
|
-
## Task: add-directive
|
|
38
|
-
|
|
39
|
-
Add or modify a Vue directive in `directivePatterns` (`@markuplint/vue-spec`). Follow recipe #1 in `docs/maintenance.md`.
|
|
40
|
-
|
|
41
|
-
### Step 1: Understand the directive
|
|
42
|
-
|
|
43
|
-
1. Open `@markuplint/vue-spec` (`packages/@markuplint/vue-spec/src/index.ts`) and find the `directivePatterns` array
|
|
44
|
-
2. Identify where in the priority chain the new directive should be processed
|
|
45
|
-
3. Determine whether the directive needs `potentialName`, `isDirective`, or `isDynamicValue`
|
|
46
|
-
|
|
47
|
-
### Step 2: Add the directive pattern
|
|
48
|
-
|
|
49
|
-
1. Create a new regex block following the existing pattern (scoped block with destructuring)
|
|
50
|
-
2. Use `attr.name.raw.match()` to extract the directive prefix and value
|
|
51
|
-
3. Set the appropriate metadata:
|
|
52
|
-
- `potentialName` — maps the directive to an equivalent HTML attribute name
|
|
53
|
-
- `isDirective` — marks as a Vue-only directive with no HTML equivalent
|
|
54
|
-
- `isDynamicValue` — indicates the attribute value is a JavaScript expression
|
|
55
|
-
4. Check if the attribute should be in `duplicatableAttrs` (e.g., class, style)
|
|
56
|
-
|
|
57
|
-
### Step 3: Verify
|
|
58
|
-
|
|
59
|
-
1. Build: `yarn build --scope @markuplint/vue-parser`
|
|
60
|
-
2. Add test cases to `src/index.spec.ts` covering:
|
|
61
|
-
- The directive with its full form (e.g., `v-bind:prop`)
|
|
62
|
-
- The shorthand form if applicable (e.g., `:prop`)
|
|
63
|
-
- Edge cases with modifiers (e.g., `.stop`, `.lazy`)
|
|
64
|
-
3. Test: `yarn test --scope @markuplint/vue-parser`
|
|
65
|
-
|
|
66
|
-
## Task: modify-element-type-detection
|
|
67
|
-
|
|
68
|
-
Update `detectElementType()` matchers. Follow recipe #2 in `docs/maintenance.md`.
|
|
69
|
-
|
|
70
|
-
### Step 1: Understand current matchers
|
|
71
|
-
|
|
72
|
-
1. Read the `detectElementType()` method in `src/parser.ts`
|
|
73
|
-
2. Review the matcher array: string literals for built-in components, regex for PascalCase
|
|
74
|
-
|
|
75
|
-
### Step 2: Make the change
|
|
76
|
-
|
|
77
|
-
1. To add a new built-in component: add the component name as a string to the matcher array
|
|
78
|
-
2. To add a new pattern: add a regex to the matcher array
|
|
79
|
-
3. Components matching any entry return `'authored'` element type
|
|
80
|
-
|
|
81
|
-
### Step 3: Verify
|
|
82
|
-
|
|
83
|
-
1. Build: `yarn build --scope @markuplint/vue-parser`
|
|
84
|
-
2. Add test cases to the `elementType` test block in `src/index.spec.ts`
|
|
85
|
-
3. Test: `yarn test --scope @markuplint/vue-parser`
|
|
86
|
-
|
|
87
|
-
## Task: update-vue-version-support
|
|
88
|
-
|
|
89
|
-
Handle vue-eslint-parser API changes. Follow recipe #3 in `docs/maintenance.md`.
|
|
90
|
-
|
|
91
|
-
### Step 1: Understand the change
|
|
92
|
-
|
|
93
|
-
1. Read `src/vue-parser/index.ts` — the wrapper around vue-eslint-parser
|
|
94
|
-
2. Check the vue-eslint-parser changelog for breaking changes
|
|
95
|
-
3. Verify the `ASTNode` and `ASTComment` type exports still match
|
|
96
|
-
|
|
97
|
-
### Step 2: Make the change
|
|
98
|
-
|
|
99
|
-
1. Update `vueParse()` if the `parse()` API signature changed
|
|
100
|
-
2. Update type exports (`ASTNode`, `ASTComment`, `VueTokens`) if AST types changed
|
|
101
|
-
3. Update `tokenize()` in `src/parser.ts` if `templateBody` structure changed
|
|
102
|
-
|
|
103
|
-
### Step 3: Verify
|
|
104
|
-
|
|
105
|
-
1. Build: `yarn build --scope @markuplint/vue-parser`
|
|
106
|
-
2. Test: `yarn test --scope @markuplint/vue-parser`
|
|
107
|
-
3. Test with real Vue SFC files to ensure correct parsing
|
|
108
|
-
|
|
109
|
-
## Task: fix-comment-injection
|
|
110
|
-
|
|
111
|
-
Fix template comment injection in `flattenNodes()`. Follow recipe #4 in `docs/maintenance.md`.
|
|
112
|
-
|
|
113
|
-
### Step 1: Understand the issue
|
|
114
|
-
|
|
115
|
-
1. Read the `flattenNodes()` method in `src/parser.ts`
|
|
116
|
-
2. Understand the two-pass approach: first flatten, then inject comments
|
|
117
|
-
3. Check how `this.state.comments` is populated in `tokenize()`
|
|
118
|
-
|
|
119
|
-
### Step 2: Fix the injection logic
|
|
120
|
-
|
|
121
|
-
1. Verify the comment range check: `lastOffset <= comment.range[0] && comment.range[1] <= node.startOffset`
|
|
122
|
-
2. Verify `this.visitComment()` is called with the correct `isBogus` flag
|
|
123
|
-
3. Verify `this.appendChild()` correctly attaches the comment to the parent
|
|
124
|
-
|
|
125
|
-
### Step 3: Verify
|
|
126
|
-
|
|
127
|
-
1. Build: `yarn build --scope @markuplint/vue-parser`
|
|
128
|
-
2. Test with Vue templates containing HTML comments (`<!-- -->`), bogus comments (`<!...>`), and mixed content
|
|
129
|
-
3. Test: `yarn test --scope @markuplint/vue-parser`
|
|
130
|
-
|
|
131
|
-
## Task: update-component-scanner
|
|
132
|
-
|
|
133
|
-
Update `src/component-scanner.ts` when Vue slot syntax or script block handling changes.
|
|
134
|
-
|
|
135
|
-
### When to update
|
|
136
|
-
|
|
137
|
-
- New slot-like syntax is added to Vue (e.g., a new built-in component that acts as a slot)
|
|
138
|
-
- `<script setup>` extraction logic needs to change (e.g., new script attributes)
|
|
139
|
-
- The `extractComponentInfo` shared logic needs a fix (also update svelte-parser and astro-parser)
|
|
140
|
-
|
|
141
|
-
### Step 1: Make the change
|
|
142
|
-
|
|
143
|
-
1. Read `src/component-scanner.ts`
|
|
144
|
-
2. Modify `detectSlots()` for new slot patterns, or `extractVueScriptSetup()` for script changes
|
|
145
|
-
3. If modifying `extractComponentInfo()`, apply the same change to all three parsers (vue, svelte, astro)
|
|
146
|
-
|
|
147
|
-
### Step 2: Verify
|
|
148
|
-
|
|
149
|
-
1. Update tests in `src/component-scanner.spec.ts`
|
|
150
|
-
2. Build: `yarn build --scope @markuplint/vue-parser`
|
|
151
|
-
3. Test: `npx vitest run packages/@markuplint/vue-parser/src/component-scanner.spec.ts`
|
|
152
|
-
4. Run pretenders integration tests: `npx vitest run packages/@markuplint/pretenders`
|
|
153
|
-
|
|
154
|
-
## Rules
|
|
155
|
-
|
|
156
|
-
1. **Use vue-eslint-parser** for all template parsing — never parse Vue templates manually.
|
|
157
|
-
2. **Use `potentialName`** for directives that map to HTML attributes (e.g., `@click` -> `onclick`).
|
|
158
|
-
3. **Use `isDirective: true`** for directives with no HTML equivalent (e.g., `v-if`, `v-for`).
|
|
159
|
-
4. **Test with `nodeListToDebugMaps`** — this is the standard assertion pattern for parser tests.
|
|
160
|
-
5. **Add JSDoc comments** to all new public methods and properties.
|
|
161
|
-
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-`.
|