@markuplint/svelte-parser 4.7.13 → 5.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/parse-block.js +3 -3
- package/lib/parser.d.ts +12 -14
- package/lib/parser.js +254 -235
- package/package.json +9 -6
package/lib/parse-block.js
CHANGED
|
@@ -27,7 +27,7 @@ originBlockNode) {
|
|
|
27
27
|
/**
|
|
28
28
|
* `{/xxx}`
|
|
29
29
|
*/
|
|
30
|
-
const closeToken = parser.sliceFragment(token.
|
|
30
|
+
const closeToken = parser.sliceFragment(token.offset + eachCloseStartIndex, originBlockNode.end);
|
|
31
31
|
const fragment = originBlockNode.type === 'IfBlock'
|
|
32
32
|
? originBlockNode.consequent.nodes
|
|
33
33
|
: originBlockNode.type === 'AwaitBlock'
|
|
@@ -48,10 +48,10 @@ originBlockNode) {
|
|
|
48
48
|
*/
|
|
49
49
|
let openToken;
|
|
50
50
|
if (fragStart != null && fragEnd != null) {
|
|
51
|
-
openToken = parser.sliceFragment(token.
|
|
51
|
+
openToken = parser.sliceFragment(token.offset, fragStart);
|
|
52
52
|
}
|
|
53
53
|
else {
|
|
54
|
-
openToken = parser.sliceFragment(token.
|
|
54
|
+
openToken = parser.sliceFragment(token.offset, eachCloseStartIndex);
|
|
55
55
|
}
|
|
56
56
|
return {
|
|
57
57
|
openToken,
|
package/lib/parser.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SvelteNode } from './svelte-parser/index.js';
|
|
2
|
-
import type { MLASTNodeTreeItem, MLASTParentNode, MLASTPreprocessorSpecificBlock,
|
|
2
|
+
import type { MLASTNodeTreeItem, MLASTParentNode, MLASTPreprocessorSpecificBlock, MLASTBlockBehavior } from '@markuplint/ml-ast';
|
|
3
3
|
import type { ChildToken, ParseOptions, Token } from '@markuplint/parser-utils';
|
|
4
4
|
import { ParserError, Parser } from '@markuplint/parser-utils';
|
|
5
5
|
/**
|
|
@@ -11,7 +11,6 @@ import { ParserError, Parser } from '@markuplint/parser-utils';
|
|
|
11
11
|
*/
|
|
12
12
|
export declare class SvelteParser extends Parser<SvelteNode> {
|
|
13
13
|
#private;
|
|
14
|
-
readonly specificBindDirective: ReadonlySet<string>;
|
|
15
14
|
constructor();
|
|
16
15
|
tokenize(): {
|
|
17
16
|
ast: SvelteNode[];
|
|
@@ -46,13 +45,13 @@ export declare class SvelteParser extends Parser<SvelteNode> {
|
|
|
46
45
|
*
|
|
47
46
|
* @param token - The child token with node name and fragment flag
|
|
48
47
|
* @param childNodes - The child Svelte AST nodes within the block
|
|
49
|
-
* @param
|
|
48
|
+
* @param blockBehavior - The block behavior, or null
|
|
50
49
|
* @returns A single-element tuple containing the preprocessor-specific block
|
|
51
50
|
*/
|
|
52
51
|
visitPsBlock(token: ChildToken & {
|
|
53
52
|
readonly nodeName: string;
|
|
54
53
|
readonly isFragment: boolean;
|
|
55
|
-
}, childNodes?: readonly SvelteNode[],
|
|
54
|
+
}, childNodes?: readonly SvelteNode[], blockBehavior?: MLASTBlockBehavior | null): readonly [MLASTPreprocessorSpecificBlock];
|
|
56
55
|
/**
|
|
57
56
|
* Visits child nodes and verifies that no sibling nodes with differing
|
|
58
57
|
* hierarchy levels are produced. Throws a ParserError if unexpected
|
|
@@ -65,8 +64,10 @@ export declare class SvelteParser extends Parser<SvelteNode> {
|
|
|
65
64
|
visitChildren(children: readonly SvelteNode[], parentNode: MLASTParentNode | null): never[];
|
|
66
65
|
/**
|
|
67
66
|
* Visits an attribute token, handling Svelte-specific syntax including
|
|
68
|
-
* curly-brace expression values
|
|
69
|
-
* `bind
|
|
67
|
+
* curly-brace expression values and shorthand attributes (`{name}`).
|
|
68
|
+
* Directive resolution (`bind:`, `class:`, `on:`, etc.) and IDL attribute
|
|
69
|
+
* mapping are now handled declaratively by svelte-spec's directivePatterns
|
|
70
|
+
* and ml-core's useIDLAttributeNames.
|
|
70
71
|
*
|
|
71
72
|
* @param token - The token representing the attribute
|
|
72
73
|
* @returns The parsed attribute node with Svelte-specific metadata
|
|
@@ -75,8 +76,6 @@ export declare class SvelteParser extends Parser<SvelteNode> {
|
|
|
75
76
|
__rightText?: string;
|
|
76
77
|
}) | {
|
|
77
78
|
isDynamicValue: true | undefined;
|
|
78
|
-
isDirective: true | undefined;
|
|
79
|
-
isDuplicatable: boolean;
|
|
80
79
|
potentialName: string | undefined;
|
|
81
80
|
type: "attr";
|
|
82
81
|
nodeName: string;
|
|
@@ -88,17 +87,16 @@ export declare class SvelteParser extends Parser<SvelteNode> {
|
|
|
88
87
|
startQuote: import("@markuplint/ml-ast").MLASTToken;
|
|
89
88
|
value: import("@markuplint/ml-ast").MLASTToken;
|
|
90
89
|
endQuote: import("@markuplint/ml-ast").MLASTToken;
|
|
90
|
+
isDirective?: true;
|
|
91
91
|
potentialValue?: string;
|
|
92
92
|
valueType?: "string" | "number" | "boolean" | "code";
|
|
93
93
|
candidate?: string;
|
|
94
|
+
isDuplicatable: boolean;
|
|
94
95
|
uuid: string;
|
|
95
96
|
raw: string;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
endLine: number;
|
|
100
|
-
startCol: number;
|
|
101
|
-
endCol: number;
|
|
97
|
+
offset: number;
|
|
98
|
+
line: number;
|
|
99
|
+
col: number;
|
|
102
100
|
__rightText?: string;
|
|
103
101
|
};
|
|
104
102
|
/**
|
package/lib/parser.js
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
2
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
3
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
4
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5
|
-
};
|
|
6
|
-
var _SvelteParser_instances, _SvelteParser_parseAwaitBlock, _SvelteParser_parseEachBlock, _SvelteParser_traverseIfBlock;
|
|
7
|
-
import { getNamespace } from '@markuplint/html-parser';
|
|
8
1
|
import { ParserError, Parser, AttrState } from '@markuplint/parser-utils';
|
|
9
2
|
import { parseBlock } from './parse-block.js';
|
|
10
3
|
import { svelteParse } from './svelte-parser/index.js';
|
|
@@ -33,8 +26,6 @@ export class SvelteParser extends Parser {
|
|
|
33
26
|
],
|
|
34
27
|
maskChar: '-',
|
|
35
28
|
});
|
|
36
|
-
_SvelteParser_instances.add(this);
|
|
37
|
-
this.specificBindDirective = new Set(['group', 'this']);
|
|
38
29
|
}
|
|
39
30
|
tokenize() {
|
|
40
31
|
return {
|
|
@@ -71,7 +62,6 @@ export class SvelteParser extends Parser {
|
|
|
71
62
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
72
63
|
originNode, parentNode, depth) {
|
|
73
64
|
const token = this.sliceFragment(originNode.start, originNode.end);
|
|
74
|
-
const parentNamespace = parentNode && 'namespace' in parentNode ? parentNode.namespace : 'http://www.w3.org/1999/xhtml';
|
|
75
65
|
switch (originNode.type) {
|
|
76
66
|
case 'Text': {
|
|
77
67
|
return this.visitText({
|
|
@@ -102,14 +92,13 @@ export class SvelteParser extends Parser {
|
|
|
102
92
|
const reEndTag = new RegExp(`</${originNode.name}\\s*>$`, 'i');
|
|
103
93
|
const startTagEndOffset = children.length > 0
|
|
104
94
|
? (children[0]?.start ?? 0)
|
|
105
|
-
: token.raw.replace(reEndTag, '').length + token.
|
|
106
|
-
const startTagLocation = this.sliceFragment(token.
|
|
95
|
+
: token.raw.replace(reEndTag, '').length + token.offset;
|
|
96
|
+
const startTagLocation = this.sliceFragment(token.offset, startTagEndOffset);
|
|
107
97
|
return this.visitElement({
|
|
108
98
|
...startTagLocation,
|
|
109
99
|
depth,
|
|
110
100
|
parentNode,
|
|
111
101
|
nodeName: originNode.name,
|
|
112
|
-
namespace: getNamespace(originNode.name, parentNamespace),
|
|
113
102
|
}, originNode.fragment.nodes, {
|
|
114
103
|
createEndTagToken: () => {
|
|
115
104
|
if (!reEndTag.test(token.raw)) {
|
|
@@ -120,7 +109,7 @@ export class SvelteParser extends Parser {
|
|
|
120
109
|
throw new Error('Parse error');
|
|
121
110
|
}
|
|
122
111
|
const endTagRaw = endTagRawMatched[0];
|
|
123
|
-
const endTagStartOffset = token.
|
|
112
|
+
const endTagStartOffset = token.offset + token.raw.lastIndexOf(endTagRaw);
|
|
124
113
|
const endTagEndOffset = endTagStartOffset + endTagRaw.length;
|
|
125
114
|
const endTagLocation = this.sliceFragment(endTagStartOffset, endTagEndOffset);
|
|
126
115
|
return {
|
|
@@ -133,7 +122,7 @@ export class SvelteParser extends Parser {
|
|
|
133
122
|
}
|
|
134
123
|
case 'IfBlock': {
|
|
135
124
|
const expressions = [];
|
|
136
|
-
const ifElseBlocks =
|
|
125
|
+
const ifElseBlocks = this.#traverseIfBlock(originNode, token.offset);
|
|
137
126
|
for (const ifElseBlock of ifElseBlocks) {
|
|
138
127
|
const expression = this.visitPsBlock({
|
|
139
128
|
...ifElseBlock,
|
|
@@ -142,24 +131,27 @@ export class SvelteParser extends Parser {
|
|
|
142
131
|
nodeName: ifElseBlock.type,
|
|
143
132
|
isFragment: false,
|
|
144
133
|
}, ifElseBlock.children, {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
134
|
+
type: {
|
|
135
|
+
if: 'if',
|
|
136
|
+
elseif: 'if:elseif',
|
|
137
|
+
else: 'if:else',
|
|
138
|
+
'/if': 'end',
|
|
139
|
+
}[ifElseBlock.type],
|
|
140
|
+
expression: ifElseBlock.raw,
|
|
141
|
+
})[0];
|
|
150
142
|
expressions.push(expression);
|
|
151
143
|
}
|
|
152
144
|
return expressions;
|
|
153
145
|
}
|
|
154
146
|
case 'EachBlock': {
|
|
155
|
-
return
|
|
147
|
+
return this.#parseEachBlock({
|
|
156
148
|
...token,
|
|
157
149
|
depth,
|
|
158
150
|
parentNode,
|
|
159
151
|
}, originNode);
|
|
160
152
|
}
|
|
161
153
|
case 'AwaitBlock': {
|
|
162
|
-
return
|
|
154
|
+
return this.#parseAwaitBlock({
|
|
163
155
|
...token,
|
|
164
156
|
depth,
|
|
165
157
|
parentNode,
|
|
@@ -253,11 +245,11 @@ export class SvelteParser extends Parser {
|
|
|
253
245
|
*
|
|
254
246
|
* @param token - The child token with node name and fragment flag
|
|
255
247
|
* @param childNodes - The child Svelte AST nodes within the block
|
|
256
|
-
* @param
|
|
248
|
+
* @param blockBehavior - The block behavior, or null
|
|
257
249
|
* @returns A single-element tuple containing the preprocessor-specific block
|
|
258
250
|
*/
|
|
259
|
-
visitPsBlock(token, childNodes = [],
|
|
260
|
-
const nodes = super.visitPsBlock(token, childNodes,
|
|
251
|
+
visitPsBlock(token, childNodes = [], blockBehavior = null) {
|
|
252
|
+
const nodes = super.visitPsBlock(token, childNodes, blockBehavior);
|
|
261
253
|
const block = nodes.at(0);
|
|
262
254
|
if (!block || block.type !== 'psblock') {
|
|
263
255
|
throw new ParserError('Parse error', token);
|
|
@@ -287,8 +279,10 @@ export class SvelteParser extends Parser {
|
|
|
287
279
|
}
|
|
288
280
|
/**
|
|
289
281
|
* Visits an attribute token, handling Svelte-specific syntax including
|
|
290
|
-
* curly-brace expression values
|
|
291
|
-
* `bind
|
|
282
|
+
* curly-brace expression values and shorthand attributes (`{name}`).
|
|
283
|
+
* Directive resolution (`bind:`, `class:`, `on:`, etc.) and IDL attribute
|
|
284
|
+
* mapping are now handled declaratively by svelte-spec's directivePatterns
|
|
285
|
+
* and ml-core's useIDLAttributeNames.
|
|
292
286
|
*
|
|
293
287
|
* @param token - The token representing the attribute
|
|
294
288
|
* @returns The parsed attribute node with Svelte-specific metadata
|
|
@@ -309,35 +303,17 @@ export class SvelteParser extends Parser {
|
|
|
309
303
|
}
|
|
310
304
|
let isDynamicValue = attr.startQuote.raw === '{' || undefined;
|
|
311
305
|
let potentialName;
|
|
312
|
-
|
|
313
|
-
let isDuplicatable = false;
|
|
306
|
+
// Shorthand {name} → potentialName = value
|
|
314
307
|
if (isDynamicValue && attr.name.raw === '') {
|
|
315
308
|
potentialName = attr.value.raw;
|
|
316
309
|
}
|
|
317
|
-
|
|
318
|
-
if (subName) {
|
|
319
|
-
isDirective = true;
|
|
320
|
-
if (baseName === 'bind' && !this.specificBindDirective.has(subName)) {
|
|
321
|
-
potentialName = subName;
|
|
322
|
-
isDirective = undefined;
|
|
323
|
-
isDynamicValue = true;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
if (baseName?.toLowerCase() === 'class') {
|
|
327
|
-
isDuplicatable = true;
|
|
328
|
-
if (subName) {
|
|
329
|
-
potentialName = 'class';
|
|
330
|
-
isDynamicValue = true;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
310
|
+
// Final curly brace check
|
|
333
311
|
if (attr.startQuote.raw === '{' && attr.endQuote.raw === '}') {
|
|
334
312
|
isDynamicValue = true;
|
|
335
313
|
}
|
|
336
314
|
return {
|
|
337
315
|
...attr,
|
|
338
316
|
isDynamicValue,
|
|
339
|
-
isDirective,
|
|
340
|
-
isDuplicatable,
|
|
341
317
|
potentialName,
|
|
342
318
|
};
|
|
343
319
|
}
|
|
@@ -352,217 +328,260 @@ export class SvelteParser extends Parser {
|
|
|
352
328
|
detectElementType(nodeName) {
|
|
353
329
|
return super.detectElementType(nodeName, /^[A-Z]|\./);
|
|
354
330
|
}
|
|
355
|
-
}
|
|
356
|
-
_SvelteParser_instances = new WeakSet(), _SvelteParser_parseAwaitBlock = function _SvelteParser_parseAwaitBlock(token,
|
|
357
|
-
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
358
|
-
originBlockNode) {
|
|
359
|
-
const { closeToken } = parseBlock(this, token, originBlockNode);
|
|
360
|
-
const pendingNodes = originBlockNode.pending?.nodes ?? [];
|
|
361
|
-
const thenNodes = originBlockNode.then?.nodes ?? [];
|
|
362
|
-
const pendingEnd = pendingNodes.at(-1)?.end;
|
|
363
|
-
const thenEnd = thenNodes.at(-1)?.end;
|
|
364
|
-
// @ts-ignore - new Svelte Compiler Type doesn't support `start` and `end` yet.
|
|
365
|
-
const awaitConditionEnd = originBlockNode.expression.end;
|
|
366
331
|
/**
|
|
367
|
-
* `{#await
|
|
368
|
-
*
|
|
332
|
+
* Parses a Svelte `{#await}` block into its constituent preprocessor-specific blocks:
|
|
333
|
+
* the await expression, optional `{:then}` branch, optional `{:catch}` branch,
|
|
334
|
+
* and the closing `{/await}` tag.
|
|
369
335
|
*
|
|
370
|
-
*
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* `}...{:then name}...{:catch name}...{/await}`
|
|
375
|
-
* ^___find
|
|
336
|
+
* @param token - The child token representing the entire await block
|
|
337
|
+
* @param originBlockNode - The Svelte AST AwaitBlock node
|
|
338
|
+
* @returns An array of preprocessor-specific block nodes
|
|
376
339
|
*/
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
340
|
+
#parseAwaitBlock(token,
|
|
341
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
342
|
+
originBlockNode) {
|
|
343
|
+
const { closeToken } = parseBlock(this, token, originBlockNode);
|
|
344
|
+
const pendingNodes = originBlockNode.pending?.nodes ?? [];
|
|
345
|
+
const thenNodes = originBlockNode.then?.nodes ?? [];
|
|
346
|
+
const pendingEnd = pendingNodes.at(-1)?.end;
|
|
347
|
+
const thenEnd = thenNodes.at(-1)?.end;
|
|
348
|
+
// @ts-ignore - new Svelte Compiler Type doesn't support `start` and `end` yet.
|
|
349
|
+
const awaitConditionEnd = originBlockNode.expression.end;
|
|
350
|
+
/**
|
|
351
|
+
* `{#await expression}...{:then name}...{:catch name}...{/await}`
|
|
352
|
+
* find___^ and cut
|
|
353
|
+
*
|
|
354
|
+
* `}...{:then name}...{:catch name}...{/await}`
|
|
355
|
+
*/
|
|
356
|
+
const rawAwaitConditionBelow = this.rawCode.slice(awaitConditionEnd, originBlockNode.end);
|
|
357
|
+
/**
|
|
358
|
+
* `}...{:then name}...{:catch name}...{/await}`
|
|
359
|
+
* ^___find
|
|
360
|
+
*/
|
|
361
|
+
const awaitExpEnd = awaitConditionEnd + rawAwaitConditionBelow.indexOf('}') + 1;
|
|
362
|
+
/**
|
|
363
|
+
* `{#await expression}`
|
|
364
|
+
*/
|
|
365
|
+
const awaitExpToken = this.sliceFragment(token.offset, awaitExpEnd);
|
|
366
|
+
let thenToken = null;
|
|
367
|
+
/**
|
|
368
|
+
* `{#await expression}...{:then name}...{:catch name}...{/await}`
|
|
369
|
+
* find___^
|
|
370
|
+
*/
|
|
371
|
+
const thenExpStart = pendingEnd ?? awaitExpEnd;
|
|
372
|
+
/**
|
|
373
|
+
* `{:then name}...{:catch name}...{/await}`
|
|
374
|
+
*/
|
|
375
|
+
const rawPendingNodesBelow = this.rawCode.slice(thenExpStart, originBlockNode.end);
|
|
376
|
+
if (
|
|
377
|
+
// eslint-disable-next-line regexp/strict
|
|
378
|
+
/^{\s*:then[\s|}]/.test(rawPendingNodesBelow)) {
|
|
379
|
+
let thenExpEndCharOffset;
|
|
380
|
+
if (originBlockNode.value) {
|
|
381
|
+
const thenIdentifierEnd =
|
|
382
|
+
// @ts-ignore - new Svelte Compiler Type doesn't support `start` and `end` yet.
|
|
383
|
+
originBlockNode.value.end;
|
|
384
|
+
const rawThenExpCloseCharAndBelow = this.rawCode.slice(thenIdentifierEnd, originBlockNode.end);
|
|
385
|
+
const thenExpEndCharIndex = rawThenExpCloseCharAndBelow.indexOf('}') + 1;
|
|
386
|
+
thenExpEndCharOffset = thenIdentifierEnd + thenExpEndCharIndex;
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
thenExpEndCharOffset = thenExpStart + rawPendingNodesBelow.indexOf('}') + 1;
|
|
390
|
+
}
|
|
391
|
+
thenToken = this.sliceFragment(token.offset + thenExpStart, thenExpEndCharOffset);
|
|
403
392
|
}
|
|
404
|
-
|
|
405
|
-
|
|
393
|
+
let catchToken = null;
|
|
394
|
+
/**
|
|
395
|
+
* `{#await expression}...{:then name}...{:catch name}...{/await}`
|
|
396
|
+
* find___^
|
|
397
|
+
*
|
|
398
|
+
* If `then` block is not found:
|
|
399
|
+
*
|
|
400
|
+
* `{#await expression}...{:catch name}...{/await}`
|
|
401
|
+
* find___^
|
|
402
|
+
*/
|
|
403
|
+
const catchExpStart = thenToken
|
|
404
|
+
? (thenEnd ?? thenToken.offset + thenToken.raw.length)
|
|
405
|
+
: (pendingEnd ?? awaitExpEnd);
|
|
406
|
+
/**
|
|
407
|
+
* `{:catch name}...{/await}`
|
|
408
|
+
*/
|
|
409
|
+
const rawThenNodesBelow = this.rawCode.slice(catchExpStart, originBlockNode.end);
|
|
410
|
+
if (
|
|
411
|
+
// eslint-disable-next-line regexp/strict
|
|
412
|
+
/^{\s*:catch[\s|}]/.test(rawThenNodesBelow)) {
|
|
413
|
+
let catchExpEndCharOffset;
|
|
414
|
+
if (originBlockNode.error) {
|
|
415
|
+
const catchIdentifierEnd =
|
|
416
|
+
// @ts-ignore - new Svelte Compiler Type doesn't support `start` and `end` yet.
|
|
417
|
+
originBlockNode.error.end;
|
|
418
|
+
const rawCatchExpCloseCharAndBelow = this.rawCode.slice(catchIdentifierEnd, originBlockNode.end);
|
|
419
|
+
const catchExpEndCharIndex = rawCatchExpCloseCharAndBelow.indexOf('}') + 1;
|
|
420
|
+
catchExpEndCharOffset = catchIdentifierEnd + catchExpEndCharIndex;
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
catchExpEndCharOffset = catchExpStart + rawThenNodesBelow.indexOf('}') + 1;
|
|
424
|
+
}
|
|
425
|
+
catchToken = this.sliceFragment(token.offset + catchExpStart, catchExpEndCharOffset);
|
|
406
426
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
if (originBlockNode.error) {
|
|
431
|
-
const catchIdentifierEnd =
|
|
432
|
-
// @ts-ignore - new Svelte Compiler Type doesn't support `start` and `end` yet.
|
|
433
|
-
originBlockNode.error.end;
|
|
434
|
-
const rawCatchExpCloseCharAndBelow = this.rawCode.slice(catchIdentifierEnd, originBlockNode.end);
|
|
435
|
-
const catchExpEndCharIndex = rawCatchExpCloseCharAndBelow.indexOf('}') + 1;
|
|
436
|
-
catchExpEndCharOffset = catchIdentifierEnd + catchExpEndCharIndex;
|
|
427
|
+
const expressions = [
|
|
428
|
+
this.visitPsBlock({
|
|
429
|
+
...awaitExpToken,
|
|
430
|
+
depth: token.depth,
|
|
431
|
+
parentNode: token.parentNode,
|
|
432
|
+
nodeName: 'await',
|
|
433
|
+
isFragment: false,
|
|
434
|
+
}, originBlockNode.pending?.nodes, {
|
|
435
|
+
type: 'await',
|
|
436
|
+
expression: awaitExpToken.raw,
|
|
437
|
+
})[0],
|
|
438
|
+
];
|
|
439
|
+
if (thenToken) {
|
|
440
|
+
expressions.push(this.visitPsBlock({
|
|
441
|
+
...thenToken,
|
|
442
|
+
depth: token.depth,
|
|
443
|
+
parentNode: token.parentNode,
|
|
444
|
+
nodeName: 'await:then',
|
|
445
|
+
isFragment: false,
|
|
446
|
+
}, originBlockNode.then?.nodes, {
|
|
447
|
+
type: 'await:then',
|
|
448
|
+
expression: thenToken.raw,
|
|
449
|
+
})[0]);
|
|
437
450
|
}
|
|
438
|
-
|
|
439
|
-
|
|
451
|
+
if (catchToken) {
|
|
452
|
+
expressions.push(this.visitPsBlock({
|
|
453
|
+
...catchToken,
|
|
454
|
+
depth: token.depth,
|
|
455
|
+
parentNode: token.parentNode,
|
|
456
|
+
nodeName: 'await:catch',
|
|
457
|
+
isFragment: false,
|
|
458
|
+
}, originBlockNode.catch?.nodes, {
|
|
459
|
+
type: 'await:catch',
|
|
460
|
+
expression: catchToken.raw,
|
|
461
|
+
})[0]);
|
|
440
462
|
}
|
|
441
|
-
catchToken = this.sliceFragment(token.startOffset + catchExpStart, catchExpEndCharOffset);
|
|
442
|
-
}
|
|
443
|
-
const expressions = [];
|
|
444
|
-
expressions.push(this.visitPsBlock({
|
|
445
|
-
...awaitExpToken,
|
|
446
|
-
depth: token.depth,
|
|
447
|
-
parentNode: token.parentNode,
|
|
448
|
-
nodeName: 'await',
|
|
449
|
-
isFragment: false,
|
|
450
|
-
}, originBlockNode.pending?.nodes, 'await')[0]);
|
|
451
|
-
if (thenToken) {
|
|
452
463
|
expressions.push(this.visitPsBlock({
|
|
453
|
-
...
|
|
464
|
+
...closeToken,
|
|
454
465
|
depth: token.depth,
|
|
455
466
|
parentNode: token.parentNode,
|
|
456
|
-
nodeName: 'await
|
|
467
|
+
nodeName: '/await',
|
|
457
468
|
isFragment: false,
|
|
458
|
-
},
|
|
469
|
+
}, undefined, {
|
|
470
|
+
type: 'end',
|
|
471
|
+
expression: closeToken.raw,
|
|
472
|
+
})[0]);
|
|
473
|
+
return expressions;
|
|
459
474
|
}
|
|
460
|
-
|
|
475
|
+
/**
|
|
476
|
+
* Parses a Svelte `{#each}` block into its constituent preprocessor-specific blocks:
|
|
477
|
+
* the each expression, optional `{:else}` fallback branch,
|
|
478
|
+
* and the closing `{/each}` tag.
|
|
479
|
+
*
|
|
480
|
+
* @param token - The child token representing the entire each block
|
|
481
|
+
* @param originBlockNode - The Svelte AST EachBlock node
|
|
482
|
+
* @returns An array of preprocessor-specific block nodes
|
|
483
|
+
*/
|
|
484
|
+
#parseEachBlock(token,
|
|
485
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
486
|
+
originBlockNode) {
|
|
487
|
+
const expressions = [];
|
|
488
|
+
/**
|
|
489
|
+
* `{/each}`
|
|
490
|
+
*/
|
|
491
|
+
const { closeToken } = parseBlock(this, token, originBlockNode);
|
|
492
|
+
/**
|
|
493
|
+
* `{#each expression as name}...{:else}...{/each}`
|
|
494
|
+
* find___^
|
|
495
|
+
*/
|
|
496
|
+
const bodyStart = originBlockNode.body.nodes.at(0)?.start ?? closeToken.offset;
|
|
497
|
+
/**
|
|
498
|
+
* `{#each expression as name}...{:else}...{/each}`
|
|
499
|
+
* find___^
|
|
500
|
+
*/
|
|
501
|
+
const fallbackScopeStart = originBlockNode.fallback?.nodes.at(0)?.start ?? closeToken.offset;
|
|
502
|
+
/**
|
|
503
|
+
* `{#each expression as name}...{:else}`
|
|
504
|
+
*/
|
|
505
|
+
const rawUntilFallbackScope = this.rawCode.slice(token.offset, fallbackScopeStart);
|
|
506
|
+
let elseToken = null;
|
|
507
|
+
/**
|
|
508
|
+
* `{#each expression as name}...{:else}`
|
|
509
|
+
* find___^
|
|
510
|
+
*/
|
|
511
|
+
// eslint-disable-next-line regexp/strict
|
|
512
|
+
const elseTokenStart = rawUntilFallbackScope.match(/{\s*:else\s*}$/)?.index;
|
|
513
|
+
if (elseTokenStart != null) {
|
|
514
|
+
elseToken = this.sliceFragment(token.offset + elseTokenStart, fallbackScopeStart);
|
|
515
|
+
}
|
|
516
|
+
const eachToken = this.sliceFragment(token.offset, bodyStart);
|
|
461
517
|
expressions.push(this.visitPsBlock({
|
|
462
|
-
...
|
|
518
|
+
...eachToken,
|
|
463
519
|
depth: token.depth,
|
|
464
520
|
parentNode: token.parentNode,
|
|
465
|
-
nodeName: '
|
|
521
|
+
nodeName: 'each',
|
|
466
522
|
isFragment: false,
|
|
467
|
-
}, originBlockNode.
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}, _SvelteParser_parseEachBlock = function _SvelteParser_parseEachBlock(token,
|
|
478
|
-
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
479
|
-
originBlockNode) {
|
|
480
|
-
const expressions = [];
|
|
481
|
-
/**
|
|
482
|
-
* `{/each}`
|
|
483
|
-
*/
|
|
484
|
-
const { closeToken } = parseBlock(this, token, originBlockNode);
|
|
485
|
-
/**
|
|
486
|
-
* `{#each expression as name}...{:else}...{/each}`
|
|
487
|
-
* find___^
|
|
488
|
-
*/
|
|
489
|
-
const bodyStart = originBlockNode.body.nodes.at(0)?.start ?? closeToken.startOffset;
|
|
490
|
-
/**
|
|
491
|
-
* `{#each expression as name}...{:else}...{/each}`
|
|
492
|
-
* find___^
|
|
493
|
-
*/
|
|
494
|
-
const fallbackScopeStart = originBlockNode.fallback?.nodes.at(0)?.start ?? closeToken.startOffset;
|
|
495
|
-
/**
|
|
496
|
-
* `{#each expression as name}...{:else}`
|
|
497
|
-
*/
|
|
498
|
-
const rawUntilFallbackScope = this.rawCode.slice(token.startOffset, fallbackScopeStart);
|
|
499
|
-
let elseToken = null;
|
|
500
|
-
/**
|
|
501
|
-
* `{#each expression as name}...{:else}`
|
|
502
|
-
* find___^
|
|
503
|
-
*/
|
|
504
|
-
// eslint-disable-next-line regexp/strict
|
|
505
|
-
const elseTokenStart = rawUntilFallbackScope.match(/{\s*:else\s*}$/)?.index;
|
|
506
|
-
if (elseTokenStart != null) {
|
|
507
|
-
elseToken = this.sliceFragment(token.startOffset + elseTokenStart, fallbackScopeStart);
|
|
508
|
-
}
|
|
509
|
-
const eachToken = this.sliceFragment(token.startOffset, bodyStart);
|
|
510
|
-
expressions.push(this.visitPsBlock({
|
|
511
|
-
...eachToken,
|
|
512
|
-
depth: token.depth,
|
|
513
|
-
parentNode: token.parentNode,
|
|
514
|
-
nodeName: 'each',
|
|
515
|
-
isFragment: false,
|
|
516
|
-
}, originBlockNode.body.nodes, 'each')[0]);
|
|
517
|
-
if (elseToken) {
|
|
523
|
+
}, originBlockNode.body.nodes, { type: 'each', expression: eachToken.raw })[0]);
|
|
524
|
+
if (elseToken) {
|
|
525
|
+
expressions.push(this.visitPsBlock({
|
|
526
|
+
...elseToken,
|
|
527
|
+
depth: token.depth,
|
|
528
|
+
parentNode: token.parentNode,
|
|
529
|
+
nodeName: 'each:empty',
|
|
530
|
+
isFragment: false,
|
|
531
|
+
}, originBlockNode.fallback?.nodes, { type: 'each:empty', expression: elseToken.raw })[0]);
|
|
532
|
+
}
|
|
518
533
|
expressions.push(this.visitPsBlock({
|
|
519
|
-
...
|
|
534
|
+
...closeToken,
|
|
520
535
|
depth: token.depth,
|
|
521
536
|
parentNode: token.parentNode,
|
|
522
|
-
nodeName: 'each
|
|
537
|
+
nodeName: '/each',
|
|
523
538
|
isFragment: false,
|
|
524
|
-
},
|
|
539
|
+
}, undefined, { type: 'end', expression: closeToken.raw })[0]);
|
|
540
|
+
return expressions;
|
|
525
541
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
542
|
+
/**
|
|
543
|
+
* Recursively traverses a Svelte `{#if}` block and its chained `{:else if}` / `{:else}`
|
|
544
|
+
* branches, producing a flat list of token segments with their conditional type labels
|
|
545
|
+
* and child node arrays.
|
|
546
|
+
*
|
|
547
|
+
* @param originBlockNode - The Svelte AST IfBlock node to traverse
|
|
548
|
+
* @param start - The source offset where this block segment begins
|
|
549
|
+
* @param type - The conditional branch type: 'if', 'elseif', or 'else'
|
|
550
|
+
* @returns A flat array of token segments with children and type labels
|
|
551
|
+
*/
|
|
552
|
+
#traverseIfBlock(
|
|
553
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
554
|
+
originBlockNode, start, type = 'if') {
|
|
555
|
+
const result = [];
|
|
556
|
+
const end = originBlockNode.consequent.nodes?.[0]?.start ?? originBlockNode.end;
|
|
557
|
+
const tag = this.sliceFragment(start, end);
|
|
558
|
+
const children = originBlockNode.consequent.nodes;
|
|
559
|
+
result.push({ ...tag, children, type });
|
|
560
|
+
if (originBlockNode.alternate) {
|
|
561
|
+
if (originBlockNode.alternate.nodes?.[0]?.type === 'IfBlock') {
|
|
562
|
+
const elseif = this.#traverseIfBlock(originBlockNode.alternate.nodes[0], children.at(-1)?.end ?? start, 'elseif');
|
|
563
|
+
result.push(...elseif);
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
const start = children.at(-1)?.end ?? originBlockNode.end;
|
|
567
|
+
const end = originBlockNode.alternate.nodes?.[0]?.start;
|
|
568
|
+
const tag = this.sliceFragment(start, end);
|
|
569
|
+
result.push({
|
|
570
|
+
...tag,
|
|
571
|
+
children: originBlockNode.alternate.nodes,
|
|
572
|
+
type: 'else',
|
|
573
|
+
});
|
|
574
|
+
}
|
|
546
575
|
}
|
|
547
|
-
|
|
548
|
-
const start = children.at(-1)?.end ?? originBlockNode.end;
|
|
549
|
-
const end = originBlockNode.
|
|
576
|
+
{
|
|
577
|
+
const start = result.at(-1)?.children.at(-1)?.end ?? originBlockNode.end;
|
|
578
|
+
const end = originBlockNode.end;
|
|
550
579
|
const tag = this.sliceFragment(start, end);
|
|
551
|
-
|
|
552
|
-
...tag,
|
|
553
|
-
|
|
554
|
-
type: 'else',
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
{
|
|
559
|
-
const start = result.at(-1)?.children.at(-1)?.end ?? originBlockNode.end;
|
|
560
|
-
const end = originBlockNode.end;
|
|
561
|
-
const tag = this.sliceFragment(start, end);
|
|
562
|
-
if (tag.raw) {
|
|
563
|
-
result.push({ ...tag, children: [], type: '/if' });
|
|
580
|
+
if (tag.raw) {
|
|
581
|
+
result.push({ ...tag, children: [], type: '/if' });
|
|
582
|
+
}
|
|
564
583
|
}
|
|
584
|
+
return result;
|
|
565
585
|
}
|
|
566
|
-
|
|
567
|
-
};
|
|
586
|
+
}
|
|
568
587
|
export const parser = new SvelteParser();
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markuplint/svelte-parser",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0-alpha.0",
|
|
4
4
|
"description": "Svelte parser for markuplint",
|
|
5
5
|
"repository": "git@github.com:markuplint/markuplint.git",
|
|
6
6
|
"author": "Yusuke Hirao <yusukehirao@me.com>",
|
|
7
7
|
"license": "MIT",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=22"
|
|
10
|
+
},
|
|
8
11
|
"type": "module",
|
|
9
12
|
"exports": {
|
|
10
13
|
".": {
|
|
@@ -29,10 +32,10 @@
|
|
|
29
32
|
"clean": "tsc --build --clean tsconfig.build.json"
|
|
30
33
|
},
|
|
31
34
|
"dependencies": {
|
|
32
|
-
"@markuplint/html-parser": "
|
|
33
|
-
"@markuplint/ml-ast": "
|
|
34
|
-
"@markuplint/parser-utils": "
|
|
35
|
-
"svelte": "5.
|
|
35
|
+
"@markuplint/html-parser": "5.0.0-alpha.0",
|
|
36
|
+
"@markuplint/ml-ast": "5.0.0-alpha.0",
|
|
37
|
+
"@markuplint/parser-utils": "5.0.0-alpha.0",
|
|
38
|
+
"svelte": "5.53.0"
|
|
36
39
|
},
|
|
37
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "13dcfc84ec83d87360c720e253383b60767e1b56"
|
|
38
41
|
}
|