@markuplint/svelte-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/README.md CHANGED
@@ -23,3 +23,15 @@ Add `parser` option to your [configuration](https://markuplint.dev/configuration
23
23
  }
24
24
  }
25
25
  ```
26
+
27
+ ### Use with [SvelteKit](https://kit.svelte.dev/)
28
+
29
+ ```diff
30
+ {
31
+ "parser": {
32
+ -- ".svelte$": "@markuplint/svelte-parser"
33
+ ++ ".svelte$": "@markuplint/svelte-parser",
34
+ ++ ".html$": "@markuplint/svelte-parser/kit"
35
+ }
36
+ }
37
+ ```
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,54 @@
1
+ /// <reference types="svelte" />
2
+ import type { SvelteNode } from './svelte-parser/index.js';
3
+ import type { MLASTParentNode, MLASTPreprocessorSpecificBlock } from '@markuplint/ml-ast';
4
+ import type { ChildToken, ParseOptions, Token } from '@markuplint/parser-utils';
5
+ import { ParserError, Parser } from '@markuplint/parser-utils';
6
+ declare class SvelteParser extends Parser<SvelteNode> {
7
+ readonly specificBindDirective: ReadonlySet<string>;
8
+ constructor();
9
+ tokenize(): {
10
+ ast: import("svelte/types/compiler/interfaces").TemplateNode[];
11
+ isFragment: boolean;
12
+ };
13
+ parse(raw: string, options?: ParseOptions): import("@markuplint/ml-ast").MLASTDocument;
14
+ parseError(error: any): ParserError;
15
+ nodeize(originNode: SvelteNode, parentNode: MLASTParentNode | null, depth: number): readonly import("@markuplint/ml-ast").MLASTNodeTreeItem[];
16
+ visitPsBlock(token: ChildToken & {
17
+ readonly nodeName: string;
18
+ }, childNodes?: readonly SvelteNode[]): readonly [MLASTPreprocessorSpecificBlock];
19
+ visitChildren(children: readonly SvelteNode[], parentNode: MLASTParentNode | null): never[];
20
+ visitAttr(token: Token): (import("@markuplint/ml-ast").MLASTSpreadAttr & {
21
+ __rightText?: string | undefined;
22
+ }) | {
23
+ isDynamicValue: true | undefined;
24
+ isDirective: true | undefined;
25
+ isDuplicatable: boolean;
26
+ potentialName: string | undefined;
27
+ type: "attr";
28
+ nodeName: string;
29
+ spacesBeforeName: import("@markuplint/ml-ast").MLASTToken;
30
+ name: import("@markuplint/ml-ast").MLASTToken;
31
+ spacesBeforeEqual: import("@markuplint/ml-ast").MLASTToken;
32
+ equal: import("@markuplint/ml-ast").MLASTToken;
33
+ spacesAfterEqual: import("@markuplint/ml-ast").MLASTToken;
34
+ startQuote: import("@markuplint/ml-ast").MLASTToken;
35
+ value: import("@markuplint/ml-ast").MLASTToken;
36
+ endQuote: import("@markuplint/ml-ast").MLASTToken;
37
+ potentialValue?: string | undefined;
38
+ valueType?: "string" | "number" | "boolean" | "code" | undefined;
39
+ candidate?: string | undefined;
40
+ uuid: string;
41
+ raw: string;
42
+ startOffset: number;
43
+ endOffset: number;
44
+ startLine: number;
45
+ endLine: number;
46
+ startCol: number;
47
+ endCol: number;
48
+ __rightText?: string | undefined;
49
+ };
50
+ detectElementType(nodeName: string): import("@markuplint/ml-ast").ElementType;
51
+ visitExpression(token: ChildToken, originBlockNode: SvelteNode): MLASTPreprocessorSpecificBlock[];
52
+ }
53
+ export declare const parser: SvelteParser;
54
+ export {};
package/lib/parser.js ADDED
@@ -0,0 +1,267 @@
1
+ import { getNamespace } from '@markuplint/html-parser';
2
+ import { ParserError, Parser, AttrState } from '@markuplint/parser-utils';
3
+ import { svelteParse } from './svelte-parser/index.js';
4
+ class SvelteParser extends Parser {
5
+ constructor() {
6
+ super({
7
+ endTagType: 'xml',
8
+ ignoreTags: [
9
+ {
10
+ type: 'Script',
11
+ start: '<script',
12
+ end: '</script>',
13
+ },
14
+ {
15
+ type: 'Style',
16
+ start: '<style',
17
+ end: '</style>',
18
+ },
19
+ ],
20
+ maskChar: '-',
21
+ });
22
+ this.specificBindDirective = new Set(['group', 'this']);
23
+ }
24
+ tokenize() {
25
+ return {
26
+ ast: svelteParse(this.rawCode),
27
+ isFragment: true,
28
+ };
29
+ }
30
+ parse(raw, options) {
31
+ return super.parse(raw, {
32
+ ...options,
33
+ ignoreFrontMatter: false,
34
+ });
35
+ }
36
+ parseError(error) {
37
+ if (error instanceof Error && 'start' in error && 'end' in error && 'frame' in error) {
38
+ // @ts-ignore
39
+ const token = this.sliceFragment(error.start.character, error.end.character);
40
+ throw new ParserError(error.message + '\n' + error.frame, token);
41
+ }
42
+ return super.parseError(error);
43
+ }
44
+ nodeize(
45
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
46
+ originNode, parentNode, depth) {
47
+ const token = this.sliceFragment(originNode.start, originNode.end);
48
+ const parentNamespace = parentNode && 'namespace' in parentNode ? parentNode.namespace : 'http://www.w3.org/1999/xhtml';
49
+ switch (originNode.type) {
50
+ case 'Text': {
51
+ return this.visitText({
52
+ ...token,
53
+ depth,
54
+ parentNode,
55
+ });
56
+ }
57
+ case 'Comment': {
58
+ return this.visitComment({
59
+ ...token,
60
+ depth,
61
+ parentNode,
62
+ });
63
+ }
64
+ case 'MustacheTag': {
65
+ return this.visitPsBlock({
66
+ ...token,
67
+ depth,
68
+ parentNode,
69
+ nodeName: 'MustacheTag',
70
+ });
71
+ }
72
+ case 'InlineComponent':
73
+ case 'Element': {
74
+ const children = originNode.children ?? [];
75
+ const reEndTag = new RegExp(`</${originNode.name}\\s*>$`, 'i');
76
+ const startTagEndOffset = children.length > 0
77
+ ? children[0]?.start ?? 0
78
+ : token.raw.replace(reEndTag, '').length + token.startOffset;
79
+ const startTagLocation = this.sliceFragment(token.startOffset, startTagEndOffset);
80
+ return this.visitElement({
81
+ ...startTagLocation,
82
+ depth,
83
+ parentNode,
84
+ nodeName: originNode.name,
85
+ namespace: getNamespace(originNode.name, parentNamespace),
86
+ }, originNode.children, {
87
+ createEndTagToken: () => {
88
+ if (!reEndTag.test(token.raw)) {
89
+ return null;
90
+ }
91
+ const endTagRawMatched = token.raw.match(reEndTag);
92
+ if (!endTagRawMatched) {
93
+ throw new Error('Parse error');
94
+ }
95
+ const endTagRaw = endTagRawMatched[0];
96
+ const endTagStartOffset = token.startOffset + token.raw.lastIndexOf(endTagRaw);
97
+ const endTagEndOffset = endTagStartOffset + endTagRaw.length;
98
+ const endTagLocation = this.sliceFragment(endTagStartOffset, endTagEndOffset);
99
+ return {
100
+ ...endTagLocation,
101
+ depth,
102
+ parentNode,
103
+ };
104
+ },
105
+ });
106
+ }
107
+ default: {
108
+ return this.visitExpression({
109
+ ...token,
110
+ depth,
111
+ parentNode,
112
+ }, originNode);
113
+ }
114
+ }
115
+ }
116
+ visitPsBlock(token, childNodes = []) {
117
+ const nodes = super.visitPsBlock(token, childNodes);
118
+ const block = nodes.at(0);
119
+ if (!block || block.type !== 'psblock') {
120
+ throw new ParserError('Parse error', token);
121
+ }
122
+ if (nodes.length > 1) {
123
+ throw new ParserError('Parse error', nodes.at(1));
124
+ }
125
+ return [block];
126
+ }
127
+ visitChildren(
128
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
129
+ children, parentNode) {
130
+ const siblings = super.visitChildren(children, parentNode);
131
+ if (siblings.length > 0) {
132
+ throw new ParserError('Discovered child nodes with differing hierarchy levels', siblings[0]);
133
+ }
134
+ return [];
135
+ }
136
+ visitAttr(token) {
137
+ const attr = super.visitAttr(token, {
138
+ quoteSet: [
139
+ { start: '"', end: '"' },
140
+ { start: "'", end: "'" },
141
+ { start: '{', end: '}' },
142
+ ],
143
+ quoteInValueChars: [
144
+ { start: '"', end: '"' },
145
+ { start: "'", end: "'" },
146
+ { start: '`', end: '`' },
147
+ { start: '${', end: '}' },
148
+ ],
149
+ startState:
150
+ // is shorthand attribute
151
+ token.raw.trim().startsWith('{') ? AttrState.BeforeValue : AttrState.BeforeName,
152
+ });
153
+ if (attr.type === 'spread') {
154
+ return attr;
155
+ }
156
+ let isDynamicValue = attr.startQuote.raw === '{' || undefined;
157
+ let potentialName;
158
+ let isDirective;
159
+ let isDuplicatable = false;
160
+ if (isDynamicValue && attr.name.raw === '') {
161
+ potentialName = attr.value.raw;
162
+ }
163
+ const [baseName, subName] = attr.name.raw.split(':');
164
+ if (subName) {
165
+ isDirective = true;
166
+ if (baseName === 'bind' && !this.specificBindDirective.has(subName)) {
167
+ potentialName = subName;
168
+ isDirective = undefined;
169
+ isDynamicValue = true;
170
+ }
171
+ }
172
+ if (baseName?.toLowerCase() === 'class') {
173
+ isDuplicatable = true;
174
+ if (subName) {
175
+ potentialName = 'class';
176
+ isDynamicValue = true;
177
+ }
178
+ }
179
+ if (attr.startQuote.raw === '{' && attr.endQuote.raw === '}') {
180
+ isDynamicValue = true;
181
+ }
182
+ return {
183
+ ...attr,
184
+ isDynamicValue,
185
+ isDirective,
186
+ isDuplicatable,
187
+ potentialName,
188
+ };
189
+ }
190
+ detectElementType(nodeName) {
191
+ return super.detectElementType(nodeName, /[.A-Z]/);
192
+ }
193
+ visitExpression(token,
194
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
195
+ originBlockNode) {
196
+ const props = ['', 'else', 'pending', 'then', 'catch'];
197
+ const expressions = [];
198
+ const blockType = originBlockNode.type.toLowerCase().replace('block', '');
199
+ const nodeList = new Map();
200
+ for (const prop of props) {
201
+ let node = (originBlockNode[prop] ?? originBlockNode);
202
+ if (nodeList.has(node)) {
203
+ continue;
204
+ }
205
+ if (node.type === 'ElseBlock' && node.children?.[0]?.elseif) {
206
+ node = node.children[0];
207
+ while (node != null) {
208
+ if (!['IfBlock', 'ElseBlock'].includes(node.type)) {
209
+ break;
210
+ }
211
+ const type = node.elseif ? 'elseif' : 'else';
212
+ nodeList.set(node, type);
213
+ node = node.else ?? node.children?.[0] ?? null;
214
+ }
215
+ continue;
216
+ }
217
+ let type = prop || blockType;
218
+ if (prop === 'pending') {
219
+ type = 'await';
220
+ }
221
+ nodeList.set(node, type);
222
+ }
223
+ let lastChild = null;
224
+ for (const [node, type] of nodeList.entries()) {
225
+ let start = node.start;
226
+ let end = node.end;
227
+ if (type === 'await') {
228
+ start = originBlockNode.start;
229
+ }
230
+ end = node.children?.[0]?.start ?? end;
231
+ if (type === 'else' && originBlockNode.type === 'EachBlock') {
232
+ start = lastChild?.end ?? start;
233
+ }
234
+ if (['else', 'elseif'].includes(type) && originBlockNode.type === 'IfBlock') {
235
+ start = lastChild?.end ?? start;
236
+ }
237
+ const tag = this.sliceFragment(start, end);
238
+ if (node.children && Array.isArray(node.children)) {
239
+ lastChild = node.children.at(-1) ?? null;
240
+ }
241
+ const expression = this.visitPsBlock({
242
+ ...tag,
243
+ depth: token.depth,
244
+ parentNode: token.parentNode,
245
+ nodeName: type,
246
+ }, node.children)[0];
247
+ expressions.push(expression);
248
+ }
249
+ const lastText = this.sliceFragment(lastChild?.end ?? originBlockNode.end, originBlockNode.end);
250
+ if (lastText.raw) {
251
+ // Cut before whitespace
252
+ const index = lastText.raw.search(/\S/);
253
+ const lastToken = this.sliceFragment(lastText.startOffset + index, originBlockNode.end);
254
+ if (lastToken.raw) {
255
+ const expression = this.visitPsBlock({
256
+ ...lastToken,
257
+ depth: token.depth,
258
+ parentNode: token.parentNode,
259
+ nodeName: '/' + blockType,
260
+ })[0];
261
+ expressions.push(expression);
262
+ }
263
+ }
264
+ return expressions;
265
+ }
266
+ }
267
+ export const parser = new SvelteParser();
@@ -0,0 +1,6 @@
1
+ import { HtmlParser } from '@markuplint/html-parser';
2
+ declare class SvelteKitTemplateParser extends HtmlParser {
3
+ constructor();
4
+ }
5
+ export declare const parser: SvelteKitTemplateParser;
6
+ export {};
@@ -0,0 +1,15 @@
1
+ import { HtmlParser } from '@markuplint/html-parser';
2
+ class SvelteKitTemplateParser extends HtmlParser {
3
+ constructor() {
4
+ super({
5
+ ignoreTags: [
6
+ {
7
+ type: 'sveltekit-placeholder',
8
+ start: '%sveltekit.',
9
+ end: '%',
10
+ },
11
+ ],
12
+ });
13
+ }
14
+ }
15
+ export const parser = new SvelteKitTemplateParser();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markuplint/svelte-parser",
3
- "version": "4.0.0-dev.20+6b35da16",
3
+ "version": "4.0.0-dev.23+d6f2aa9bc",
4
4
  "description": "Svelte parser for markuplint",
5
5
  "repository": "git@github.com:markuplint/markuplint.git",
6
6
  "author": "Yusuke Hirao <yusukehirao@me.com>",
@@ -10,6 +10,9 @@
10
10
  "exports": {
11
11
  ".": {
12
12
  "import": "./lib/index.js"
13
+ },
14
+ "./kit": {
15
+ "import": "./lib/sveltekit-parser.js"
13
16
  }
14
17
  },
15
18
  "types": "lib/index.d.ts",
@@ -21,10 +24,10 @@
21
24
  "clean": "tsc --build --clean"
22
25
  },
23
26
  "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
- "svelte": "^4.2.8"
27
+ "@markuplint/html-parser": "4.0.0-dev.23+d6f2aa9bc",
28
+ "@markuplint/ml-ast": "4.0.0-dev.23+d6f2aa9bc",
29
+ "@markuplint/parser-utils": "4.0.0-dev.23+d6f2aa9bc",
30
+ "svelte": "^4.2.9"
28
31
  },
29
- "gitHead": "6b35da161d94f784953d0adecc2d28502052d92a"
32
+ "gitHead": "d6f2aa9bc287768466f23b5340e4e0eecfa30d59"
30
33
  }
package/lib/attr.d.ts DELETED
@@ -1,5 +0,0 @@
1
- import type { SvelteDirective } from './svelte-parser/index.js';
2
- import type { MLASTAttr } from '@markuplint/ml-ast';
3
- export declare function attr(attr: SvelteDirective, rawHTML: string): MLASTAttr | {
4
- __spreadAttr: true;
5
- };
package/lib/attr.js DELETED
@@ -1,61 +0,0 @@
1
- import { defaultValueDelimiters, parseAttr, sliceFragment } from '@markuplint/parser-utils';
2
- import { directiveTokenizer } from './directive-tokenizer.js';
3
- const mustacheTag = {
4
- start: '{',
5
- end: '}',
6
- };
7
- const specificBindDirective = new Set(['bind:group', 'bind:this']);
8
- export function attr(
9
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
10
- attr, rawHTML) {
11
- const isShorthand = attr.value && Array.isArray(attr.value)
12
- ? attr.value.some((val) => val.type === 'AttributeShorthand')
13
- : false;
14
- const { start, end } = attr;
15
- if (attr.type === 'Spread') {
16
- return {
17
- __spreadAttr: true,
18
- };
19
- }
20
- let token;
21
- if (attr.type === 'Attribute' && !isShorthand) {
22
- const { raw } = sliceFragment(rawHTML, start, end);
23
- token = parseAttr(raw, start, rawHTML, {
24
- valueDelimiters: [...defaultValueDelimiters, mustacheTag],
25
- });
26
- }
27
- else {
28
- const { raw, startLine, startCol, startOffset } = sliceFragment(rawHTML, start, end);
29
- const valueToken = isShorthand
30
- ? attr.name
31
- : attr.expression && 'start' in attr.expression && 'end' in attr.expression
32
- ? sliceFragment(rawHTML, attr.expression.start, attr.expression.end).raw
33
- : '';
34
- token = directiveTokenizer(raw, valueToken, startLine, startCol, startOffset);
35
- }
36
- if (!specificBindDirective.has(token.name.raw) && /^bind:/i.test(token.name.raw)) {
37
- // Remove "bind:"
38
- token.potentialName = token.name.raw.slice(5);
39
- token.isDirective = undefined;
40
- token.isDynamicValue = true;
41
- }
42
- if (isShorthand) {
43
- token.potentialName = token.value.raw.trim();
44
- token.isDirective = undefined;
45
- token.isDynamicValue = true;
46
- }
47
- const [baseName, subName] = token.name.raw.split(':');
48
- if (baseName?.toLowerCase() === 'class') {
49
- token.isDuplicatable = true;
50
- if (subName) {
51
- token.potentialName = 'class';
52
- token.isDynamicValue = true;
53
- }
54
- }
55
- if (token.startQuote.raw === '{' && token.endQuote.raw === '}') {
56
- token.isDynamicValue = true;
57
- }
58
- return {
59
- ...token,
60
- };
61
- }
@@ -1,2 +0,0 @@
1
- import type { MLASTHTMLAttr } from '@markuplint/ml-ast';
2
- export declare function directiveTokenizer(raw: string, rawValue: string, line: number, col: number, startOffset: number): MLASTHTMLAttr;
@@ -1,16 +0,0 @@
1
- import { AttrState, attrTokenizer } from '@markuplint/parser-utils';
2
- export function directiveTokenizer(raw, rawValue, line, col, startOffset) {
3
- const nameOnly = raw.trim().startsWith('{') && raw.trim().endsWith('}');
4
- const directiveToken = attrTokenizer(raw, line, col, startOffset, [
5
- { start: '"', end: '"' },
6
- { start: "'", end: "'" },
7
- { start: '{', end: '}' },
8
- ], nameOnly ? AttrState.BeforeValue : AttrState.BeforeName, [
9
- { start: '"', end: '"' },
10
- { start: "'", end: "'" },
11
- { start: '`', end: '`' },
12
- { start: '${', end: '}' },
13
- ]);
14
- directiveToken.isDirective = true;
15
- return directiveToken;
16
- }
package/lib/nodeize.d.ts DELETED
@@ -1,3 +0,0 @@
1
- import type { SvelteNode } from './svelte-parser/index.js';
2
- import type { MLASTNode, MLASTParentNode, ParserOptions } from '@markuplint/ml-ast';
3
- export declare function nodeize(originNode: SvelteNode, prevNode: MLASTNode | null, parentNode: MLASTParentNode | null, rawHtml: string, options?: ParserOptions): MLASTNode | MLASTNode[] | null;
package/lib/nodeize.js DELETED
@@ -1,290 +0,0 @@
1
- import { getNamespace } from '@markuplint/html-parser';
2
- import { detectElementType, sliceFragment, uuid, tagParser } from '@markuplint/parser-utils';
3
- import { attr } from './attr.js';
4
- import { parseCtrlBlock } from './parse-ctrl-block.js';
5
- import { traverse } from './traverse.js';
6
- export function nodeize(
7
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
8
- originNode,
9
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
10
- prevNode,
11
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
12
- parentNode, rawHtml, options) {
13
- const nextNode = null;
14
- const { startOffset, endOffset, startLine, endLine, startCol, endCol, raw } = sliceFragment(rawHtml, originNode.start, originNode.end);
15
- const parentNamespace = parentNode && 'namespace' in parentNode ? parentNode.namespace : 'http://www.w3.org/1999/xhtml';
16
- switch (originNode.type) {
17
- case 'Text': {
18
- const node = {
19
- uuid: uuid(),
20
- raw,
21
- startOffset,
22
- endOffset,
23
- startLine,
24
- endLine,
25
- startCol,
26
- endCol,
27
- nodeName: '#text',
28
- type: 'text',
29
- parentNode,
30
- prevNode,
31
- nextNode,
32
- isFragment: false,
33
- isGhost: false,
34
- };
35
- return node;
36
- }
37
- case 'MustacheTag': {
38
- return {
39
- uuid: uuid(),
40
- raw,
41
- startOffset,
42
- endOffset,
43
- startLine,
44
- endLine,
45
- startCol,
46
- endCol,
47
- nodeName: '#ps:MustacheTag',
48
- type: 'psblock',
49
- parentNode,
50
- prevNode,
51
- nextNode,
52
- isFragment: false,
53
- isGhost: false,
54
- };
55
- }
56
- case 'InlineComponent':
57
- case 'Element': {
58
- const children = originNode.children ?? [];
59
- const reEndTag = new RegExp(`</${originNode.name}\\s*>$`, 'i');
60
- const startTagEndOffset = children.length > 0 ? children[0]?.start ?? 0 : raw.replace(reEndTag, '').length + startOffset;
61
- const startTagLocation = sliceFragment(rawHtml, startOffset, startTagEndOffset);
62
- let endTag = null;
63
- if (reEndTag.test(raw)) {
64
- const endTagRawMatched = raw.match(reEndTag);
65
- if (!endTagRawMatched) {
66
- throw new Error('Parse error');
67
- }
68
- const endTagRaw = endTagRawMatched[0];
69
- const endTagStartOffset = startOffset + raw.lastIndexOf(endTagRaw);
70
- const endTagEndOffset = endTagStartOffset + endTagRaw.length;
71
- const endTagLocation = sliceFragment(rawHtml, endTagStartOffset, endTagEndOffset);
72
- const namespace = getNamespace(originNode.name, parentNamespace);
73
- endTag = {
74
- uuid: uuid(),
75
- raw: endTagRaw,
76
- startOffset: endTagStartOffset,
77
- endOffset: endTagEndOffset,
78
- startLine: endTagLocation.startLine,
79
- endLine: endTagLocation.endLine,
80
- startCol: endTagLocation.startCol,
81
- endCol: endTagLocation.endCol,
82
- nodeName: originNode.name,
83
- type: 'endtag',
84
- namespace,
85
- attributes: [],
86
- parentNode,
87
- prevNode,
88
- nextNode,
89
- pearNode: null,
90
- isFragment: false,
91
- isGhost: false,
92
- tagOpenChar: '</',
93
- tagCloseChar: '>',
94
- };
95
- }
96
- const directives = originNode.attributes.map(a => attr(a, rawHtml)) ?? [];
97
- const attributes = directives.filter((d) => !('__spreadAttr' in d));
98
- const hasSpreadAttr = directives.some(d => '__spreadAttr' in d);
99
- const tagTokens = tagParser(startTagLocation.raw, startTagLocation.startLine, startTagLocation.startCol, startTagLocation.startOffset);
100
- const namespace = getNamespace(originNode.name, parentNamespace);
101
- const startTag = {
102
- uuid: uuid(),
103
- ...startTagLocation,
104
- nodeName: originNode.name,
105
- type: 'starttag',
106
- namespace,
107
- elementType: detectElementType(originNode.name, options?.authoredElementName, /[.A-Z]/),
108
- attributes,
109
- hasSpreadAttr,
110
- parentNode,
111
- prevNode,
112
- nextNode,
113
- pearNode: endTag,
114
- selfClosingSolidus: tagTokens.selfClosingSolidus,
115
- endSpace: tagTokens.afterAttrSpaces,
116
- isFragment: false,
117
- isGhost: false,
118
- tagOpenChar: '<',
119
- tagCloseChar: '>',
120
- };
121
- if (endTag) {
122
- endTag.pearNode = startTag;
123
- }
124
- if (originNode.children) {
125
- startTag.childNodes = traverse(originNode.children, startTag, rawHtml, options);
126
- }
127
- return startTag;
128
- }
129
- case 'IfBlock': {
130
- const ifBlocks = parseCtrlBlock('if', originNode, raw, rawHtml, startOffset, parentNode, prevNode, nextNode, options);
131
- return ifBlocks;
132
- }
133
- case 'EachBlock': {
134
- return parseCtrlBlock('each', originNode, raw, rawHtml, startOffset, parentNode, prevNode, nextNode, options);
135
- }
136
- case 'AwaitBlock': {
137
- const pendingNode = originNode.pending;
138
- const pendingTag = sliceFragment(rawHtml, originNode.start, pendingNode.start);
139
- const pending = {
140
- uuid: uuid(),
141
- ...pendingTag,
142
- nodeName: pendingNode.type,
143
- type: 'psblock',
144
- parentNode,
145
- prevNode,
146
- nextNode,
147
- isFragment: false,
148
- isGhost: false,
149
- };
150
- if (pendingNode.children) {
151
- pending.childNodes = traverse(pendingNode.children, pending, rawHtml, options);
152
- }
153
- let then = null;
154
- if (originNode.then) {
155
- const thenNode = originNode.then;
156
- const thenTag = sliceFragment(rawHtml, thenNode.start, (thenNode.children && thenNode.children[0] && thenNode.children[0].start) ?? thenNode.end);
157
- then = {
158
- uuid: uuid(),
159
- ...thenTag,
160
- nodeName: thenNode.type,
161
- type: 'psblock',
162
- parentNode,
163
- prevNode,
164
- nextNode,
165
- isFragment: false,
166
- isGhost: false,
167
- };
168
- if (thenNode.children) {
169
- then.childNodes = traverse(thenNode.children, then, rawHtml, options);
170
- }
171
- }
172
- let awaitCatch = null;
173
- if (originNode.catch) {
174
- const awaitCatchNode = originNode.catch;
175
- const awaitCatchTag = sliceFragment(rawHtml, awaitCatchNode.start, (awaitCatchNode.children && awaitCatchNode.children[0] && awaitCatchNode.children[0].start) ??
176
- awaitCatchNode.end);
177
- awaitCatch = {
178
- uuid: uuid(),
179
- ...awaitCatchTag,
180
- nodeName: awaitCatchNode.type,
181
- type: 'psblock',
182
- parentNode,
183
- prevNode,
184
- nextNode,
185
- isFragment: false,
186
- isGhost: false,
187
- };
188
- if (awaitCatchNode.children) {
189
- awaitCatch.childNodes = traverse(awaitCatchNode.children, awaitCatch, rawHtml, options);
190
- }
191
- }
192
- // eslint-disable-next-line regexp/strict
193
- const reEndTag = /{\s*\/await\s*}$/i;
194
- let endTag = null;
195
- if (reEndTag.test(raw)) {
196
- const endTagRawMatched = raw.match(reEndTag);
197
- if (!endTagRawMatched) {
198
- throw new Error('Parse error');
199
- }
200
- const endTagRaw = endTagRawMatched[0];
201
- const endTagStartOffset = startOffset + raw.indexOf(endTagRaw);
202
- const endTagEndOffset = endTagStartOffset + endTagRaw.length;
203
- const endTagLocation = sliceFragment(rawHtml, endTagStartOffset, endTagEndOffset);
204
- endTag = {
205
- uuid: uuid(),
206
- raw: endTagRaw,
207
- startOffset: endTagStartOffset,
208
- endOffset: endTagEndOffset,
209
- startLine: endTagLocation.startLine,
210
- endLine: endTagLocation.endLine,
211
- startCol: endTagLocation.startCol,
212
- endCol: endTagLocation.endCol,
213
- nodeName: originNode.type,
214
- type: 'psblock',
215
- parentNode,
216
- prevNode,
217
- nextNode,
218
- isFragment: false,
219
- isGhost: false,
220
- };
221
- }
222
- const tags = [pending];
223
- if (then) {
224
- tags.push(then);
225
- }
226
- if (awaitCatch) {
227
- tags.push(awaitCatch);
228
- }
229
- if (endTag) {
230
- tags.push(endTag);
231
- }
232
- return tags;
233
- }
234
- default: {
235
- const startTag = {
236
- uuid: uuid(),
237
- raw,
238
- startOffset,
239
- endOffset,
240
- startLine,
241
- endLine,
242
- startCol,
243
- endCol,
244
- nodeName: originNode.name || originNode.type,
245
- type: 'psblock',
246
- parentNode,
247
- prevNode,
248
- nextNode,
249
- isFragment: false,
250
- isGhost: false,
251
- };
252
- let endTag = null;
253
- if (originNode.children) {
254
- startTag.childNodes = traverse(originNode.children, startTag, rawHtml, options);
255
- const firstChild = startTag.childNodes[0];
256
- if (firstChild) {
257
- startTag.endOffset = firstChild.startOffset;
258
- startTag.endLine = firstChild.startLine;
259
- startTag.endCol = firstChild.startCol;
260
- startTag.raw = rawHtml.slice(startTag.startOffset, startTag.endOffset);
261
- }
262
- const lastChild = startTag.childNodes.at(-1);
263
- if (lastChild && lastChild.endOffset > startTag.endOffset) {
264
- const startOffset = lastChild.endOffset;
265
- const startLine = lastChild.endLine;
266
- const startCol = lastChild.endCol;
267
- const raw = rawHtml.slice(startOffset, endOffset);
268
- endTag = {
269
- uuid: uuid(),
270
- raw,
271
- startOffset,
272
- endOffset,
273
- startLine,
274
- endLine,
275
- startCol,
276
- endCol,
277
- nodeName: originNode.name || originNode.type,
278
- type: 'psblock',
279
- parentNode,
280
- prevNode,
281
- nextNode,
282
- isFragment: false,
283
- isGhost: false,
284
- };
285
- }
286
- }
287
- return endTag == null ? startTag : [startTag, endTag];
288
- }
289
- }
290
- }
@@ -1,3 +0,0 @@
1
- import type { SvelteNode } from './svelte-parser/index.js';
2
- import type { MLASTNode, MLASTParentNode, MLASTPreprocessorSpecificBlock, ParserOptions } from '@markuplint/ml-ast';
3
- export declare function parseCtrlBlock(ctrlName: 'if' | 'each', originNode: SvelteNode, raw: string, rawHtml: string, startOffset: number, parentNode: MLASTParentNode | null, prevNode: MLASTNode | null, nextNode: MLASTNode | null, options?: ParserOptions): MLASTPreprocessorSpecificBlock[];
@@ -1,171 +0,0 @@
1
- import { sliceFragment, uuid } from '@markuplint/parser-utils';
2
- import { traverse } from './traverse.js';
3
- export function parseCtrlBlock(ctrlName,
4
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
5
- originNode, raw, rawHtml, startOffset,
6
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
7
- parentNode,
8
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
9
- prevNode,
10
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
11
- nextNode, options) {
12
- if (ctrlName === 'if') {
13
- return parseIfBlock(originNode, raw, rawHtml, startOffset, originNode.start, parentNode, prevNode, nextNode, options);
14
- }
15
- const children = originNode.children ?? [];
16
- // eslint-disable-next-line regexp/strict
17
- const reEndTag = /{\s*\/each\s*}$/i;
18
- const startTagEndOffset = children.length > 0 ? children[0]?.start ?? 0 : raw.replace(reEndTag, '').length + startOffset;
19
- const startTagLocation = sliceFragment(rawHtml, startOffset, startTagEndOffset);
20
- const tag = {
21
- uuid: uuid(),
22
- ...startTagLocation,
23
- nodeName: originNode.type,
24
- type: 'psblock',
25
- parentNode,
26
- prevNode,
27
- nextNode,
28
- isFragment: false,
29
- isGhost: false,
30
- };
31
- if (originNode.children) {
32
- tag.childNodes = traverse(originNode.children, tag, rawHtml, options);
33
- }
34
- let elseTag = null;
35
- if (originNode.else) {
36
- const elseNode = originNode.else;
37
- const elseTagStartOffset = children.length > 0 ? children.at(-1)?.end ?? 0 : startTagLocation.endOffset;
38
- const elseTagLocation = sliceFragment(rawHtml, elseTagStartOffset, elseNode.start);
39
- elseTag = {
40
- uuid: uuid(),
41
- ...elseTagLocation,
42
- nodeName: elseNode.type,
43
- type: 'psblock',
44
- parentNode,
45
- prevNode,
46
- nextNode,
47
- isFragment: false,
48
- isGhost: false,
49
- };
50
- if (elseNode.children) {
51
- elseTag.childNodes = traverse(elseNode.children, elseTag, rawHtml, options);
52
- }
53
- }
54
- let endTag = null;
55
- const endTagRawMatched = raw.match(reEndTag);
56
- if (endTagRawMatched) {
57
- const endTagRaw = endTagRawMatched[0];
58
- const endTagStartOffset = originNode.end - endTagRaw.length;
59
- const endTagEndOffset = originNode.end;
60
- const endTagLocation = sliceFragment(rawHtml, endTagStartOffset, endTagEndOffset);
61
- endTag = {
62
- uuid: uuid(),
63
- raw: endTagRaw,
64
- startOffset: endTagStartOffset,
65
- endOffset: endTagEndOffset,
66
- startLine: endTagLocation.startLine,
67
- endLine: endTagLocation.endLine,
68
- startCol: endTagLocation.startCol,
69
- endCol: endTagLocation.endCol,
70
- nodeName: originNode.type,
71
- type: 'psblock',
72
- parentNode,
73
- prevNode,
74
- nextNode,
75
- isFragment: false,
76
- isGhost: false,
77
- };
78
- }
79
- const tags = [tag];
80
- if (elseTag) {
81
- tags.push(elseTag);
82
- }
83
- if (endTag) {
84
- tags.push(endTag);
85
- }
86
- return tags;
87
- }
88
- function parseIfBlock(
89
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
90
- originNode, raw, rawHtml, statementStartOffset, tokenStartOffset,
91
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
92
- parentNode,
93
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
94
- prevNode,
95
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
96
- nextNode, options) {
97
- const children = originNode.children ?? [];
98
- const startTagEndOffset = children[0]?.start ?? tokenStartOffset;
99
- const startTagLocation = sliceFragment(rawHtml, tokenStartOffset, startTagEndOffset);
100
- const tag = {
101
- uuid: uuid(),
102
- ...startTagLocation,
103
- nodeName: originNode.elseif ? 'ElseIfBlock' : originNode.type,
104
- type: 'psblock',
105
- parentNode,
106
- prevNode,
107
- nextNode,
108
- isFragment: false,
109
- isGhost: false,
110
- };
111
- if (originNode.children) {
112
- tag.childNodes = traverse(originNode.children, tag, rawHtml, options);
113
- }
114
- const elseOrElseIfTags = [];
115
- if (originNode.else) {
116
- const elseNode = originNode.else;
117
- const elseTagStartOffset = children.length > 0 ? children.at(-1)?.end ?? 0 : startTagLocation.endOffset;
118
- const elseTagLocation = sliceFragment(rawHtml, elseTagStartOffset, elseNode.start);
119
- if (elseNode.children) {
120
- if (elseNode.children.length === 1 && elseNode.children[0]?.type === 'IfBlock') {
121
- const elseIfTags = parseIfBlock(elseNode.children[0], elseTagLocation.raw, rawHtml, statementStartOffset, originNode.children?.[0]?.end ?? startTagLocation.endOffset, parentNode, null, null, options);
122
- elseOrElseIfTags.push(...elseIfTags);
123
- }
124
- else {
125
- const elseTag = {
126
- uuid: uuid(),
127
- ...elseTagLocation,
128
- nodeName: elseNode.type,
129
- type: 'psblock',
130
- parentNode,
131
- prevNode,
132
- nextNode,
133
- isFragment: false,
134
- isGhost: false,
135
- };
136
- elseTag.childNodes = traverse(elseNode.children, elseTag, rawHtml, options);
137
- elseOrElseIfTags.push(elseTag);
138
- }
139
- }
140
- }
141
- if (originNode.elseif) {
142
- return [tag, ...elseOrElseIfTags];
143
- }
144
- // eslint-disable-next-line regexp/strict
145
- const endTagRawMatched = raw.match(/{\s*\/if\s*}/);
146
- if (!endTagRawMatched) {
147
- throw new Error('Missing the end token `{/if}`');
148
- }
149
- const endTagRaw = endTagRawMatched[0];
150
- const endTagStartOffset = originNode.end - endTagRaw.length;
151
- const endTagEndOffset = originNode.end;
152
- const endTagLocation = sliceFragment(rawHtml, endTagStartOffset, endTagEndOffset);
153
- const endTag = {
154
- uuid: uuid(),
155
- raw: endTagRaw,
156
- startOffset: endTagStartOffset,
157
- endOffset: endTagEndOffset,
158
- startLine: endTagLocation.startLine,
159
- endLine: endTagLocation.endLine,
160
- startCol: endTagLocation.startCol,
161
- endCol: endTagLocation.endCol,
162
- nodeName: originNode.type,
163
- type: 'psblock',
164
- parentNode,
165
- prevNode,
166
- nextNode,
167
- isFragment: false,
168
- isGhost: false,
169
- };
170
- return [tag, ...elseOrElseIfTags, endTag];
171
- }
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,47 +0,0 @@
1
- import { flattenNodes, ParserError, ignoreBlock, restoreNode } from '@markuplint/parser-utils';
2
- import { svelteParse } from './svelte-parser/index.js';
3
- import { traverse } from './traverse.js';
4
- export const parse = (rawCode, options) => {
5
- const blocks = ignoreBlock(rawCode, [
6
- {
7
- type: 'Script',
8
- start: '<script',
9
- end: '</script>',
10
- },
11
- {
12
- type: 'Style',
13
- start: '<style',
14
- end: '</style>',
15
- },
16
- ], '-');
17
- let ast;
18
- try {
19
- ast = svelteParse(blocks.replaced);
20
- }
21
- catch (error) {
22
- if (error instanceof Error && 'start' in error && 'end' in error && 'frame' in error) {
23
- // @ts-ignore
24
- const raw = rawCode.slice(error.start.character, error.end.character);
25
- throw new ParserError(
26
- // @ts-ignore
27
- error.message + '\n' + error.frame, {
28
- // @ts-ignore
29
- line: error.start.line,
30
- // @ts-ignore
31
- col: error.start.column,
32
- raw,
33
- });
34
- }
35
- return {
36
- nodeList: [],
37
- isFragment: true,
38
- parseError: error instanceof Error ? error.message : new Error(`${error}`).message,
39
- };
40
- }
41
- const nodes = traverse(ast, null, blocks.replaced, options);
42
- const nodeList = restoreNode(flattenNodes(nodes, blocks.replaced), blocks);
43
- return {
44
- nodeList,
45
- isFragment: true,
46
- };
47
- };
package/lib/traverse.d.ts DELETED
@@ -1,3 +0,0 @@
1
- import type { SvelteNode } from './svelte-parser/index.js';
2
- import type { MLASTNode, MLASTParentNode, ParserOptions } from '@markuplint/ml-ast';
3
- export declare function traverse(astNodes: readonly SvelteNode[], parentNode: MLASTParentNode | null | undefined, rawHtml: string, options?: ParserOptions): MLASTNode[];
package/lib/traverse.js DELETED
@@ -1,38 +0,0 @@
1
- import { nodeize } from './nodeize.js';
2
- export function traverse(
3
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
4
- astNodes, parentNode = null, rawHtml, options) {
5
- const nodeList = [];
6
- let prevNode = null;
7
- for (const astNode of astNodes) {
8
- const nodes = nodeize(astNode, prevNode, parentNode, rawHtml, options);
9
- if (!nodes) {
10
- continue;
11
- }
12
- let node;
13
- if (Array.isArray(nodes)) {
14
- const lastNode = nodes.at(-1);
15
- if (!lastNode) {
16
- continue;
17
- }
18
- node = lastNode;
19
- }
20
- else {
21
- node = nodes;
22
- }
23
- if (prevNode) {
24
- if (node.type !== 'endtag') {
25
- prevNode.nextNode = node;
26
- }
27
- node.prevNode = prevNode;
28
- }
29
- prevNode = node;
30
- if (Array.isArray(nodes)) {
31
- nodeList.push(...nodes);
32
- }
33
- else {
34
- nodeList.push(nodes);
35
- }
36
- }
37
- return nodeList;
38
- }