@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 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 (`isComponent`)
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: ast.templateBody?.children ?? [],
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 (`isComponent`)
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.4.0",
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.4.0",
36
- "@markuplint/ml-ast": "5.3.0",
37
- "@markuplint/parser-utils": "5.4.0",
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": "647f6fb5d5743f060678599c97e9c84b58970747"
41
+ "gitHead": "eade3d7202682e567f3c1b6985478f1d4b5c5481"
42
42
  }