@markuplint/vue-parser 4.0.0-dev.20 → 4.0.0-dev.23

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-2023 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.js';
2
- export declare const endTag = "xml";
1
+ export { parser } from './parser.js';
package/lib/index.js CHANGED
@@ -1,2 +1 @@
1
- export { parse } from './parse.js';
2
- export const 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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markuplint/vue-parser",
3
- "version": "4.0.0-dev.20+6b35da16",
3
+ "version": "4.0.0-dev.23+d6f2aa9bc",
4
4
  "description": "Vue parser for markuplint",
5
5
  "repository": "git@github.com:markuplint/markuplint.git",
6
6
  "author": "Yusuke Hirao <yusukehirao@me.com>",
@@ -21,10 +21,10 @@
21
21
  "clean": "tsc --build --clean"
22
22
  },
23
23
  "dependencies": {
24
- "@markuplint/html-parser": "4.0.0-dev.20+6b35da16",
25
- "@markuplint/ml-ast": "4.0.0-dev.20+6b35da16",
26
- "@markuplint/parser-utils": "4.0.0-dev.20+6b35da16",
27
- "vue-eslint-parser": "^9.3.1"
24
+ "@markuplint/html-parser": "4.0.0-dev.23+d6f2aa9bc",
25
+ "@markuplint/ml-ast": "4.0.0-dev.23+d6f2aa9bc",
26
+ "@markuplint/parser-utils": "4.0.0-dev.23+d6f2aa9bc",
27
+ "vue-eslint-parser": "^9.4.1"
28
28
  },
