@markuplint/vue-parser 3.12.0 → 4.0.0-alpha.10

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2017-2019 Yusuke Hirao
3
+ Copyright (c) 2017-2024 Yusuke Hirao
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/lib/index.d.ts CHANGED
@@ -1,2 +1 @@
1
- export { parse } from './parse';
2
- export declare const endTag = 'xml';
1
+ export { parser } from './parser.js';
package/lib/index.js CHANGED
@@ -1,6 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.endTag = exports.parse = void 0;
4
- var parse_1 = require("./parse");
5
- Object.defineProperty(exports, "parse", { enumerable: true, get: function () { return parse_1.parse; } });
6
- exports.endTag = 'xml';
1
+ export { parser } from './parser.js';
@@ -0,0 +1,28 @@
1
+ /// <reference types="vue-eslint-parser" />
2
+ import type { ASTNode, ASTComment } from './vue-parser/index.js';
3
+ import type { MLASTParentNode, MLASTNodeTreeItem } from '@markuplint/ml-ast';
4
+ import type { Token } from '@markuplint/parser-utils';
5
+ import { ParserError, Parser } from '@markuplint/parser-utils';
6
+ type State = {
7
+ comments: readonly ASTComment[];
8
+ };
9
+ declare class VueParser extends Parser<ASTNode, State> {
10
+ readonly duplicatableAttrs: Set<string>;
11
+ constructor();
12
+ tokenize(): {
13
+ ast: (import("vue-eslint-parser/ast/nodes").VElement | import("vue-eslint-parser/ast/nodes").VText | import("vue-eslint-parser/ast/nodes").VExpressionContainer)[];
14
+ isFragment: boolean;
15
+ };
16
+ parseError(error: any): ParserError;
17
+ nodeize(originNode: ASTNode, parentNode: MLASTParentNode | null, depth: number): readonly MLASTNodeTreeItem[];
18
+ flattenNodes(nodeTree: readonly MLASTNodeTreeItem[]): MLASTNodeTreeItem[];
19
+ afterFlattenNodes(nodeList: readonly MLASTNodeTreeItem[]): MLASTNodeTreeItem[];
20
+ visitAttr(token: Token): (import("@markuplint/ml-ast").MLASTHTMLAttr & {
21
+ __rightText?: string | undefined;
22
+ }) | (import("@markuplint/ml-ast").MLASTSpreadAttr & {
23
+ __rightText?: string | undefined;
24
+ });
25
+ detectElementType(nodeName: string): import("@markuplint/ml-ast").ElementType;
26
+ }
27
+ export declare const parser: VueParser;
28
+ export {};
package/lib/parser.js ADDED
@@ -0,0 +1,238 @@
1
+ import { ParserError, Parser } from '@markuplint/parser-utils';
2
+ import { vueParse } from './vue-parser/index.js';
3
+ class VueParser extends Parser {
4
+ constructor() {
5
+ super({
6
+ endTagType: 'xml',
7
+ }, {
8
+ comments: [],
9
+ });
10
+ this.duplicatableAttrs = new Set(['class', 'style']);
11
+ }
12
+ tokenize() {
13
+ const ast = vueParse(this.rawCode);
14
+ if (ast.templateBody?.comments) {
15
+ this.state.comments = ast.templateBody.comments;
16
+ }
17
+ return {
18
+ ast: ast.templateBody?.children ?? [],
19
+ isFragment: true,
20
+ };
21
+ }
22
+ parseError(error) {
23
+ if (error instanceof SyntaxError && 'lineNumber' in error && 'column' in error) {
24
+ throw new ParserError(error.message, {
25
+ line: error.lineNumber,
26
+ col: error.column,
27
+ raw: '',
28
+ });
29
+ }
30
+ return super.parseError(error);
31
+ }
32
+ nodeize(
33
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
34
+ originNode, parentNode, depth) {
35
+ const token = this.sliceFragment(originNode.range[0], originNode.range[1]);
36
+ switch (originNode.type) {
37
+ case 'VText': {
38
+ return this.visitText({
39
+ ...token,
40
+ depth,
41
+ parentNode,
42
+ });
43
+ }
44
+ case 'VExpressionContainer': {
45
+ return this.visitPsBlock({
46
+ ...token,
47
+ depth,
48
+ parentNode,
49
+ nodeName: 'vue-expression-container',
50
+ });
51
+ }
52
+ default: {
53
+ const token = this.sliceFragment(originNode.startTag.range[0], originNode.startTag.range[1]);
54
+ return this.visitElement({
55
+ ...token,
56
+ depth,
57
+ parentNode,
58
+ nodeName: originNode.name,
59
+ namespace: originNode.namespace,
60
+ }, originNode.children, {
61
+ createEndTagToken: () => {
62
+ if (!originNode.endTag) {
63
+ return null;
64
+ }
65
+ const token = this.sliceFragment(originNode.endTag.range[0], originNode.endTag.range[1]);
66
+ return {
67
+ ...token,
68
+ depth,
69
+ parentNode,
70
+ };
71
+ },
72
+ });
73
+ }
74
+ }
75
+ }
76
+ flattenNodes(nodeTree) {
77
+ const nodeList = super.flattenNodes(nodeTree);
78
+ const newNodeList = [];
79
+ let prevNode = null;
80
+ for (const node of nodeList) {
81
+ const lastOffset = prevNode?.endOffset ?? node.parentNode?.endOffset ?? 0;
82
+ const betweenComment = this.state.comments.find(comment => {
83
+ return lastOffset <= comment.range[0] && comment.range[1] <= node.startOffset;
84
+ });
85
+ if (betweenComment) {
86
+ const token = this.sliceFragment(betweenComment.range[0], betweenComment.range[1]);
87
+ const comment = this.visitComment({
88
+ ...token,
89
+ depth: node.depth,
90
+ parentNode: node.parentNode,
91
+ }, {
92
+ isBogus: betweenComment.type === 'HTMLBogusComment',
93
+ })[0];
94
+ if (comment && comment.type === 'comment') {
95
+ newNodeList.push(comment);
96
+ if (node.parentNode) {
97
+ this.appendChild(node.parentNode, comment);
98
+ }
99
+ }
100
+ }
101
+ newNodeList.push(node);
102
+ prevNode = node;
103
+ }
104
+ return newNodeList;
105
+ }
106
+ afterFlattenNodes(nodeList) {
107
+ const optimizedNodeList = super.afterFlattenNodes(nodeList);
108
+ const newNodeList = [];
109
+ let openTemplateTag = false;
110
+ let closeTemplateTag = false;
111
+ for (const node of optimizedNodeList) {
112
+ if (!openTemplateTag && node.type === 'text') {
113
+ continue;
114
+ }
115
+ if (node.type === 'starttag' && node.nodeName === 'template' && node.depth === 0) {
116
+ openTemplateTag = true;
117
+ continue;
118
+ }
119
+ if (/^<\/template>$/i.test(node.raw) && node.depth === 0) {
120
+ closeTemplateTag = true;
121
+ continue;
122
+ }
123
+ if (closeTemplateTag && node.type === 'text') {
124
+ continue;
125
+ }
126
+ newNodeList.push(node);
127
+ }
128
+ return newNodeList;
129
+ }
130
+ visitAttr(token) {
131
+ const attr = super.visitAttr(token);
132
+ if (attr.type === 'spread') {
133
+ return attr;
134
+ }
135
+ {
136
+ /**
137
+ * `v-on`
138
+ */
139
+ const [, directive, potentialName] = attr.name.raw.match(/^(v-on:|@)([^.]+)(?:\.([^.]+))?$/i) ?? [];
140
+ if (directive && potentialName) {
141
+ return {
142
+ ...attr,
143
+ potentialName: `on${potentialName.toLowerCase()}`,
144
+ isDynamicValue: true,
145
+ };
146
+ }
147
+ }
148
+ {
149
+ /**
150
+ * `v-bind`
151
+ */
152
+ const [, directive, potentialName, modifier] = attr.name.raw.match(/^(v-bind:|:)([^.]+)(?:\.([^.]+))?$/i) ?? [];
153
+ if (directive && potentialName) {
154
+ if (this.duplicatableAttrs.has(potentialName.toLowerCase())) {
155
+ this.updateAttr(attr, { isDuplicatable: true });
156
+ }
157
+ if (!modifier) {
158
+ return {
159
+ ...attr,
160
+ potentialName,
161
+ isDynamicValue: true,
162
+ };
163
+ }
164
+ switch (modifier) {
165
+ case '.attr': {
166
+ return {
167
+ ...attr,
168
+ potentialName,
169
+ isDynamicValue: true,
170
+ };
171
+ }
172
+ /* eslint-disable unicorn/no-useless-switch-case */
173
+ case '.prop':
174
+ case '.camel':
175
+ default: {
176
+ const name = `v-bind:${potentialName}${modifier ?? ''}`;
177
+ return {
178
+ ...attr,
179
+ potentialName: attr.name.raw === name ? undefined : name,
180
+ isDirective: true,
181
+ };
182
+ }
183
+ /* eslint-enable unicorn/no-useless-switch-case */
184
+ }
185
+ }
186
+ }
187
+ {
188
+ /**
189
+ * `v-model`
190
+ */
191
+ const [, directive] = attr.name.raw.match(/^(v-model)(?:\.([^.]+))?$/i) ?? [];
192
+ if (directive) {
193
+ return {
194
+ ...attr,
195
+ isDirective: true,
196
+ };
197
+ }
198
+ }
199
+ {
200
+ /**
201
+ * `v-slot`
202
+ */
203
+ const slotName = (attr.name.raw.match(/^(v-slot:|#)(.+)$/i) ?? [])[2];
204
+ const name = `v-slot:${slotName}`;
205
+ if (slotName) {
206
+ return {
207
+ ...attr,
208
+ potentialName: attr.name.raw === name ? undefined : name,
209
+ isDirective: true,
210
+ };
211
+ }
212
+ }
213
+ /**
214
+ * If directives
215
+ */
216
+ if (attr.name.raw.startsWith('v-')) {
217
+ return {
218
+ ...attr,
219
+ isDirective: true,
220
+ };
221
+ }
222
+ return attr;
223
+ }
224
+ detectElementType(nodeName) {
225
+ return super.detectElementType(nodeName, [
226
+ 'component',
227
+ 'slot',
228
+ 'Transition',
229
+ 'TransitionGroup',
230
+ 'KeepAlive',
231
+ 'Teleport',
232
+ 'Suspense',
233
+ // Backward compatibility
234
+ /^[A-Z]/,
235
+ ]);
236
+ }
237
+ }
238
+ export const parser = new VueParser();
@@ -1,7 +1,5 @@
1
1
  import * as VueESLintParser from 'vue-eslint-parser';
2
2
  export type VueTokens = VueESLintParser.AST.ESLintProgram;
3
- export default function vueParse(vueTemplate: string): VueTokens;
4
- export type ASTNode =
5
- | VueESLintParser.AST.VElement
6
- | VueESLintParser.AST.VText
7
- | VueESLintParser.AST.VExpressionContainer;
3
+ export declare function vueParse(vueTemplate: string): VueTokens;
4
+ export type ASTNode = VueESLintParser.AST.VElement | VueESLintParser.AST.VText | VueESLintParser.AST.VExpressionContainer;
5
+ export type ASTComment = VueESLintParser.AST.Token & VueESLintParser.AST.HasLocation;
@@ -1,9 +1,5 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const tslib_1 = require("tslib");
4
- const VueESLintParser = tslib_1.__importStar(require("vue-eslint-parser"));
5
- function vueParse(vueTemplate) {
1
+ import * as VueESLintParser from 'vue-eslint-parser';
2
+ export function vueParse(vueTemplate) {
6
3
  const ast = VueESLintParser.parse(vueTemplate, { parser: false });
7
4
  return ast;
8
5
  }
9
- exports.default = vueParse;
package/package.json CHANGED
@@ -1,12 +1,17 @@
1
1
  {
2
2
  "name": "@markuplint/vue-parser",
3
- "version": "3.12.0",
3
+ "version": "4.0.0-alpha.10",
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
8
  "private": false,
9
- "main": "lib/index.js",
9
+ "type": "module",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./lib/index.js"
13
+ }
14
+ },
10
15
  "types": "lib/index.d.ts",
11
16
  "publishConfig": {
12
17
  "access": "public"
@@ -16,11 +21,10 @@
16
21
  "clean": "tsc --build --clean"
17
22
  },
18
23
  "dependencies": {
19
- "@markuplint/html-parser": "3.13.0",
20
- "@markuplint/ml-ast": "3.2.0",
21
- "@markuplint/parser-utils": "3.13.0",
22
- "tslib": "^2.6.2",
23
- "vue-eslint-parser": "^9.3.2"
24
+ "@markuplint/html-parser": "4.0.0-alpha.10",
25
+ "@markuplint/ml-ast": "4.0.0-alpha.10",
26
+ "@markuplint/parser-utils": "4.0.0-alpha.10",
27
+ "vue-eslint-parser": "^9.4.2"
24
28
  },
25
- "gitHead": "b37b749d7ac0f9e6cbd022ee7031bc020c6677d3"
29
+ "gitHead": "b41153ea665aa8f091daf6114a06047f4ccb8350"
26
30
  }
package/lib/attr.d.ts DELETED
@@ -1,2 +0,0 @@
1
- import type { MLASTAttr } from '@markuplint/ml-ast';
2
- export declare function attr(attr: MLASTAttr): MLASTAttr;
package/lib/attr.js DELETED
@@ -1,113 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.attr = void 0;
4
- const duplicatableAttrs = ['class', 'style'];
5
- function attr(
6
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
7
- attr) {
8
- var _a, _b, _c, _d;
9
- if (attr.type !== 'html-attr') {
10
- const name = attr.potentialName;
11
- if (duplicatableAttrs.includes(name)) {
12
- attr.isDuplicatable = true;
13
- }
14
- return attr;
15
- }
16
- {
17
- /**
18
- * `v-on`
19
- */
20
- const [, directive, potentialName, modifier] = (_a = attr.name.raw.match(/^(v-on:|@)([^.]+)(?:\.([^.]+))?$/i)) !== null && _a !== void 0 ? _a : [];
21
- if (directive && potentialName) {
22
- return {
23
- ...attr,
24
- potentialName: `on${potentialName.toLowerCase()}`,
25
- isDynamicValue: true,
26
- // @ts-ignore
27
- _modifier: modifier,
28
- };
29
- }
30
- }
31
- {
32
- /**
33
- * `v-bind`
34
- */
35
- const [, directive, potentialName, modifier] = (_b = attr.name.raw.match(/^(v-bind:|:)([^.]+)(?:\.([^.]+))?$/i)) !== null && _b !== void 0 ? _b : [];
36
- if (directive && potentialName) {
37
- if (duplicatableAttrs.includes(potentialName.toLowerCase())) {
38
- attr.isDuplicatable = true;
39
- }
40
- if (!modifier) {
41
- return {
42
- ...attr,
43
- potentialName,
44
- isDynamicValue: true,
45
- };
46
- }
47
- switch (modifier) {
48
- case '.attr': {
49
- return {
50
- ...attr,
51
- potentialName,
52
- isDynamicValue: true,
53
- // @ts-ignore
54
- _modifier: modifier,
55
- };
56
- }
57
- // TODO: Supporting for `prop` and `camel` https://github.com/markuplint/markuplint/pull/681
58
- case '.prop':
59
- case '.camel':
60
- default: {
61
- const name = `v-bind:${potentialName}${modifier !== null && modifier !== void 0 ? modifier : ''}`;
62
- return {
63
- ...attr,
64
- potentialName: attr.name.raw !== name ? name : undefined,
65
- isDirective: true,
66
- // @ts-ignore
67
- _modifier: modifier,
68
- };
69
- }
70
- }
71
- }
72
- }
73
- {
74
- /**
75
- * `v-model`
76
- */
77
- const [, directive, modifier] = (_c = attr.name.raw.match(/^(v-model)(?:\.([^.]+))?$/i)) !== null && _c !== void 0 ? _c : [];
78
- if (directive) {
79
- // TODO: Supporting for `v-model` https://github.com/markuplint/markuplint/pull/681
80
- return {
81
- ...attr,
82
- isDirective: true,
83
- // @ts-ignore
84
- _modifier: modifier,
85
- };
86
- }
87
- }
88
- {
89
- /**
90
- * `v-slot`
91
- */
92
- const [, , slotName] = (_d = attr.name.raw.match(/^(v-slot:|#)(.+)$/i)) !== null && _d !== void 0 ? _d : [];
93
- const name = `v-slot:${slotName}`;
94
- if (slotName) {
95
- return {
96
- ...attr,
97
- potentialName: attr.name.raw !== name ? name : undefined,
98
- isDirective: true,
99
- };
100
- }
101
- }
102
- /**
103
- * If directives
104
- */
105
- if (/^v-/.test(attr.name.raw)) {
106
- return {
107
- ...attr,
108
- isDirective: true,
109
- };
110
- }
111
- return attr;
112
- }
113
- exports.attr = attr;
package/lib/parse.d.ts DELETED
@@ -1,2 +0,0 @@
1
- import type { Parse } from '@markuplint/ml-ast';
2
- export declare const parse: Parse;
package/lib/parse.js DELETED
@@ -1,213 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parse = void 0;
4
- const tslib_1 = require("tslib");
5
- const html_parser_1 = require("@markuplint/html-parser");
6
- const parser_utils_1 = require("@markuplint/parser-utils");
7
- const attr_1 = require("./attr");
8
- const vue_parser_1 = tslib_1.__importDefault(require("./vue-parser"));
9
- const parse = (rawCode, options) => {
10
- let ast;
11
- try {
12
- ast = (0, vue_parser_1.default)(rawCode);
13
- }
14
- catch (err) {
15
- if (err instanceof SyntaxError && 'lineNumber' in err && 'column' in err) {
16
- throw new parser_utils_1.ParserError(
17
- // @ts-ignore
18
- err.message, {
19
- // @ts-ignore
20
- line: err.lineNumber,
21
- // @ts-ignore
22
- col: err.column,
23
- raw: '',
24
- });
25
- }
26
- return {
27
- nodeList: [],
28
- isFragment: true,
29
- parseError: err instanceof Error ? err.message : new Error(`${err}`).message,
30
- };
31
- }
32
- const nodeList = ast.templateBody
33
- ? (0, parser_utils_1.flattenNodes)(traverse(ast.templateBody, null, rawCode, options), rawCode)
34
- : [];
35
- // Remove `</template>`
36
- const templateEndTagIndex = nodeList.findIndex(node => /\s*<\/\s*template\s*>\s*/i.test(node.raw));
37
- if (templateEndTagIndex !== -1) {
38
- const templateEndTag = nodeList[templateEndTagIndex];
39
- for (const node of nodeList) {
40
- if (node.nextNode && node.nextNode.uuid === (templateEndTag === null || templateEndTag === void 0 ? void 0 : templateEndTag.uuid)) {
41
- node.nextNode = null;
42
- }
43
- }
44
- nodeList.splice(templateEndTagIndex, 1);
45
- }
46
- return {
47
- nodeList,
48
- isFragment: true,
49
- };
50
- };
51
- exports.parse = parse;
52
- function traverse(
53
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
54
- rootNode, parentNode = null, rawHtml, options) {
55
- const nodeList = [];
56
- if (rootNode.type !== 'VElement') {
57
- return [];
58
- }
59
- let prevNode = null;
60
- for (const vNode of rootNode.children) {
61
- const node = nodeize(vNode, prevNode, parentNode, rawHtml, options);
62
- if (!node) {
63
- continue;
64
- }
65
- if (prevNode) {
66
- if (node.type !== 'endtag') {
67
- prevNode.nextNode = node;
68
- }
69
- node.prevNode = prevNode;
70
- }
71
- prevNode = node;
72
- nodeList.push(node);
73
- }
74
- return nodeList;
75
- }
76
- function nodeize(
77
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
78
- originNode,
79
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
80
- prevNode,
81
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
82
- parentNode, rawHtml, options) {
83
- const nextNode = null;
84
- const startOffset = originNode.range[0];
85
- const endOffset = originNode.range[1];
86
- const startLine = originNode.loc.start.line;
87
- const endLine = originNode.loc.end.line;
88
- const startCol = originNode.loc.start.column + 1;
89
- const endCol = originNode.loc.end.column + 1;
90
- const raw = rawHtml.slice(startOffset, endOffset !== null && endOffset !== void 0 ? endOffset : startOffset);
91
- switch (originNode.type) {
92
- case 'VText': {
93
- const node = {
94
- uuid: (0, parser_utils_1.uuid)(),
95
- raw,
96
- startOffset,
97
- endOffset,
98
- startLine,
99
- endLine,
100
- startCol,
101
- endCol,
102
- nodeName: '#text',
103
- type: 'text',
104
- parentNode,
105
- prevNode,
106
- nextNode,
107
- isFragment: false,
108
- isGhost: false,
109
- };
110
- return node;
111
- }
112
- case 'VExpressionContainer': {
113
- return {
114
- uuid: (0, parser_utils_1.uuid)(),
115
- raw,
116
- startOffset,
117
- endOffset,
118
- startLine,
119
- endLine,
120
- startCol,
121
- endCol,
122
- nodeName: '#vue-expression-container',
123
- type: 'psblock',
124
- parentNode,
125
- prevNode,
126
- nextNode,
127
- isFragment: false,
128
- isGhost: false,
129
- };
130
- }
131
- default: {
132
- const tagLoc = originNode.startTag;
133
- const startTagRaw = rawHtml.slice(tagLoc.range[0], tagLoc.range[1]);
134
- const tagTokens = (0, html_parser_1.parseRawTag)(startTagRaw, startLine, startCol, startOffset);
135
- const tagName = tagTokens.tagName;
136
- let endTag = null;
137
- const endTagLoc = originNode.endTag;
138
- if (endTagLoc) {
139
- const endTagRaw = rawHtml.slice(endTagLoc.range[0], endTagLoc.range[1]);
140
- const endTagTokens = (0, html_parser_1.parseRawTag)(endTagRaw, endTagLoc.loc.start.line, endTagLoc.loc.start.column, endTagLoc.range[0]);
141
- const endTagName = endTagTokens.tagName;
142
- endTag = {
143
- uuid: (0, parser_utils_1.uuid)(),
144
- raw: endTagRaw,
145
- startOffset: endTagLoc.range[0],
146
- endOffset: endTagLoc.range[1],
147
- startLine: endTagLoc.loc.start.line,
148
- endLine: endTagLoc.loc.end.line,
149
- startCol: endTagLoc.loc.start.column + 1,
150
- endCol: endTagLoc.loc.end.column + 1,
151
- nodeName: endTagName,
152
- type: 'endtag',
153
- namespace: originNode.namespace,
154
- attributes: endTagTokens.attrs,
155
- parentNode,
156
- prevNode,
157
- nextNode,
158
- pearNode: null,
159
- isFragment: false,
160
- isGhost: false,
161
- tagOpenChar: '</',
162
- tagCloseChar: '>',
163
- };
164
- }
165
- const startTag = {
166
- uuid: (0, parser_utils_1.uuid)(),
167
- raw: startTagRaw,
168
- startOffset,
169
- endOffset: startOffset + startTagRaw.length,
170
- startLine,
171
- endLine: (0, parser_utils_1.getEndLine)(startTagRaw, startLine),
172
- startCol,
173
- endCol: (0, parser_utils_1.getEndCol)(startTagRaw, startCol),
174
- nodeName: tagName,
175
- type: 'starttag',
176
- namespace: originNode.namespace,
177
- elementType: (0, parser_utils_1.detectElementType)(tagName, options === null || options === void 0 ? void 0 : options.authoredElementName,
178
- /**
179
- * @see https://vuejs.org/api/built-in-components.html
180
- * @see https://vuejs.org/api/built-in-special-elements.html
181
- */
182
- [
183
- 'component',
184
- 'slot',
185
- 'Transition',
186
- 'TransitionGroup',
187
- 'KeepAlive',
188
- 'Teleport',
189
- 'Suspense',
190
- // Backward compatibility
191
- /^[A-Z]/,
192
- ]),
193
- attributes: tagTokens.attrs.map(attr_1.attr),
194
- hasSpreadAttr: false,
195
- parentNode,
196
- prevNode,
197
- nextNode,
198
- pearNode: endTag,
199
- selfClosingSolidus: tagTokens.selfClosingSolidus,
200
- endSpace: tagTokens.endSpace,
201
- isFragment: false,
202
- isGhost: false,
203
- tagOpenChar: '<',
204
- tagCloseChar: '>',
205
- };
206
- if (endTag) {
207
- endTag.pearNode = startTag;
208
- }
209
- startTag.childNodes = traverse(originNode, startTag, rawHtml, options);
210
- return startTag;
211
- }
212
- }
213
- }
@@ -1,455 +0,0 @@
1
- const { nodeListToDebugMaps } = require('@markuplint/parser-utils');
2
-
3
- const { parse } = require('../lib/');
4
-
5
- describe('parser', () => {
6
- it('empty', () => {
7
- const doc = parse('');
8
- expect(doc.nodeList).toStrictEqual([]);
9
- expect(doc.nodeList.length).toBe(0);
10
- });
11
-
12
- it('syntax error', () => {
13
- expect(() => {
14
- parse('"');
15
- }).toThrow('Unterminated string constant');
16
- });
17
-
18
- it('silent syntax error', () => {
19
- const doc = parse('<!--');
20
- expect(doc.nodeList).toStrictEqual([]);
21
- expect(doc.nodeList.length).toBe(0);
22
- });
23
-
24
- it('template empty', () => {
25
- const doc = parse('<template></template>');
26
- expect(doc.nodeList).toStrictEqual([]);
27
- expect(doc.nodeList.length).toBe(0);
28
- });
29
-
30
- it('<div />', () => {
31
- const doc = parse('<template><div /></template>');
32
- expect(doc.nodeList[0].nodeName).toBe('div');
33
- expect(doc.nodeList.length).toBe(1);
34
- });
35
-
36
- it('<div></div>', () => {
37
- const doc = parse('<template><div></div></template>');
38
- expect(doc.nodeList[0].nodeName).toBe('div');
39
- expect(doc.nodeList[1].nodeName).toBe('div');
40
- expect(doc.nodeList.length).toBe(2);
41
- });
42
-
43
- it('text only', () => {
44
- const doc = parse('<template>text</template>');
45
- expect(doc.nodeList[0].nodeName).toBe('#text');
46
- expect(doc.nodeList[0].raw).toBe('text');
47
- expect(doc.nodeList.length).toBe(1);
48
- });
49
-
50
- it('fragments', () => {
51
- const doc = parse('<template><header></header><main></main><footer></footer></template>');
52
- expect(doc.nodeList[0].nodeName).toBe('header');
53
- expect(doc.nodeList[1].nodeName).toBe('header');
54
- expect(doc.nodeList[2].nodeName).toBe('main');
55
- expect(doc.nodeList[3].nodeName).toBe('main');
56
- expect(doc.nodeList[4].nodeName).toBe('footer');
57
- expect(doc.nodeList[5].nodeName).toBe('footer');
58
- expect(doc.nodeList.length).toBe(6);
59
- });
60
-
61
- it('standard code', () => {
62
- const doc = parse(`
63
- <template>
64
- <script>
65
- const i = 0;
66
- </script>
67
- <!comment-node>
68
- {{ CodeExpression }}
69
- <div>
70
- text&amp;div
71
- </div>
72
- <table>
73
- <tr>
74
- <th>header</th>
75
- <td>cell</td>
76
- </tr>
77
- </table>
78
- <table>
79
- <tbody>
80
- <tr>
81
- <th>header</th>
82
- <td>cell</td>
83
- </tr>
84
- </tbody>
85
- </table>
86
- <img src="path/to" />
87
- invalid-indent
88
-
89
- <?template engine;
90
- $var = '<html attr="value">text</html>'
91
- ?>
92
-
93
- <%template engine;
94
- $var = '<html attr="value">text</html>'
95
- %>
96
-
97
- </expected>
98
- <div>
99
- text-node
100
- </template>
101
- `);
102
- const map = nodeListToDebugMaps(doc.nodeList);
103
- expect(map).toStrictEqual([
104
- '[2:12]>[3:3](12,15)#text: ⏎→→',
105
- '[3:3]>[3:11](15,23)script: <script>',
106
- '[3:11]>[5:3](23,42)#text: ⏎→→→const␣i␣=␣0;⏎→→',
107
- '[5:3]>[5:12](42,51)script: </script>',
108
- '[5:12]>[7:3](51,72)#text: ⏎→→⏎→→',
109
- '[7:3]>[7:23](72,92)#vue-expression-container: {{␣CodeExpression␣}}',
110
- '[7:23]>[8:3](92,95)#text: ⏎→→',
111
- '[8:3]>[8:8](95,100)div: <div>',
112
- '[8:8]>[10:3](100,119)#text: ⏎→→→text&amp;div⏎→→',
113
- '[10:3]>[10:9](119,125)div: </div>',
114
- '[10:9]>[11:3](125,128)#text: ⏎→→',
115
- '[11:3]>[11:10](128,135)table: <table>',
116
- '[11:10]>[12:4](135,139)#text: ⏎→→→',
117
- '[12:4]>[12:8](139,143)tr: <tr>',
118
- '[12:8]>[13:5](143,148)#text: ⏎→→→→',
119
- '[13:5]>[13:9](148,152)th: <th>',
120
- '[13:9]>[13:15](152,158)#text: header',
121
- '[13:15]>[13:20](158,163)th: </th>',
122
- '[13:20]>[14:5](163,168)#text: ⏎→→→→',
123
- '[14:5]>[14:9](168,172)td: <td>',
124
- '[14:9]>[14:13](172,176)#text: cell',
125
- '[14:13]>[14:18](176,181)td: </td>',
126
- '[14:18]>[15:4](181,185)#text: ⏎→→→',
127
- '[15:4]>[15:9](185,190)tr: </tr>',
128
- '[15:9]>[16:3](190,193)#text: ⏎→→',
129
- '[16:3]>[16:11](193,201)table: </table>',
130
- '[16:11]>[17:3](201,204)#text: ⏎→→',
131
- '[17:3]>[17:10](204,211)table: <table>',
132
- '[17:10]>[18:4](211,215)#text: ⏎→→→',
133
- '[18:4]>[18:11](215,222)tbody: <tbody>',
134
- '[18:11]>[19:5](222,227)#text: ⏎→→→→',
135
- '[19:5]>[19:9](227,231)tr: <tr>',
136
- '[19:9]>[20:6](231,237)#text: ⏎→→→→→',
137
- '[20:6]>[20:10](237,241)th: <th>',
138
- '[20:10]>[20:16](241,247)#text: header',
139
- '[20:16]>[20:21](247,252)th: </th>',
140
- '[20:21]>[21:6](252,258)#text: ⏎→→→→→',
141
- '[21:6]>[21:10](258,262)td: <td>',
142
- '[21:10]>[21:14](262,266)#text: cell',
143
- '[21:14]>[21:19](266,271)td: </td>',
144
- '[21:19]>[22:5](271,276)#text: ⏎→→→→',
145
- '[22:5]>[22:10](276,281)tr: </tr>',
146
- '[22:10]>[23:4](281,285)#text: ⏎→→→',
147
- '[23:4]>[23:12](285,293)tbody: </tbody>',
148
- '[23:12]>[24:3](293,296)#text: ⏎→→',
149
- '[24:3]>[24:11](296,304)table: </table>',
150
- '[24:11]>[25:3](304,307)#text: ⏎→→',
151
- '[25:3]>[25:24](307,328)img: <img␣src="path/to"␣/>',
152
- "[25:24]>[33:12](328,451)#text: ⏎→→→→invalid-indent⏎⏎→→text'⏎→→?>⏎⏎→→<%template␣engine;⏎→→→$var␣=␣'",
153
- '[33:12]>[33:31](451,470)html: <html␣attr="value">',
154
- '[33:31]>[33:35](470,474)#text: text',
155
- '[33:35]>[33:42](474,481)html: </html>',
156
- "[33:42]>[37:3](481,505)#text: '⏎→→%>⏎⏎→→⏎→→",
157
- '[37:3]>[37:8](505,510)div: <div>',
158
- '[37:8]>[39:2](510,523)#text: ⏎→text-node⏎→',
159
- ]);
160
- });
161
-
162
- it('<template>', () => {
163
- const doc = parse(`
164
- <template>
165
- <script>
166
- const i = 0;
167
- </script>
168
- <!comment-node>
169
- <!-- html-comment -->
170
- <div>
171
- text&amp;div
172
- </div>
173
- <table>
174
- <tr>
175
- <th>header</th>
176
- <td>cell</td>
177
- </tr>
178
- </table>
179
- <table>
180
- <tbody>
181
- <tr>
182
- <th>header</th>
183
- <td>cell</td>
184
- </tr>
185
- </tbody>
186
- </table>
187
- <img src="path/to" />
188
- invalid-indent
189
-
190
- <?template engine;
191
- $var = '<html attr="value">text</html>'
192
- ?>
193
-
194
- <%template engine;
195
- $var = '<html attr="value">text</html>'
196
- %>
197
-
198
- </expected>
199
- <div>
200
- text-node
201
- </template>
202
- `);
203
- const map = nodeListToDebugMaps(doc.nodeList);
204
- expect(map).toStrictEqual([
205
- '[2:12]>[3:3](12,15)#text: ⏎→→',
206
- '[3:3]>[3:11](15,23)script: <script>',
207
- '[3:11]>[5:3](23,42)#text: ⏎→→→const␣i␣=␣0;⏎→→',
208
- '[5:3]>[5:12](42,51)script: </script>',
209
- '[5:12]>[8:3](51,96)#text: ⏎→→⏎→→⏎→→',
210
- '[8:3]>[8:8](96,101)div: <div>',
211
- '[8:8]>[10:3](101,120)#text: ⏎→→→text&amp;div⏎→→',
212
- '[10:3]>[10:9](120,126)div: </div>',
213
- '[10:9]>[11:3](126,129)#text: ⏎→→',
214
- '[11:3]>[11:10](129,136)table: <table>',
215
- '[11:10]>[12:4](136,140)#text: ⏎→→→',
216
- '[12:4]>[12:8](140,144)tr: <tr>',
217
- '[12:8]>[13:5](144,149)#text: ⏎→→→→',
218
- '[13:5]>[13:9](149,153)th: <th>',
219
- '[13:9]>[13:15](153,159)#text: header',
220
- '[13:15]>[13:20](159,164)th: </th>',
221
- '[13:20]>[14:5](164,169)#text: ⏎→→→→',
222
- '[14:5]>[14:9](169,173)td: <td>',
223
- '[14:9]>[14:13](173,177)#text: cell',
224
- '[14:13]>[14:18](177,182)td: </td>',
225
- '[14:18]>[15:4](182,186)#text: ⏎→→→',
226
- '[15:4]>[15:9](186,191)tr: </tr>',
227
- '[15:9]>[16:3](191,194)#text: ⏎→→',
228
- '[16:3]>[16:11](194,202)table: </table>',
229
- '[16:11]>[17:3](202,205)#text: ⏎→→',
230
- '[17:3]>[17:10](205,212)table: <table>',
231
- '[17:10]>[18:4](212,216)#text: ⏎→→→',
232
- '[18:4]>[18:11](216,223)tbody: <tbody>',
233
- '[18:11]>[19:5](223,228)#text: ⏎→→→→',
234
- '[19:5]>[19:9](228,232)tr: <tr>',
235
- '[19:9]>[20:6](232,238)#text: ⏎→→→→→',
236
- '[20:6]>[20:10](238,242)th: <th>',
237
- '[20:10]>[20:16](242,248)#text: header',
238
- '[20:16]>[20:21](248,253)th: </th>',
239
- '[20:21]>[21:6](253,259)#text: ⏎→→→→→',
240
- '[21:6]>[21:10](259,263)td: <td>',
241
- '[21:10]>[21:14](263,267)#text: cell',
242
- '[21:14]>[21:19](267,272)td: </td>',
243
- '[21:19]>[22:5](272,277)#text: ⏎→→→→',
244
- '[22:5]>[22:10](277,282)tr: </tr>',
245
- '[22:10]>[23:4](282,286)#text: ⏎→→→',
246
- '[23:4]>[23:12](286,294)tbody: </tbody>',
247
- '[23:12]>[24:3](294,297)#text: ⏎→→',
248
- '[24:3]>[24:11](297,305)table: </table>',
249
- '[24:11]>[25:3](305,308)#text: ⏎→→',
250
- '[25:3]>[25:24](308,329)img: <img␣src="path/to"␣/>',
251
- "[25:24]>[33:12](329,452)#text: ⏎→→→→invalid-indent⏎⏎→→text'⏎→→?>⏎⏎→→<%template␣engine;⏎→→→$var␣=␣'",
252
- '[33:12]>[33:31](452,471)html: <html␣attr="value">',
253
- '[33:31]>[33:35](471,475)#text: text',
254
- '[33:35]>[33:42](475,482)html: </html>',
255
- "[33:42]>[37:3](482,506)#text: '⏎→→%>⏎⏎→→⏎→→",
256
- '[37:3]>[37:8](506,511)div: <div>',
257
- '[37:8]>[39:2](511,524)#text: ⏎→text-node⏎→',
258
- ]);
259
- });
260
-
261
- it('<noscript>', () => {
262
- const doc = parse(`
263
- <template>
264
- <noscript>
265
- <div>test</div>
266
- <expected>
267
- </expected2>
268
- </noscript>
269
- </template>
270
- `);
271
- const map = nodeListToDebugMaps(doc.nodeList);
272
- expect(map).toStrictEqual([
273
- '[2:12]>[3:2](12,14)#text: ⏎→',
274
- '[3:2]>[3:12](14,24)noscript: <noscript>',
275
- '[3:12]>[7:2](24,72)#text: ⏎→→<div>test</div>⏎→→<expected>⏎→→</expected2>⏎→',
276
- '[7:2]>[7:13](72,83)noscript: </noscript>',
277
- '[7:13]>[8:2](83,85)#text: ⏎→',
278
- ]);
279
- });
280
-
281
- it('UUID', () => {
282
- const doc = parse(
283
- '<template><x-wrap><x-before><span>title</span></x-before><x-after><div>test</div></x-after></x-wrap></template>',
284
- );
285
- // const map = nodeListToDebugMaps(doc.nodeList);
286
- // console.log(map);
287
- // console.log(doc.nodeList.map((n, i) => `${i}: ${n.uuid} ${n.raw.trim()}`));
288
-
289
- // <x-wrap>
290
- expect(doc.nodeList[0].parentNode).toEqual(null);
291
- expect(doc.nodeList[0].prevNode).toEqual(null);
292
- expect(doc.nodeList[0].nextNode).toEqual(null);
293
- // @ts-ignore
294
- expect(doc.nodeList[0].pearNode.uuid).toEqual(doc.nodeList[11].uuid);
295
-
296
- // </x-wrap>
297
- expect(doc.nodeList[11].parentNode).toEqual(null);
298
- // @ts-ignore
299
- expect(doc.nodeList[11].pearNode.uuid).toEqual(doc.nodeList[0].uuid);
300
-
301
- // <x-before>
302
- // @ts-ignore
303
- expect(doc.nodeList[1].parentNode.uuid).toEqual(doc.nodeList[0].uuid);
304
- // @ts-ignore
305
- expect(doc.nodeList[1].prevNode).toEqual(null);
306
- // @ts-ignore
307
- expect(doc.nodeList[1].nextNode.uuid).toEqual(doc.nodeList[6].uuid);
308
- // @ts-ignore
309
- expect(doc.nodeList[1].pearNode.uuid).toEqual(doc.nodeList[5].uuid);
310
-
311
- // </x-before>
312
- // @ts-ignore
313
- expect(doc.nodeList[5].parentNode.uuid).toEqual(doc.nodeList[0].uuid);
314
- // @ts-ignore
315
- expect(doc.nodeList[5].pearNode.uuid).toEqual(doc.nodeList[1].uuid);
316
-
317
- // <x-after>
318
- // @ts-ignore
319
- expect(doc.nodeList[6].parentNode.uuid).toEqual(doc.nodeList[0].uuid);
320
- // @ts-ignore
321
- expect(doc.nodeList[6].prevNode.uuid).toEqual(doc.nodeList[1].uuid);
322
- // @ts-ignore
323
- expect(doc.nodeList[6].nextNode).toEqual(null);
324
- // @ts-ignore
325
- expect(doc.nodeList[6].pearNode.uuid).toEqual(doc.nodeList[10].uuid);
326
-
327
- // </x-after>
328
- // @ts-ignore
329
- expect(doc.nodeList[10].parentNode.uuid).toEqual(doc.nodeList[0].uuid);
330
- // @ts-ignore
331
- expect(doc.nodeList[10].pearNode.uuid).toEqual(doc.nodeList[6].uuid);
332
- });
333
-
334
- it('attributes', () => {
335
- const doc = parse(
336
- '<template><div v-if="bool" data-attr v-bind:data-attr2="variable" @click.once="event" v-on:click.foobar="event"></div></template>',
337
- );
338
- // @ts-ignore
339
- expect(doc.nodeList[0].attributes[0].raw).toBe('v-if="bool"');
340
- // @ts-ignore
341
- expect(doc.nodeList[0].attributes[0].isDirective).toBeTruthy();
342
- // @ts-ignore
343
- expect(doc.nodeList[0].attributes[1].raw).toBe('data-attr');
344
- // @ts-ignore
345
- expect(doc.nodeList[0].attributes[1].isDirective).toBeUndefined();
346
- // @ts-ignore
347
- expect(doc.nodeList[0].attributes[2].raw).toBe('v-bind:data-attr2="variable"');
348
- // @ts-ignore
349
- expect(doc.nodeList[0].attributes[2].potentialName).toBe('data-attr2');
350
- // @ts-ignore
351
- expect(doc.nodeList[0].attributes[3].isDirective).toBeFalsy();
352
- // @ts-ignore
353
- expect(doc.nodeList[0].attributes[4].isDirective).toBeFalsy();
354
- });
355
-
356
- it('namespace', () => {
357
- const doc = parse('<template><div><svg><text /></svg></div></template>');
358
- expect(doc.nodeList[0].nodeName).toBe('div');
359
- expect(doc.nodeList[0].namespace).toBe('http://www.w3.org/1999/xhtml');
360
- expect(doc.nodeList[1].nodeName).toBe('svg');
361
- expect(doc.nodeList[1].namespace).toBe('http://www.w3.org/2000/svg');
362
- expect(doc.nodeList[2].nodeName).toBe('text');
363
- expect(doc.nodeList[2].namespace).toBe('http://www.w3.org/2000/svg');
364
- });
365
-
366
- it('elementType', () => {
367
- expect(parse('<template><div/></template>').nodeList[0].elementType).toBe('html');
368
- expect(parse('<template><Div/></template>').nodeList[0].elementType).toBe('authored');
369
- expect(parse('<template><x-div/></template>').nodeList[0].elementType).toBe('web-component');
370
- expect(parse('<template><foo/></template>').nodeList[0].elementType).toBe('html');
371
- expect(parse('<template><Foo/></template>').nodeList[0].elementType).toBe('authored');
372
- expect(parse('<template><div><Foo/></div></template>').nodeList[1].elementType).toBe('authored');
373
- expect(parse('<template><div><Component/></div></template>').nodeList[1].elementType).toBe('authored');
374
- expect(parse('<template><svg><Component/></svg></template>').nodeList[1].elementType).toBe('authored');
375
- expect(parse('<template><svg><feBlend/></svg></template>').nodeList[1].elementType).toBe('html');
376
- expect(parse('<template><component/></template>').nodeList[0].elementType).toBe('authored');
377
- expect(parse('<template><slot/></template>').nodeList[0].elementType).toBe('authored');
378
- expect(parse('<template><Transition/></template>').nodeList[0].elementType).toBe('authored');
379
- expect(parse('<template><transition/></template>').nodeList[0].elementType).toBe('html');
380
- });
381
-
382
- it('Directives', () => {
383
- const doc = parse(`
384
- <template>
385
- <div>
386
- <template #header></template>
387
- </div>
388
- </template>
389
- `);
390
- const map = nodeListToDebugMaps(doc.nodeList, true);
391
- expect(map).toStrictEqual([
392
- '[2:12]>[3:3](12,15)#text: ⏎→→',
393
- '[3:3]>[3:8](15,20)div: <div>',
394
- '[3:8]>[4:4](20,24)#text: ⏎→→→',
395
- '[4:4]>[4:22](24,42)template: <template␣#header>',
396
- '[4:14]>[4:21](34,41)v-slot:header: #header',
397
- ' [4:13]>[4:14](33,34)bN: ␣',
398
- ' [4:14]>[4:21](34,41)name: #header',
399
- ' [4:21]>[4:21](41,41)bE: ',
400
- ' [4:21]>[4:21](41,41)equal: ',
401
- ' [4:21]>[4:21](41,41)aE: ',
402
- ' [4:21]>[4:21](41,41)sQ: ',
403
- ' [4:21]>[4:21](41,41)value: ',
404
- ' [4:21]>[4:21](41,41)eQ: ',
405
- ' isDirective: true',
406
- ' isDynamicValue: false',
407
- ' potentialName: v-slot:header',
408
- '[4:33]>[5:3](53,56)#text: ⏎→→',
409
- '[5:3]>[5:9](56,62)div: </div>',
410
- '[5:9]>[6:2](62,64)#text: ⏎→',
411
- ]);
412
- });
413
- });
414
-
415
- describe('Issues', () => {
416
- test('#783', () => {
417
- const doc = parse(`
418
- <template>
419
- <button @click.stop="foo" v-on:click.stop="bar">...</button>
420
- </template>
421
- `);
422
- const map = nodeListToDebugMaps(doc.nodeList, true);
423
- expect(map).toStrictEqual([
424
- '[2:12]>[3:3](12,15)#text: ⏎→→',
425
- '[3:3]>[3:51](15,63)button: <button␣@click.stop="foo"␣v-on:click.stop="bar">',
426
- '[3:11]>[3:28](23,40)onclick: @click.stop="foo"',
427
- ' [3:10]>[3:11](22,23)bN: ␣',
428
- ' [3:11]>[3:22](23,34)name: @click.stop',
429
- ' [3:22]>[3:22](34,34)bE: ',
430
- ' [3:22]>[3:23](34,35)equal: =',
431
- ' [3:23]>[3:23](35,35)aE: ',
432
- ' [3:23]>[3:24](35,36)sQ: "',
433
- ' [3:24]>[3:27](36,39)value: foo',
434
- ' [3:27]>[3:28](39,40)eQ: "',
435
- ' isDirective: false',
436
- ' isDynamicValue: true',
437
- ' potentialName: onclick',
438
- '[3:29]>[3:50](41,62)onclick: v-on:click.stop="bar"',
439
- ' [3:28]>[3:29](40,41)bN: ␣',
440
- ' [3:29]>[3:44](41,56)name: v-on:click.stop',
441
- ' [3:44]>[3:44](56,56)bE: ',
442
- ' [3:44]>[3:45](56,57)equal: =',
443
- ' [3:45]>[3:45](57,57)aE: ',
444
- ' [3:45]>[3:46](57,58)sQ: "',
445
- ' [3:46]>[3:49](58,61)value: bar',
446
- ' [3:49]>[3:50](61,62)eQ: "',
447
- ' isDirective: false',
448
- ' isDynamicValue: true',
449
- ' potentialName: onclick',
450
- '[3:51]>[3:54](63,66)#text: ...',
451
- '[3:54]>[3:63](66,75)button: </button>',
452
- '[3:63]>[4:2](75,77)#text: ⏎→',
453
- ]);
454
- });
455
- });