@markuplint/vue-parser 5.4.0 → 5.6.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/CHANGELOG.md +18 -0
- package/README.md +2 -0
- package/lib/parser.d.ts +4 -3
- package/lib/parser.js +73 -4
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,24 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [5.6.0](https://github.com/markuplint/markuplint/compare/v5.5.0...v5.6.0) (2026-11-12)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **vue-parser:** treat slot as a fragment ([0f5e7b2](https://github.com/markuplint/markuplint/commit/0f5e7b2748ddac50bbb276febfcd20bdccf51c5d))
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
* **vue-parser:** declare that slot is the placeholder of the framework ([cb12dd5](https://github.com/markuplint/markuplint/commit/cb12dd5016d48525905ab3fca578cb1da2bad369))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# [5.5.0](https://github.com/markuplint/markuplint/compare/v5.4.0...v5.5.0) (2026-11-10)
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* **vue-parser:** treat a style and a script of the template as code that the compiler removes ([44315f6](https://github.com/markuplint/markuplint/commit/44315f692b852ec85f282d2632cf71e09f830594))
|
|
22
|
+
|
|
23
|
+
|
|
6
24
|
# [5.4.0](https://github.com/markuplint/markuplint/compare/v5.3.0...v5.4.0) (2026-10-29)
|
|
7
25
|
|
|
8
26
|
**Note:** Version bump only for package @markuplint/vue-parser
|
package/README.md
CHANGED
|
@@ -24,4 +24,6 @@ Add `parser` option to your [configuration](https://markuplint.dev/configuration
|
|
|
24
24
|
}
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
Vue removes a `<style>` and a `<script>` from a template, so no rule checks one and, when you set `severity.parseError`, each is reported as the parse error `vue-ignored-side-effect-tag`. See [`parse-error`](https://markuplint.dev/docs/migration/v4-to-v5/rules/parse-error).
|
|
28
|
+
|
|
27
29
|
`parserOptions` option work in progress. In the current, the setting is `{ "sourceType": "module" }` that hard coded.
|
package/lib/parser.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ASTNode, ASTComment } from './vue-parser/index.js';
|
|
2
|
-
import type { MLASTParentNode, MLASTNodeTreeItem } from '@markuplint/ml-ast';
|
|
2
|
+
import type { MLASTParentNode, MLASTNodeTreeItem, MLASTParseError } from '@markuplint/ml-ast';
|
|
3
3
|
import { ParserError, Parser } from '@markuplint/parser-utils';
|
|
4
4
|
type State = {
|
|
5
5
|
comments: readonly ASTComment[];
|
|
@@ -30,6 +30,7 @@ declare class VueParser extends Parser<ASTNode, State> {
|
|
|
30
30
|
tokenize(): {
|
|
31
31
|
readonly ast: ASTNode[];
|
|
32
32
|
readonly isFragment: boolean;
|
|
33
|
+
readonly parseErrors?: readonly MLASTParseError[];
|
|
33
34
|
};
|
|
34
35
|
parseError(error: any): ParserError;
|
|
35
36
|
nodeize(originNode: ASTNode, parentNode: MLASTParentNode | null, depth: number): readonly MLASTNodeTreeItem[];
|
|
@@ -68,9 +69,9 @@ declare class VueParser extends Parser<ASTNode, State> {
|
|
|
68
69
|
* The user's `authoredElementName` replaces all of this, as it replaces any default of a parser.
|
|
69
70
|
*
|
|
70
71
|
* @see https://vuejs.org/guide/essentials/component-basics#using-a-component
|
|
71
|
-
* @see https://vuejs.org/api/built-in-components.html
|
|
72
|
+
* @see https://vuejs.org/api/built-in-components.html#built-in-components
|
|
72
73
|
* @see https://vuejs.org/api/built-in-special-elements.html#built-in-special-elements
|
|
73
|
-
* @see https://github.com/vuejs/core/blob/main/packages/compiler-core/src/parser.ts
|
|
74
|
+
* @see https://github.com/vuejs/core/blob/main/packages/compiler-core/src/parser.ts#isComponent
|
|
74
75
|
*/
|
|
75
76
|
detectElementType(nodeName: string): import("@markuplint/ml-ast").ElementType;
|
|
76
77
|
/**
|
package/lib/parser.js
CHANGED
|
@@ -5,7 +5,7 @@ import { vueParse } from './vue-parser/index.js';
|
|
|
5
5
|
* The built-in components, as written or in kebab-case: `isBuiltInType(tag, name)` of Vue is
|
|
6
6
|
* `tag === name || tag === hyphenate(name)`.
|
|
7
7
|
*
|
|
8
|
-
* @see https://vuejs.org/api/built-in-components.html
|
|
8
|
+
* @see https://vuejs.org/api/built-in-components.html#built-in-components
|
|
9
9
|
*/
|
|
10
10
|
const BUILT_IN_COMPONENTS = [
|
|
11
11
|
'Transition',
|
|
@@ -19,6 +19,55 @@ const BUILT_IN_COMPONENTS = [
|
|
|
19
19
|
'Suspense',
|
|
20
20
|
'suspense',
|
|
21
21
|
];
|
|
22
|
+
/**
|
|
23
|
+
* Whether the element is a `<style>` or a `<script>` that Vue's compiler removes from a client template.
|
|
24
|
+
*
|
|
25
|
+
* > Tags with side effect (<script> and <style>) are ignored in client component templates.
|
|
26
|
+
*
|
|
27
|
+
* `ignoreSideEffectTags` of `@vue/compiler-dom` removes the element whose `tag` is `script` or `style`
|
|
28
|
+
* and whose `tagType` is `ELEMENT`: a name with a capital letter (`<Style>`) is a component
|
|
29
|
+
* (`isComponent` of `@vue/compiler-core`). The parser of `vue-eslint-parser` lower-cases `name` and
|
|
30
|
+
* keeps `rawName`, so `rawName` is what tells them apart.
|
|
31
|
+
*
|
|
32
|
+
* @see https://github.com/vuejs/core/blob/main/packages/compiler-dom/src/transforms/ignoreSideEffectTags.ts#ignoreSideEffectTags
|
|
33
|
+
* @see https://github.com/vuejs/core/blob/main/packages/compiler-dom/src/errors.ts#DOMErrorMessages
|
|
34
|
+
* @param element - The element of the template
|
|
35
|
+
* @returns `true` if the compiler removes the element
|
|
36
|
+
*/
|
|
37
|
+
function isIgnoredSideEffectTag(element) {
|
|
38
|
+
return element.rawName === 'style' || element.rawName === 'script';
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Collects the non-fatal parse errors for the elements that the compiler removes, in the order of the source.
|
|
42
|
+
* It does not look inside one: the compiler removes it with its content.
|
|
43
|
+
*
|
|
44
|
+
* @param nodes - The nodes of a level of the template
|
|
45
|
+
* @param rawCode - The source that the ranges of `nodes` are positions in
|
|
46
|
+
* @returns The parse errors, whose positions cover the whole element
|
|
47
|
+
*/
|
|
48
|
+
function collectIgnoredSideEffectTags(nodes, rawCode) {
|
|
49
|
+
const errors = [];
|
|
50
|
+
for (const node of nodes) {
|
|
51
|
+
if (node.type !== 'VElement') {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (isIgnoredSideEffectTag(node)) {
|
|
55
|
+
errors.push({
|
|
56
|
+
code: 'vue-ignored-side-effect-tag',
|
|
57
|
+
startOffset: node.range[0],
|
|
58
|
+
startLine: node.loc.start.line,
|
|
59
|
+
startCol: node.loc.start.column + 1,
|
|
60
|
+
endOffset: node.range[1],
|
|
61
|
+
endLine: node.loc.end.line,
|
|
62
|
+
endCol: node.loc.end.column + 1,
|
|
63
|
+
raw: rawCode.slice(node.range[0], node.range[1]),
|
|
64
|
+
});
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
errors.push(...collectIgnoredSideEffectTags(node.children, rawCode));
|
|
68
|
+
}
|
|
69
|
+
return errors;
|
|
70
|
+
}
|
|
22
71
|
/**
|
|
23
72
|
* Directive resolution is intentionally not performed by this parser:
|
|
24
73
|
* `directivePatterns` declared in `@markuplint/vue-spec` are applied later by
|
|
@@ -51,6 +100,9 @@ class VueParser extends Parser {
|
|
|
51
100
|
// The compiler of Vue makes an element by `setAttribute` unless it can put the whole static tree into
|
|
52
101
|
// an HTML string, which it does only for a name it knows by its exact spelling.
|
|
53
102
|
attrNameRoute: 'dom-api',
|
|
103
|
+
// `<slot>` is replaced with what is passed to it, not the HTML `slot` element of a shadow tree
|
|
104
|
+
// @see https://vuejs.org/guide/components/slots.html
|
|
105
|
+
slotElement: 'framework',
|
|
54
106
|
}, {
|
|
55
107
|
comments: [],
|
|
56
108
|
});
|
|
@@ -60,9 +112,12 @@ class VueParser extends Parser {
|
|
|
60
112
|
if (ast.templateBody?.comments) {
|
|
61
113
|
this.state.comments = ast.templateBody.comments;
|
|
62
114
|
}
|
|
115
|
+
const children = ast.templateBody?.children ?? [];
|
|
116
|
+
const parseErrors = collectIgnoredSideEffectTags(children, this.rawCode);
|
|
63
117
|
return {
|
|
64
|
-
ast:
|
|
118
|
+
ast: children,
|
|
65
119
|
isFragment: true,
|
|
120
|
+
...(parseErrors.length > 0 ? { parseErrors } : {}),
|
|
66
121
|
};
|
|
67
122
|
}
|
|
68
123
|
parseError(error) {
|
|
@@ -81,6 +136,17 @@ class VueParser extends Parser {
|
|
|
81
136
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
82
137
|
originNode, parentNode, depth) {
|
|
83
138
|
const token = this.sliceFragment(originNode.range[0], originNode.range[1]);
|
|
139
|
+
// The compiler removes it, so neither its content nor where it is written reaches the DOM:
|
|
140
|
+
// it is a block of code that no rule reads, and `tokenize` reports it as a parse error.
|
|
141
|
+
if (originNode.type === 'VElement' && isIgnoredSideEffectTag(originNode)) {
|
|
142
|
+
return this.visitPsBlock({
|
|
143
|
+
...token,
|
|
144
|
+
depth,
|
|
145
|
+
parentNode,
|
|
146
|
+
nodeName: originNode.rawName,
|
|
147
|
+
isFragment: false,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
84
150
|
switch (originNode.type) {
|
|
85
151
|
case 'VText': {
|
|
86
152
|
return this.visitText({
|
|
@@ -108,6 +174,9 @@ class VueParser extends Parser {
|
|
|
108
174
|
parentNode,
|
|
109
175
|
nodeName: originNode.name,
|
|
110
176
|
}, originNode.children, {
|
|
177
|
+
// `<slot>` is replaced with what is passed to it: Vue renders no element for it
|
|
178
|
+
// @see https://vuejs.org/guide/components/slots.html
|
|
179
|
+
overwriteProps: originNode.name === 'slot' ? { isFragment: true } : undefined,
|
|
111
180
|
createEndTagToken: () => {
|
|
112
181
|
if (!originNode.endTag) {
|
|
113
182
|
return null;
|
|
@@ -199,9 +268,9 @@ class VueParser extends Parser {
|
|
|
199
268
|
* The user's `authoredElementName` replaces all of this, as it replaces any default of a parser.
|
|
200
269
|
*
|
|
201
270
|
* @see https://vuejs.org/guide/essentials/component-basics#using-a-component
|
|
202
|
-
* @see https://vuejs.org/api/built-in-components.html
|
|
271
|
+
* @see https://vuejs.org/api/built-in-components.html#built-in-components
|
|
203
272
|
* @see https://vuejs.org/api/built-in-special-elements.html#built-in-special-elements
|
|
204
|
-
* @see https://github.com/vuejs/core/blob/main/packages/compiler-core/src/parser.ts
|
|
273
|
+
* @see https://github.com/vuejs/core/blob/main/packages/compiler-core/src/parser.ts#isComponent
|
|
205
274
|
*/
|
|
206
275
|
detectElementType(nodeName) {
|
|
207
276
|
return super.detectElementType(nodeName, [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markuplint/vue-parser",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.6.0",
|
|
4
4
|
"description": "Vue parser for markuplint",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
"clean": "tsc --build --clean tsconfig.build.json"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@markuplint/html-parser": "5.
|
|
36
|
-
"@markuplint/ml-ast": "5.
|
|
37
|
-
"@markuplint/parser-utils": "5.
|
|
35
|
+
"@markuplint/html-parser": "5.6.0",
|
|
36
|
+
"@markuplint/ml-ast": "5.6.0",
|
|
37
|
+
"@markuplint/parser-utils": "5.6.0",
|
|
38
38
|
"@vue/shared": "^3.5.44",
|
|
39
39
|
"vue-eslint-parser": "^10.4.1"
|
|
40
40
|
},
|
|
41
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "eade3d7202682e567f3c1b6985478f1d4b5c5481"
|
|
42
42
|
}
|