@markuplint/svelte-parser 4.0.0-dev.28 → 4.0.0-rc.1

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/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,268 @@
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
+ tagNameCaseSensitive: true,
9
+ ignoreTags: [
10
+ {
11
+ type: 'Script',
12
+ start: '<script',
13
+ end: '</script>',
14
+ },
15
+ {
16
+ type: 'Style',
17
+ start: '<style',
18
+ end: '</style>',
19
+ },
20
+ ],
21
+ maskChar: '-',
22
+ });
23
+ this.specificBindDirective = new Set(['group', 'this']);
24
+ }
25
+ tokenize() {
26
+ return {
27
+ ast: svelteParse(this.rawCode),
28
+ isFragment: true,
29
+ };
30
+ }
31
+ parse(raw, options) {
32
+ return super.parse(raw, {
33
+ ...options,
34
+ ignoreFrontMatter: false,
35
+ });
36
+ }
37
+ parseError(error) {
38
+ if (error instanceof Error && 'start' in error && 'end' in error && 'frame' in error) {
39
+ // @ts-ignore
40
+ const token = this.sliceFragment(error.start.character, error.end.character);
41
+ throw new ParserError(error.message + '\n' + error.frame, token);
42
+ }
43
+ return super.parseError(error);
44
+ }
45
+ nodeize(
46
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
47
+ originNode, parentNode, depth) {
48
+ const token = this.sliceFragment(originNode.start, originNode.end);
49
+ const parentNamespace = parentNode && 'namespace' in parentNode ? parentNode.namespace : 'http://www.w3.org/1999/xhtml';
50
+ switch (originNode.type) {
51
+ case 'Text': {
52
+ return this.visitText({
53
+ ...token,
54
+ depth,
55
+ parentNode,
56
+ });
57
+ }
58
+ case 'Comment': {
59
+ return this.visitComment({
60
+ ...token,
61
+ depth,
62
+ parentNode,
63
+ });
64
+ }
65
+ case 'MustacheTag': {
66
+ return this.visitPsBlock({
67
+ ...token,
68
+ depth,
69
+ parentNode,
70
+ nodeName: 'MustacheTag',
71
+ });
72
+ }
73
+ case 'InlineComponent':
74
+ case 'Element': {
75
+ const children = originNode.children ?? [];
76
+ const reEndTag = new RegExp(`</${originNode.name}\\s*>$`, 'i');
77
+ const startTagEndOffset = children.length > 0
78
+ ? children[0]?.start ?? 0
79
+ : token.raw.replace(reEndTag, '').length + token.startOffset;
80
+ const startTagLocation = this.sliceFragment(token.startOffset, startTagEndOffset);
81
+ return this.visitElement({
82
+ ...startTagLocation,
83
+ depth,
84
+ parentNode,
85
+ nodeName: originNode.name,
86
+ namespace: getNamespace(originNode.name, parentNamespace),
87
+ }, originNode.children, {
88
+ createEndTagToken: () => {
89
+ if (!reEndTag.test(token.raw)) {
90
+ return null;
91
+ }
92
+ const endTagRawMatched = token.raw.match(reEndTag);
93
+ if (!endTagRawMatched) {
94
+ throw new Error('Parse error');
95
+ }
96
+ const endTagRaw = endTagRawMatched[0];
97
+ const endTagStartOffset = token.startOffset + token.raw.lastIndexOf(endTagRaw);
98
+ const endTagEndOffset = endTagStartOffset + endTagRaw.length;
99
+ const endTagLocation = this.sliceFragment(endTagStartOffset, endTagEndOffset);
100
+ return {
101
+ ...endTagLocation,
102
+ depth,
103
+ parentNode,
104
+ };
105
+ },
106
+ });
107
+ }
108
+ default: {
109
+ return this.visitExpression({
110
+ ...token,
111
+ depth,
112
+ parentNode,
113
+ }, originNode);
114
+ }
115
+ }
116
+ }
117
+ visitPsBlock(token, childNodes = []) {
118
+ const nodes = super.visitPsBlock(token, childNodes);
119
+ const block = nodes.at(0);
120
+ if (!block || block.type !== 'psblock') {
121
+ throw new ParserError('Parse error', token);
122
+ }
123
+ if (nodes.length > 1) {
124
+ throw new ParserError('Parse error', nodes.at(1));
125
+ }
126
+ return [block];
127
+ }
128
+ visitChildren(
129
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
130
+ children, parentNode) {
131
+ const siblings = super.visitChildren(children, parentNode);
132
+ if (siblings.length > 0) {
133
+ throw new ParserError('Discovered child nodes with differing hierarchy levels', siblings[0]);
134
+ }
135
+ return [];
136
+ }
137
+ visitAttr(token) {
138
+ const attr = super.visitAttr(token, {
139
+ quoteSet: [
140
+ { start: '"', end: '"' },
141
+ { start: "'", end: "'" },
142
+ { start: '{', end: '}' },
143
+ ],
144
+ quoteInValueChars: [
145
+ { start: '"', end: '"' },
146
+ { start: "'", end: "'" },
147
+ { start: '`', end: '`' },
148
+ { start: '${', end: '}' },
149
+ ],
150
+ startState:
151
+ // is shorthand attribute
152
+ token.raw.trim().startsWith('{') ? AttrState.BeforeValue : AttrState.BeforeName,
153
+ });
154
+ if (attr.type === 'spread') {
155
+ return attr;
156
+ }
157
+ let isDynamicValue = attr.startQuote.raw === '{' || undefined;
158
+ let potentialName;
159
+ let isDirective;
160
+ let isDuplicatable = false;
161
+ if (isDynamicValue && attr.name.raw === '') {
162
+ potentialName = attr.value.raw;
163
+ }
164
+ const [baseName, subName] = attr.name.raw.split(':');
165
+ if (subName) {
166
+ isDirective = true;
167
+ if (baseName === 'bind' && !this.specificBindDirective.has(subName)) {
168
+ potentialName = subName;
169
+ isDirective = undefined;
170
+ isDynamicValue = true;
171
+ }
172
+ }
173
+ if (baseName?.toLowerCase() === 'class') {
174
+ isDuplicatable = true;
175
+ if (subName) {
176
+ potentialName = 'class';
177
+ isDynamicValue = true;
178
+ }
179
+ }
180
+ if (attr.startQuote.raw === '{' && attr.endQuote.raw === '}') {
181
+ isDynamicValue = true;
182
+ }
183
+ return {
184
+ ...attr,
185
+ isDynamicValue,
186
+ isDirective,
187
+ isDuplicatable,
188
+ potentialName,
189
+ };
190
+ }
191
+ detectElementType(nodeName) {
192
+ return super.detectElementType(nodeName, /[.A-Z]/);
193
+ }
194
+ visitExpression(token,
195
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
196
+ originBlockNode) {
197
+ const props = ['', 'else', 'pending', 'then', 'catch'];
198
+ const expressions = [];
199
+ const blockType = originBlockNode.type.toLowerCase().replace('block', '');
200
+ const nodeList = new Map();
201
+ for (const prop of props) {
202
+ let node = (originBlockNode[prop] ?? originBlockNode);
203
+ if (nodeList.has(node)) {
204
+ continue;
205
+ }
206
+ if (node.type === 'ElseBlock' && node.children?.[0]?.elseif) {
207
+ node = node.children[0];
208
+ while (node != null) {
209
+ if (!['IfBlock', 'ElseBlock'].includes(node.type)) {
210
+ break;
211
+ }
212
+ const type = node.elseif ? 'elseif' : 'else';
213
+ nodeList.set(node, type);
214
+ node = node.else ?? node.children?.[0] ?? null;
215
+ }
216
+ continue;
217
+ }
218
+ let type = prop || blockType;
219
+ if (prop === 'pending') {
220
+ type = 'await';
221
+ }
222
+ nodeList.set(node, type);
223
+ }
224
+ let lastChild = null;
225
+ for (const [node, type] of nodeList.entries()) {
226
+ let start = node.start;
227
+ let end = node.end;
228
+ if (type === 'await') {
229
+ start = originBlockNode.start;
230
+ }
231
+ end = node.children?.[0]?.start ?? end;
232
+ if (type === 'else' && originBlockNode.type === 'EachBlock') {
233
+ start = lastChild?.end ?? start;
234
+ }
235
+ if (['else', 'elseif'].includes(type) && originBlockNode.type === 'IfBlock') {
236
+ start = lastChild?.end ?? start;
237
+ }
238
+ const tag = this.sliceFragment(start, end);
239
+ if (node.children && Array.isArray(node.children)) {
240
+ lastChild = node.children.at(-1) ?? null;
241
+ }
242
+ const expression = this.visitPsBlock({
243
+ ...tag,
244
+ depth: token.depth,
245
+ parentNode: token.parentNode,
246
+ nodeName: type,
247
+ }, node.children)[0];
248
+ expressions.push(expression);
249
+ }
250
+ const lastText = this.sliceFragment(lastChild?.end ?? originBlockNode.end, originBlockNode.end);
251
+ if (lastText.raw) {
252
+ // Cut before whitespace
253
+ const index = lastText.raw.search(/\S/);
254
+ const lastToken = this.sliceFragment(lastText.startOffset + index, originBlockNode.end);
255
+ if (lastToken.raw) {
256
+ const expression = this.visitPsBlock({
257
+ ...lastToken,
258
+ depth: token.depth,
259
+ parentNode: token.parentNode,
260
+ nodeName: '/' + blockType,
261
+ })[0];
262
+ expressions.push(expression);
263
+ }
264
+ }
265
+ return expressions;
266
+ }
267
+ }
268
+ export const parser = new SvelteParser();
@@ -1,4 +1,5 @@
1
+ /// <reference types="svelte" />
1
2
  import type { Directive, TemplateNode, Attribute, SpreadAttribute } from 'svelte/types/compiler/interfaces';
