@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/ARCHITECTURE.ja.md +302 -0
- package/ARCHITECTURE.md +302 -0
- package/CHANGELOG.md +18 -2
- package/SKILL.md +137 -0
- package/docs/maintenance.ja.md +203 -0
- package/docs/maintenance.md +203 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +6 -0
- package/lib/parser.d.ts +26 -9
- package/lib/parser.js +30 -98
- package/lib/vue-parser/index.d.ts +13 -1
- package/lib/vue-parser/index.js +6 -0
- package/package.json +9 -6
package/lib/parser.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { ParserError, Parser } from '@markuplint/parser-utils';
|
|
2
2
|
import { vueParse } from './vue-parser/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Parser implementation for Vue SFC templates.
|
|
5
|
+
* Extends the base Parser to handle Vue elements, text nodes, expression containers,
|
|
6
|
+
* directives (`v-bind`, `v-on`, `v-model`, `v-slot`), and template comments.
|
|
7
|
+
* Recognizes Vue built-in components and PascalCase user components.
|
|
8
|
+
*/
|
|
3
9
|
class VueParser extends Parser {
|
|
4
10
|
constructor() {
|
|
5
11
|
super({
|
|
@@ -7,7 +13,6 @@ class VueParser extends Parser {
|
|
|
7
13
|
}, {
|
|
8
14
|
comments: [],
|
|
9
15
|
});
|
|
10
|
-
this.duplicatableAttrs = new Set(['class', 'style']);
|
|
11
16
|
}
|
|
12
17
|
tokenize() {
|
|
13
18
|
const ast = vueParse(this.rawCode);
|
|
@@ -29,6 +34,16 @@ class VueParser extends Parser {
|
|
|
29
34
|
}
|
|
30
35
|
return super.parseError(error);
|
|
31
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Converts a Vue AST node into markuplint node tree items.
|
|
39
|
+
* Handles VText (text nodes), VExpressionContainer (template expressions),
|
|
40
|
+
* and VElement (elements with start/end tags and children).
|
|
41
|
+
*
|
|
42
|
+
* @param originNode - The Vue AST node to convert
|
|
43
|
+
* @param parentNode - The parent node in the markuplint tree, or null for root nodes
|
|
44
|
+
* @param depth - The nesting depth of the node
|
|
45
|
+
* @returns An array of markuplint node tree items
|
|
46
|
+
*/
|
|
32
47
|
nodeize(
|
|
33
48
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
34
49
|
originNode, parentNode, depth) {
|
|
@@ -57,7 +72,6 @@ class VueParser extends Parser {
|
|
|
57
72
|
depth,
|
|
58
73
|
parentNode,
|
|
59
74
|
nodeName: originNode.name,
|
|
60
|
-
namespace: originNode.namespace,
|
|
61
75
|
}, originNode.children, {
|
|
62
76
|
createEndTagToken: () => {
|
|
63
77
|
if (!originNode.endTag) {
|
|
@@ -74,14 +88,26 @@ class VueParser extends Parser {
|
|
|
74
88
|
}
|
|
75
89
|
}
|
|
76
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Extends the base flattening to inject Vue template comments between sibling nodes.
|
|
93
|
+
* Comments from the vue-eslint-parser are inserted at the correct positions
|
|
94
|
+
* based on their source offsets relative to adjacent nodes.
|
|
95
|
+
*
|
|
96
|
+
* @param nodeTree - The hierarchical node tree to flatten
|
|
97
|
+
* @returns A flat list of nodes including interleaved comments
|
|
98
|
+
*/
|
|
77
99
|
flattenNodes(nodeTree) {
|
|
78
100
|
const nodeList = super.flattenNodes(nodeTree);
|
|
79
101
|
const newNodeList = [];
|
|
80
102
|
let prevNode = null;
|
|
81
103
|
for (const node of nodeList) {
|
|
82
|
-
const lastOffset = prevNode
|
|
104
|
+
const lastOffset = prevNode
|
|
105
|
+
? prevNode.offset + prevNode.raw.length
|
|
106
|
+
: node.parentNode
|
|
107
|
+
? node.parentNode.offset + node.parentNode.raw.length
|
|
108
|
+
: 0;
|
|
83
109
|
const betweenComment = this.state.comments.find(comment => {
|
|
84
|
-
return lastOffset <= comment.range[0] && comment.range[1] <= node.
|
|
110
|
+
return lastOffset <= comment.range[0] && comment.range[1] <= node.offset;
|
|
85
111
|
});
|
|
86
112
|
if (betweenComment) {
|
|
87
113
|
const token = this.sliceFragment(betweenComment.range[0], betweenComment.range[1]);
|
|
@@ -111,100 +137,6 @@ class VueParser extends Parser {
|
|
|
111
137
|
concatText: false,
|
|
112
138
|
});
|
|
113
139
|
}
|
|
114
|
-
visitAttr(token) {
|
|
115
|
-
const attr = super.visitAttr(token);
|
|
116
|
-
if (attr.type === 'spread') {
|
|
117
|
-
return attr;
|
|
118
|
-
}
|
|
119
|
-
{
|
|
120
|
-
/**
|
|
121
|
-
* `v-on`
|
|
122
|
-
*/
|
|
123
|
-
const [, directive, potentialName] = attr.name.raw.match(/^(v-on:|@)([^.]+)(?:\.([^.]+))?$/i) ?? [];
|
|
124
|
-
if (directive && potentialName) {
|
|
125
|
-
return {
|
|
126
|
-
...attr,
|
|
127
|
-
potentialName: `on${potentialName.toLowerCase()}`,
|
|
128
|
-
isDynamicValue: true,
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
{
|
|
133
|
-
/**
|
|
134
|
-
* `v-bind`
|
|
135
|
-
*/
|
|
136
|
-
const [, directive, potentialName, modifier] = attr.name.raw.match(/^(v-bind:|:)([^.]+)(?:\.([^.]+))?$/i) ?? [];
|
|
137
|
-
if (directive && potentialName) {
|
|
138
|
-
if (this.duplicatableAttrs.has(potentialName.toLowerCase())) {
|
|
139
|
-
this.updateAttr(attr, { isDuplicatable: true });
|
|
140
|
-
}
|
|
141
|
-
if (!modifier) {
|
|
142
|
-
return {
|
|
143
|
-
...attr,
|
|
144
|
-
potentialName,
|
|
145
|
-
isDynamicValue: true,
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
switch (modifier) {
|
|
149
|
-
case '.attr': {
|
|
150
|
-
return {
|
|
151
|
-
...attr,
|
|
152
|
-
potentialName,
|
|
153
|
-
isDynamicValue: true,
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
/* eslint-disable unicorn/no-useless-switch-case */
|
|
157
|
-
case '.prop':
|
|
158
|
-
case '.camel':
|
|
159
|
-
default: {
|
|
160
|
-
const name = `v-bind:${potentialName}${modifier ?? ''}`;
|
|
161
|
-
return {
|
|
162
|
-
...attr,
|
|
163
|
-
potentialName: attr.name.raw === name ? undefined : name,
|
|
164
|
-
isDirective: true,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
/* eslint-enable unicorn/no-useless-switch-case */
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
{
|
|
172
|
-
/**
|
|
173
|
-
* `v-model`
|
|
174
|
-
*/
|
|
175
|
-
const [, directive] = attr.name.raw.match(/^(v-model)(?:\.([^.]+))?$/i) ?? [];
|
|
176
|
-
if (directive) {
|
|
177
|
-
return {
|
|
178
|
-
...attr,
|
|
179
|
-
isDirective: true,
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
{
|
|
184
|
-
/**
|
|
185
|
-
* `v-slot`
|
|
186
|
-
*/
|
|
187
|
-
const slotName = (attr.name.raw.match(/^(v-slot:|#)(.+)$/i) ?? [])[2];
|
|
188
|
-
const name = `v-slot:${slotName}`;
|
|
189
|
-
if (slotName) {
|
|
190
|
-
return {
|
|
191
|
-
...attr,
|
|
192
|
-
potentialName: attr.name.raw === name ? undefined : name,
|
|
193
|
-
isDirective: true,
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* If directives
|
|
199
|
-
*/
|
|
200
|
-
if (attr.name.raw.startsWith('v-')) {
|
|
201
|
-
return {
|
|
202
|
-
...attr,
|
|
203
|
-
isDirective: true,
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
return attr;
|
|
207
|
-
}
|
|
208
140
|
/**
|
|
209
141
|
* > In SFCs, it's recommended to use `PascalCase` tag names
|
|
210
142
|
* > for child components to differentiate from native HTML elements.
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import * as VueESLintParser from 'vue-eslint-parser';
|
|
2
|
+
/** The top-level AST produced by vue-eslint-parser, containing template body and comments. */
|
|
2
3
|
export type VueTokens = VueESLintParser.AST.ESLintProgram;
|
|
4
|
+
/**
|
|
5
|
+
* Parses a Vue SFC template string into a vue-eslint-parser AST.
|
|
6
|
+
*
|
|
7
|
+
* @param vueTemplate - The raw Vue template source code
|
|
8
|
+
* @returns The parsed AST program node containing the template body
|
|
9
|
+
*/
|
|
3
10
|
export declare function vueParse(vueTemplate: string): VueTokens;
|
|
4
|
-
export type
|
|
11
|
+
export type VElement = VueESLintParser.AST.VElement;
|
|
12
|
+
export type VText = VueESLintParser.AST.VText;
|
|
13
|
+
export type VExpressionContainer = VueESLintParser.AST.VExpressionContainer;
|
|
14
|
+
/** Union of AST node types that can appear as children in a Vue template. */
|
|
15
|
+
export type ASTNode = VElement | VText | VExpressionContainer;
|
|
16
|
+
/** Represents a comment token in the Vue template AST with location information. */
|
|
5
17
|
export type ASTComment = VueESLintParser.AST.Token & VueESLintParser.AST.HasLocation;
|
package/lib/vue-parser/index.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import * as VueESLintParser from 'vue-eslint-parser';
|
|
2
|
+
/**
|
|
3
|
+
* Parses a Vue SFC template string into a vue-eslint-parser AST.
|
|
4
|
+
*
|
|
5
|
+
* @param vueTemplate - The raw Vue template source code
|
|
6
|
+
* @returns The parsed AST program node containing the template body
|
|
7
|
+
*/
|
|
2
8
|
export function vueParse(vueTemplate) {
|
|
3
9
|
const ast = VueESLintParser.parse(vueTemplate, { parser: false });
|
|
4
10
|
return ast;
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markuplint/vue-parser",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0-alpha.0",
|
|
4
4
|
"description": "Vue parser for markuplint",
|
|
5
5
|
"repository": "git@github.com:markuplint/markuplint.git",
|
|
6
6
|
"author": "Yusuke Hirao <yusukehirao@me.com>",
|
|
7
7
|
"license": "MIT",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=22"
|
|
10
|
+
},
|
|
8
11
|
"type": "module",
|
|
9
12
|
"exports": {
|
|
10
13
|
".": {
|
|
@@ -21,10 +24,10 @@
|
|
|
21
24
|
"clean": "tsc --build --clean tsconfig.build.json"
|
|
22
25
|
},
|
|
23
26
|
"dependencies": {
|
|
24
|
-
"@markuplint/html-parser": "
|
|
25
|
-
"@markuplint/ml-ast": "
|
|
26
|
-
"@markuplint/parser-utils": "
|
|
27
|
-
"vue-eslint-parser": "10.
|
|
27
|
+
"@markuplint/html-parser": "5.0.0-alpha.0",
|
|
28
|
+
"@markuplint/ml-ast": "5.0.0-alpha.0",
|
|
29
|
+
"@markuplint/parser-utils": "5.0.0-alpha.0",
|
|
30
|
+
"vue-eslint-parser": "10.4.0"
|
|
28
31
|
},
|
|
29
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "13dcfc84ec83d87360c720e253383b60767e1b56"
|
|
30
33
|
}
|