29
- "gitHead": "6b35da161d94f784953d0adecc2d28502052d92a"
29
+ "gitHead": "d6f2aa9bc287768466f23b5340e4e0eecfa30d59"
30
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,110 +0,0 @@
1
- const duplicatableAttrs = new Set(['class', 'style']);
2
- export function attr(
3
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
4
- attr) {
5
- if (attr.type !== 'html-attr') {
6
- const name = attr.potentialName;
7
- if (duplicatableAttrs.has(name)) {
8
- attr.isDuplicatable = true;
9
- }
10
- return attr;
11
- }
12
- {
13
- /**
14
- * `v-on`
15
- */
16
- const [, directive, potentialName, modifier] = attr.name.raw.match(/^(v-on:|@)([^.]+)(?:\.([^.]+))?$/i) ?? [];
17
- if (directive && potentialName) {
18
- return {
19
- ...attr,
20
- potentialName: `on${potentialName.toLowerCase()}`,
21
- isDynamicValue: true,
22
- // @ts-ignore
23
- _modifier: modifier,
24
- };
25
- }
26
- }
27
- {
28
- /**
29
- * `v-bind`
30
- */
31
- const [, directive, potentialName, modifier] = attr.name.raw.match(/^(v-bind:|:)([^.]+)(?:\.([^.]+))?$/i) ?? [];
32
- if (directive && potentialName) {
33
- if (duplicatableAttrs.has(potentialName.toLowerCase())) {
34
- attr.isDuplicatable = true;
35
- }
36
- if (!modifier) {
37
- return {
38
- ...attr,
39
- potentialName,
40
- isDynamicValue: true,
41
- };
42
- }
43
- switch (modifier) {
44
- case '.attr': {
45
- return {
46
- ...attr,
47
- potentialName,
48
- isDynamicValue: true,
49
- // @ts-ignore
50
- _modifier: modifier,
51
- };
52
- }
53
- /* eslint-disable unicorn/no-useless-switch-case */
54
- // TODO: Supporting for `prop` and `camel` https://github.com/markuplint/markuplint/pull/681
55
- case '.prop':
56
- case '.camel':
57
- default: {
58
- const name = `v-bind:${potentialName}${modifier ?? ''}`;
59
- return {
60
- ...attr,
61
- potentialName: attr.name.raw === name ? undefined : name,
62
- isDirective: true,
63
- // @ts-ignore
64
- _modifier: modifier,
65
- };
66
- }
67
- /* eslint-enable unicorn/no-useless-switch-case */
68
- }
69
- }
70
- }
71
- {
72
- /**
73
- * `v-model`
74
- */
75
- const [, directive, modifier] = attr.name.raw.match(/^(v-model)(?:\.([^.]+))?$/i) ?? [];
76
- if (directive) {
77
- // TODO: Supporting for `v-model` https://github.com/markuplint/markuplint/pull/681
78
- return {
79
- ...attr,
80
- isDirective: true,
81
- // @ts-ignore
82
- _modifier: modifier,
83
- };
84
- }
85
- }
86
- {
87
- /**
88
- * `v-slot`
89
- */
90
- const slotName = (attr.name.raw.match(/^(v-slot:|#)(.+)$/i) ?? [])[2];
91
- const name = `v-slot:${slotName}`;
92
- if (slotName) {
93
- return {
94
- ...attr,
95
- potentialName: attr.name.raw === name ? undefined : name,
96
- isDirective: true,
97
- };
98
- }
99
- }
100
- /**
101
- * If directives
102
- */
103
- if (attr.name.raw.startsWith('v-')) {
104
- return {
105
- ...attr,
106
- isDirective: true,
107
- };
108
- }
109
- return attr;
110
- }
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,242 +0,0 @@
1
- import { flattenNodes, getEndCol, getEndLine, uuid, ParserError, detectElementType, tagParser, } from '@markuplint/parser-utils';
2
- import { attr } from './attr.js';
3
- import { vueParse } from './vue-parser/index.js';
4
- export const parse = (rawCode, options) => {
5
- let ast;
6
- try {
7
- ast = vueParse(rawCode);
8
- }
9
- catch (error) {
10
- if (error instanceof SyntaxError && 'lineNumber' in error && 'column' in error) {
11
- throw new ParserError(
12
- // @ts-ignore
13
- error.message, {
14
- // @ts-ignore
15
- line: error.lineNumber,
16
- // @ts-ignore
17
- col: error.column,
18
- raw: '',
19
- });
20
- }
21
- return {
22
- nodeList: [],
23
- isFragment: true,
24
- parseError: error instanceof Error ? error.message : new Error(`${error}`).message,
25
- };
26
- }
27
- const nodeList = ast.templateBody
28
- ? flattenNodes(traverse(ast.templateBody, null, rawCode, ast.templateBody.comments, options), rawCode)
29
- : [];
30
- // Remove `</template>`
31
- const templateEndTagIndex = nodeList.findIndex(node => /\s*<\/\s*template\s*>\s*/i.test(node.raw));
32
- if (templateEndTagIndex !== -1) {
33
- const templateEndTag = nodeList[templateEndTagIndex];
34
- for (const node of nodeList) {
35
- if (node.nextNode && node.nextNode.uuid === templateEndTag?.uuid) {
36
- node.nextNode = null;
37
- }
38
- }
39
- nodeList.splice(templateEndTagIndex, 1);
40
- }
41
- return {
42
- nodeList,
43
- isFragment: true,
44
- };
45
- };
46
- function traverse(
47
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
48
- rootNode, parentNode = null, rawHtml,
49
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
50
- comments, options) {
51
- const nodeList = [];
52
- if (rootNode.type !== 'VElement') {
53
- return [];
54
- }
55
- let prevNode = null;
56
- for (const vNode of rootNode.children) {
57
- const node = nodeize(vNode, prevNode, parentNode, rawHtml, comments, options);
58
- if (!node) {
59
- continue;
60
- }
61
- const lastOffset = prevNode?.endOffset ?? parentNode?.endOffset ?? 0;
62
- const betweenComment = comments.find(comment => {
63
- return lastOffset <= comment.range[0] && comment.range[1] <= node.startOffset;
64
- });
65
- if (betweenComment) {
66
- const comment = {
67
- uuid: uuid(),
68
- raw: `<!--${betweenComment.value}-->`,
69
- startOffset: betweenComment.range[0],
70
- endOffset: betweenComment.range[1],
71
- startLine: betweenComment.loc.start.line,
72
- endLine: betweenComment.loc.end.line,
73
- startCol: betweenComment.loc.start.column + 1,
74
- endCol: betweenComment.loc.end.column + 1,
75
- nodeName: '#comment',
76
- type: 'comment',
77
- parentNode,
78
- prevNode,
79
- nextNode: node,
80
- isFragment: false,
81
- isGhost: false,
82
- };
83
- comment.parentNode = parentNode;
84
- comment.prevNode = prevNode;
85
- comment.nextNode = node;
86
- if (prevNode) {
87
- prevNode.nextNode = comment;
88
- }
89
- nodeList.push(comment);
90
- prevNode = comment;
91
- }
92
- if (prevNode) {
93
- if (node.type !== 'endtag') {
94
- prevNode.nextNode = node;
95
- }
96
- node.prevNode = prevNode;
97
- }
98
- prevNode = node;
99
- nodeList.push(node);
100
- }
101
- return nodeList;
102
- }
103
- function nodeize(
104
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
105
- originNode,
106
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
107
- prevNode,
108
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
109
- parentNode, rawHtml,
110
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
111
- comments, options) {
112
- const nextNode = null;
113
- const startOffset = originNode.range[0];
114
- const endOffset = originNode.range[1];
115
- const startLine = originNode.loc.start.line;
116
- const endLine = originNode.loc.end.line;
117
- const startCol = originNode.loc.start.column + 1;
118
- const endCol = originNode.loc.end.column + 1;
119
- const raw = rawHtml.slice(startOffset, endOffset ?? startOffset);
120
- switch (originNode.type) {
121
- case 'VText': {
122
- const node = {
123
- uuid: uuid(),
124
- raw,
125
- startOffset,
126
- endOffset,
127
- startLine,
128
- endLine,
129
- startCol,
130
- endCol,
131
- nodeName: '#text',
132
- type: 'text',
133
- parentNode,
134
- prevNode,
135
- nextNode,
136
- isFragment: false,
137
- isGhost: false,
138
- };
139
- return node;
140
- }
141
- case 'VExpressionContainer': {
142
- return {
143
- uuid: uuid(),
144
- raw,
145
- startOffset,
146
- endOffset,
147
- startLine,
148
- endLine,
149
- startCol,
150
- endCol,
151
- nodeName: '#vue-expression-container',
152
- type: 'psblock',
153
- parentNode,
154
- prevNode,
155
- nextNode,
156
- isFragment: false,
157
- isGhost: false,
158
- };
159
- }
160
- default: {
161
- const tagLoc = originNode.startTag;
162
- const startTagRaw = rawHtml.slice(tagLoc.range[0], tagLoc.range[1]);
163
- const tagTokens = tagParser(startTagRaw, startLine, startCol, startOffset);
164
- const tagName = tagTokens.tagName;
165
- let endTag = null;
166
- const endTagLoc = originNode.endTag;
167
- if (endTagLoc) {
168
- const endTagRaw = rawHtml.slice(endTagLoc.range[0], endTagLoc.range[1]);
169
- const endTagTokens = tagParser(endTagRaw, endTagLoc.loc.start.line, endTagLoc.loc.start.column, endTagLoc.range[0]);
170
- const endTagName = endTagTokens.tagName;
171
- endTag = {
172
- uuid: uuid(),
173
- raw: endTagRaw,
174
- startOffset: endTagLoc.range[0],
175
- endOffset: endTagLoc.range[1],
176
- startLine: endTagLoc.loc.start.line,
177
- endLine: endTagLoc.loc.end.line,
178
- startCol: endTagLoc.loc.start.column + 1,
179
- endCol: endTagLoc.loc.end.column + 1,
180
- nodeName: endTagName,
181
- type: 'endtag',
182
- namespace: originNode.namespace,
183
- attributes: endTagTokens.attrs,
184
- parentNode,
185
- prevNode,
186
- nextNode,
187
- pearNode: null,
188
- isFragment: false,
189
- isGhost: false,
190
- tagOpenChar: '</',
191
- tagCloseChar: '>',
192
- };
193
- }
194
- const startTag = {
195
- uuid: uuid(),
196
- raw: startTagRaw,
197
- startOffset,
198
- endOffset: startOffset + startTagRaw.length,
199
- startLine,
200
- endLine: getEndLine(startTagRaw, startLine),
201
- startCol,
202
- endCol: getEndCol(startTagRaw, startCol),
203
- nodeName: tagName,
204
- type: 'starttag',
205
- namespace: originNode.namespace,
206
- elementType: detectElementType(tagName, options?.authoredElementName,
207
- /**
208
- * @see https://vuejs.org/api/built-in-components.html
209
- * @see https://vuejs.org/api/built-in-special-elements.html
210
- */
211
- [
212
- 'component',
213
- 'slot',
214
- 'Transition',
215
- 'TransitionGroup',
216
- 'KeepAlive',
217
- 'Teleport',
218
- 'Suspense',
219
- // Backward compatibility
220
- /^[A-Z]/,
221
- ]),
222
- attributes: tagTokens.attrs.map(attr),
223
- hasSpreadAttr: false,
224
- parentNode,
225
- prevNode,
226
- nextNode,
227
- pearNode: endTag,
228
- selfClosingSolidus: tagTokens.selfClosingSolidus,
229
- endSpace: tagTokens.afterAttrSpaces,
230
- isFragment: false,
231
- isGhost: false,
232
- tagOpenChar: '<',
233
- tagCloseChar: '>',
234
- };
235
- if (endTag) {
236
- endTag.pearNode = startTag;
237
- }
238
- startTag.childNodes = traverse(originNode, startTag, rawHtml, comments, options);
239
- return startTag;
240
- }
241
- }
242
- }