2
3
  export type SvelteNode = TemplateNode;
3
- export default function svelteParse(template: string): SvelteNode[];
4
+ export declare function svelteParse(template: string): SvelteNode[];
4
5
  export type SvelteDirective = Directive | Attribute | SpreadAttribute;
@@ -1,5 +1,5 @@
1
1
  import { parse } from 'svelte/compiler';
2
- export default function svelteParse(template) {
2
+ export function svelteParse(template) {
3
3
  const ast = parse(template, { customElement: true });
4
4
  const start = ast.html.start;
5
5
  const children = ast.html.children ?? [];
@@ -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.28+0131de5e",
3
+ "version": "4.0.0-rc.1",
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.28+0131de5e",
25
- "@markuplint/ml-ast": "4.0.0-dev.28+0131de5e",
26
- "@markuplint/parser-utils": "4.0.0-dev.28+0131de5e",
27
- "svelte": "^4.2.2"
27
+ "@markuplint/html-parser": "4.0.0-rc.1",
28
+ "@markuplint/ml-ast": "4.0.0-rc.1",
29
+ "@markuplint/parser-utils": "4.0.0-rc.1",
30
+ "svelte": "^4.2.9"
28
31
  },
29
- "gitHead": "0131de5ea9dd6d3fd5472d7b414b66644c758881"
32
+ "gitHead": "3a9dbbf4c3c05de66d402802919ee94a46a5eb67"
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 default function directiveTokenizer(raw: string, rawValue: string, line: number, col: number, startOffset: number): MLASTHTMLAttr;
@@ -1,101 +0,0 @@
1
- import { attrTokenizer } from '@markuplint/html-parser';
2
- import { tokenizer, uuid } from '@markuplint/parser-utils';
3
- // eslint-disable-next-line no-control-regex
4
- const reNameOnly = /^[^\u0000-\u001F /=>{\u007F-\u009F]+/;
5
- // eslint-disable-next-line no-control-regex
6
- const reBeforeStructure = /^(\s*)([^\u0000-\u001F /=>{\u007F-\u009F]+)(\s*)(=)(\s*){(\s*)$/;
7
- const reBeforeStructureWithoutName = /^{(\s*)$/;
8
- const reAfterStructure = /(\s*)}/;
9
- export default function directiveTokenizer(raw, rawValue, line, col, startOffset) {
10
- let spacesBeforeAttrString = '';
11
- let nameChars = '';
12
- let spacesBeforeEqualChars = '';
13
- let equalChars = null;
14
- let spacesAfterEqualChars = '';
15
- let valueChars = '';
16
- const [before, after] = raw.split(rawValue);
17
- const beforeMatchedMap = before?.match(reBeforeStructure);
18
- const beforeWithoutNameMatchedMap = before?.match(reBeforeStructureWithoutName);
19
- const afterMatchedMap = after?.match(reAfterStructure);
20
- if (beforeMatchedMap && afterMatchedMap) {
21
- spacesBeforeAttrString = beforeMatchedMap[1] ?? '';
22
- nameChars = beforeMatchedMap[2] ?? '';
23
- spacesBeforeEqualChars = beforeMatchedMap[3] ?? '';
24
- equalChars = beforeMatchedMap[4] ?? null;
25
- spacesAfterEqualChars = beforeMatchedMap[5] ?? '';
26
- valueChars = (beforeMatchedMap[6] ?? '') + rawValue + (afterMatchedMap[1] ?? '');
27
- }
28
- else if (beforeWithoutNameMatchedMap && afterMatchedMap) {
29
- valueChars = (beforeWithoutNameMatchedMap[1] ?? '') + rawValue + (afterMatchedMap[1] ?? '');
30
- }
31
- else if (reNameOnly.test(raw)) {
32
- const token = attrTokenizer(raw, line, col, startOffset);
33
- token.isDirective = true;
34
- return token;
35
- }
36
- else {
37
- throw new SyntaxError('Illegal attribute token');
38
- }
39
- let offset = startOffset;
40
- const attrToken = tokenizer(raw, line, col, offset);
41
- const spacesBeforeName = tokenizer(spacesBeforeAttrString, line, col, offset);
42
- line = spacesBeforeName.endLine;
43
- col = spacesBeforeName.endCol;
44
- offset = spacesBeforeName.endOffset;
45
- const name = tokenizer(nameChars, line, col, offset);
46
- line = name.endLine;
47
- col = name.endCol;
48
- offset = name.endOffset;
49
- const spacesBeforeEqual = tokenizer(spacesBeforeEqualChars, line, col, offset);
50
- line = spacesBeforeEqual.endLine;
51
- col = spacesBeforeEqual.endCol;
52
- offset = spacesBeforeEqual.endOffset;
53
- const equal = tokenizer(equalChars, line, col, offset);
54
- line = equal.endLine;
55
- col = equal.endCol;
56
- offset = equal.endOffset;
57
- const spacesAfterEqual = tokenizer(spacesAfterEqualChars, line, col, offset);
58
- line = spacesAfterEqual.endLine;
59
- col = spacesAfterEqual.endCol;
60
- offset = spacesAfterEqual.endOffset;
61
- const startQuote = tokenizer('{', line, col, offset);
62
- line = startQuote.endLine;
63
- col = startQuote.endCol;
64
- offset = startQuote.endOffset;
65
- const value = tokenizer(valueChars, line, col, offset);
66
- line = value.endLine;
67
- col = value.endCol;
68
- offset = value.endOffset;
69
- const endQuote = tokenizer('}', line, col, offset);
70
- line = endQuote.endLine;
71
- col = endQuote.endCol;
72
- offset = endQuote.endOffset;
73
- const result = {
74
- type: 'html-attr',
75
- uuid: uuid(),
76
- raw: attrToken.raw,
77
- startOffset: attrToken.startOffset,
78
- endOffset: attrToken.endOffset,
79
- startLine: attrToken.startLine,
80
- endLine: attrToken.endLine,
81
- startCol: attrToken.startCol,
82
- endCol: attrToken.endCol,
83
- spacesBeforeName,
84
- name,
85
- spacesBeforeEqual,
86
- equal,
87
- spacesAfterEqual,
88
- startQuote,
89
- value,
90
- endQuote,
91
- isDirective: true,
92
- isDuplicatable: false,
93
- nodeName: name.raw,
94
- parentNode: null,
95
- nextNode: null,
96
- prevNode: null,
97
- isFragment: false,
98
- isGhost: false,
99
- };
100
- return result;
101
- }
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,289 +0,0 @@
1
- import { getNamespace, parseRawTag } from '@markuplint/html-parser';
2
- import { detectElementType, sliceFragment, uuid } 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 = parseRawTag(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.endSpace,
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
- const reEndTag = new RegExp('{/await}$', 'i');
193
- let endTag = null;
194
- if (reEndTag.test(raw)) {
195
- const endTagRawMatched = raw.match(reEndTag);
196
- if (!endTagRawMatched) {
197
- throw new Error('Parse error');
198
- }
199
- const endTagRaw = endTagRawMatched[0];
200
- const endTagStartOffset = startOffset + raw.indexOf(endTagRaw);
201
- const endTagEndOffset = endTagStartOffset + endTagRaw.length;
202
- const endTagLocation = sliceFragment(rawHtml, endTagStartOffset, endTagEndOffset);
203
- endTag = {
204
- uuid: uuid(),
205
- raw: endTagRaw,
206
- startOffset: endTagStartOffset,
207
- endOffset: endTagEndOffset,
208
- startLine: endTagLocation.startLine,
209
- endLine: endTagLocation.endLine,
210
- startCol: endTagLocation.startCol,
211
- endCol: endTagLocation.endCol,
212
- nodeName: originNode.type,
213
- type: 'psblock',
214
- parentNode,
215
- prevNode,
216
- nextNode,
217
- isFragment: false,
218
- isGhost: false,
219
- };
220
- }
221
- const tags = [pending];
222
- if (then) {
223
- tags.push(then);
224
- }
225
- if (awaitCatch) {
226
- tags.push(awaitCatch);
227
- }
228
- if (endTag) {
229
- tags.push(endTag);
230
- }
231
- return tags;
232
- }
233
- default: {
234
- const startTag = {
235
- uuid: uuid(),
236
- raw,
237
- startOffset,
238
- endOffset,
239
- startLine,
240
- endLine,
241
- startCol,
242
- endCol,
243
- nodeName: originNode.name || originNode.type,
244
- type: 'psblock',
245
- parentNode,
246
- prevNode,
247
- nextNode,
248
- isFragment: false,
249
- isGhost: false,
250
- };
251
- let endTag = null;
252
- if (originNode.children) {
253
- startTag.childNodes = traverse(originNode.children, startTag, rawHtml, options);
254
- const firstChild = startTag.childNodes[0];
255
- if (firstChild) {
256
- startTag.endOffset = firstChild.startOffset;
257
- startTag.endLine = firstChild.startLine;
258
- startTag.endCol = firstChild.startCol;
259
- startTag.raw = rawHtml.slice(startTag.startOffset, startTag.endOffset);
260
- }
261
- const lastChild = startTag.childNodes.at(-1);
262
- if (lastChild && lastChild.endOffset > startTag.endOffset) {
263
- const startOffset = lastChild.endOffset;
264
- const startLine = lastChild.endLine;
265
- const startCol = lastChild.endCol;
266
- const raw = rawHtml.slice(startOffset, endOffset);
267
- endTag = {
268
- uuid: uuid(),
269
- raw,
270
- startOffset,
271
- endOffset,
272
- startLine,
273
- endLine,
274
- startCol,
275
- endCol,
276
- nodeName: originNode.name || originNode.type,
277
- type: 'psblock',
278
- parentNode,
279
- prevNode,
280
- nextNode,
281
- isFragment: false,
282
- isGhost: false,
283
- };
284
- }
285
- }
286
- return endTag == null ? startTag : [startTag, endTag];
287
- }
288
- }
289
- }
@@ -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,169 +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
- const reEndTag = new RegExp('{/each}$', 'i');
17
- const startTagEndOffset = children.length > 0 ? children[0]?.start ?? 0 : raw.replace(reEndTag, '').length + startOffset;
18
- const startTagLocation = sliceFragment(rawHtml, startOffset, startTagEndOffset);
19
- const tag = {
20
- uuid: uuid(),
21
- ...startTagLocation,
22
- nodeName: originNode.type,
23
- type: 'psblock',
24
- parentNode,
25
- prevNode,
26
- nextNode,
27
- isFragment: false,
28
- isGhost: false,
29
- };
30
- if (originNode.children) {
31
- tag.childNodes = traverse(originNode.children, tag, rawHtml, options);
32
- }
33
- let elseTag = null;
34
- if (originNode.else) {
35
- const elseNode = originNode.else;
36
- const elseTagStartOffset = children.length > 0 ? children.at(-1)?.end ?? 0 : startTagLocation.endOffset;
37
- const elseTagLocation = sliceFragment(rawHtml, elseTagStartOffset, elseNode.start);
38
- elseTag = {
39
- uuid: uuid(),
40
- ...elseTagLocation,
41
- nodeName: elseNode.type,
42
- type: 'psblock',
43
- parentNode,
44
- prevNode,
45
- nextNode,
46
- isFragment: false,
47
- isGhost: false,
48
- };
49
- if (elseNode.children) {
50
- elseTag.childNodes = traverse(elseNode.children, elseTag, rawHtml, options);
51
- }
52
- }
53
- let endTag = null;
54
- const endTagRawMatched = raw.match(reEndTag);
55
- if (endTagRawMatched) {
56
- const endTagRaw = endTagRawMatched[0];
57
- const endTagStartOffset = originNode.end - endTagRaw.length;
58
- const endTagEndOffset = originNode.end;
59
- const endTagLocation = sliceFragment(rawHtml, endTagStartOffset, endTagEndOffset);
60
- endTag = {
61
- uuid: uuid(),
62
- raw: endTagRaw,
63
- startOffset: endTagStartOffset,
64
- endOffset: endTagEndOffset,
65
- startLine: endTagLocation.startLine,
66
- endLine: endTagLocation.endLine,
67
- startCol: endTagLocation.startCol,
68
- endCol: endTagLocation.endCol,
69
- nodeName: originNode.type,
70
- type: 'psblock',
71
- parentNode,
72
- prevNode,
73
- nextNode,
74
- isFragment: false,
75
- isGhost: false,
76
- };
77
- }
78
- const tags = [tag];
79
- if (elseTag) {
80
- tags.push(elseTag);
81
- }
82
- if (endTag) {
83
- tags.push(endTag);
84
- }
85
- return tags;
86
- }
87
- function parseIfBlock(
88
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
89
- originNode, raw, rawHtml, statementStartOffset, tokenStartOffset,
90
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
91
- parentNode,
92
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
93
- prevNode,
94
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
95
- nextNode, options) {
96
- const children = originNode.children ?? [];
97
- const startTagEndOffset = children[0]?.start ?? tokenStartOffset;
98
- const startTagLocation = sliceFragment(rawHtml, tokenStartOffset, startTagEndOffset);
99
- const tag = {
100
- uuid: uuid(),
101
- ...startTagLocation,
102
- nodeName: originNode.elseif ? 'ElseIfBlock' : originNode.type,
103
- type: 'psblock',
104
- parentNode,
105
- prevNode,
106
- nextNode,
107
- isFragment: false,
108
- isGhost: false,
109
- };
110
- if (originNode.children) {
111
- tag.childNodes = traverse(originNode.children, tag, rawHtml, options);
112
- }
113
- const elseOrElseIfTags = [];
114
- if (originNode.else) {
115
- const elseNode = originNode.else;
116
- const elseTagStartOffset = children.length > 0 ? children.at(-1)?.end ?? 0 : startTagLocation.endOffset;
117
- const elseTagLocation = sliceFragment(rawHtml, elseTagStartOffset, elseNode.start);
118
- if (elseNode.children) {
119
- if (elseNode.children.length === 1 && elseNode.children[0]?.type === 'IfBlock') {
120
- const elseIfTags = parseIfBlock(elseNode.children[0], elseTagLocation.raw, rawHtml, statementStartOffset, originNode.children?.[0]?.end ?? startTagLocation.endOffset, parentNode, null, null, options);
121
- elseOrElseIfTags.push(...elseIfTags);
122
- }
123
- else {
124
- const elseTag = {
125
- uuid: uuid(),
126
- ...elseTagLocation,
127
- nodeName: elseNode.type,
128
- type: 'psblock',
129
- parentNode,
130
- prevNode,
131
- nextNode,
132
- isFragment: false,
133
- isGhost: false,
134
- };
135
- elseTag.childNodes = traverse(elseNode.children, elseTag, rawHtml, options);
136
- elseOrElseIfTags.push(elseTag);
137
- }
138
- }
139
- }
140
- if (originNode.elseif) {
141
- return [tag, ...elseOrElseIfTags];
142
- }
143
- const endTagRawMatched = raw.match('{/if}');
144
- if (!endTagRawMatched) {
145
- throw new Error('Missing the end token `{/if}`');
146
- }
147
- const endTagRaw = endTagRawMatched[0];
148
- const endTagStartOffset = originNode.end - endTagRaw.length;
149
- const endTagEndOffset = originNode.end;
150
- const endTagLocation = sliceFragment(rawHtml, endTagStartOffset, endTagEndOffset);
151
- const endTag = {
152
- uuid: uuid(),
153
- raw: endTagRaw,
154
- startOffset: endTagStartOffset,
155
- endOffset: endTagEndOffset,
156
- startLine: endTagLocation.startLine,
157
- endLine: endTagLocation.endLine,
158
- startCol: endTagLocation.startCol,
159
- endCol: endTagLocation.endCol,
160
- nodeName: originNode.type,
161
- type: 'psblock',
162
- parentNode,
163
- prevNode,
164
- nextNode,
165
- isFragment: false,
166
- isGhost: false,
167
- };
168
- return [tag, ...elseOrElseIfTags, endTag];
169
- }
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
- }