@markuplint/svelte-parser 4.7.13 → 5.0.0-alpha.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.
@@ -27,7 +27,7 @@ originBlockNode) {
27
27
  /**
28
28
  * `{/xxx}`
29
29
  */
30
- const closeToken = parser.sliceFragment(token.startOffset + eachCloseStartIndex, originBlockNode.end);
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.startOffset, fragStart);
51
+ openToken = parser.sliceFragment(token.offset, fragStart);
52
52
  }
53
53
  else {
54
- openToken = parser.sliceFragment(token.startOffset, eachCloseStartIndex);
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, MLASTPreprocessorSpecificBlockConditionalType } from '@markuplint/ml-ast';
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 conditionalType - The conditional block type identifier, or null
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[], conditionalType?: MLASTPreprocessorSpecificBlockConditionalType): readonly [MLASTPreprocessorSpecificBlock];
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, shorthand attributes (`{name}`),
69
- * `bind:` / `class:` directives, and duplicatable class attributes.
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 acceptedAttrNames.
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
- startOffset: number;
97
- endOffset: number;
98
- startLine: number;
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.startOffset;
106
- const startTagLocation = this.sliceFragment(token.startOffset, startTagEndOffset);
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.startOffset + token.raw.lastIndexOf(endTagRaw);
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 = __classPrivateFieldGet(this, _SvelteParser_instances, "m", _SvelteParser_traverseIfBlock).call(this, originNode, token.startOffset);
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
- if: 'if',
146
- elseif: 'if:elseif',
147
- else: 'if:else',
148
- '/if': 'end',
149
- }[ifElseBlock.type])[0];
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 __classPrivateFieldGet(this, _SvelteParser_instances, "m", _SvelteParser_parseEachBlock).call(this, {
147
+ return this.#parseEachBlock({
156
148
  ...token,
157
149
  depth,
158
150
  parentNode,
159
151
  }, originNode);
160
152
  }
161
153
  case 'AwaitBlock': {
162
- return __classPrivateFieldGet(this, _SvelteParser_instances, "m", _SvelteParser_parseAwaitBlock).call(this, {
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 conditionalType - The conditional block type identifier, or null
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 = [], conditionalType = null) {
260
- const nodes = super.visitPsBlock(token, childNodes, conditionalType);
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, shorthand attributes (`{name}`),
291
- * `bind:` / `class:` directives, and duplicatable class attributes.
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 acceptedAttrNames.
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
- let isDirective;
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
- const [baseName, subName] = attr.name.raw.split(':');
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 expression}...{:then name}...{:catch name}...{/await}`
368
- * find___^ and cut
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
- * `}...{:then name}...{:catch name}...{/await}`
371
- */
372
- const rawAwaitConditionBelow = this.rawCode.slice(awaitConditionEnd, originBlockNode.end);
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
- const awaitExpEnd = awaitConditionEnd + rawAwaitConditionBelow.indexOf('}') + 1;
378
- /**
379
- * `{#await expression}`
380
- */
381
- const awaitExpToken = this.sliceFragment(token.startOffset, awaitExpEnd);
382
- let thenToken = null;
383
- /**
384
- * `{#await expression}...{:then name}...{:catch name}...{/await}`
385
- * find___^
386
- */
387
- const thenExpStart = pendingEnd ?? awaitExpEnd;
388
- /**
389
- * `{:then name}...{:catch name}...{/await}`
390
- */
391
- const rawPendingNodesBelow = this.rawCode.slice(thenExpStart, originBlockNode.end);
392
- if (
393
- // eslint-disable-next-line regexp/strict
394
- /^{\s*:then[\s|}]/.test(rawPendingNodesBelow)) {
395
- let thenExpEndCharOffset;
396
- if (originBlockNode.value) {
397
- const thenIdentifierEnd =
398
- // @ts-ignore - new Svelte Compiler Type doesn't support `start` and `end` yet.
399
- originBlockNode.value.end;
400
- const rawThenExpCloseCharAndBelow = this.rawCode.slice(thenIdentifierEnd, originBlockNode.end);
401
- const thenExpEndCharIndex = rawThenExpCloseCharAndBelow.indexOf('}') + 1;
402
- thenExpEndCharOffset = thenIdentifierEnd + thenExpEndCharIndex;
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
- else {
405
- thenExpEndCharOffset = thenExpStart + rawPendingNodesBelow.indexOf('}') + 1;
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
- thenToken = this.sliceFragment(token.startOffset + thenExpStart, thenExpEndCharOffset);
408
- }
409
- let catchToken = null;
410
- /**
411
- * `{#await expression}...{:then name}...{:catch name}...{/await}`
412
- * find___^
413
- *
414
- * If `then` block is not found:
415
- *
416
- * `{#await expression}...{:catch name}...{/await}`
417
- * find___^
418
- */
419
- const catchExpStart = thenToken
420
- ? (thenEnd ?? thenToken.startOffset + thenToken.raw.length)
421
- : (pendingEnd ?? awaitExpEnd);
422
- /**
423
- * `{:catch name}...{/await}`
424
- */
425
- const rawThenNodesBelow = this.rawCode.slice(catchExpStart, originBlockNode.end);
426
- if (
427
- // eslint-disable-next-line regexp/strict
428
- /^{\s*:catch[\s|}]/.test(rawThenNodesBelow)) {
429
- let catchExpEndCharOffset;
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
- else {
439
- catchExpEndCharOffset = catchExpStart + rawThenNodesBelow.indexOf('}') + 1;
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
- ...thenToken,
464
+ ...closeToken,
454
465
  depth: token.depth,
455
466
  parentNode: token.parentNode,
456
- nodeName: 'await:then',
467
+ nodeName: '/await',
457
468
  isFragment: false,
458
- }, originBlockNode.then?.nodes, 'await:then')[0]);
469
+ }, undefined, {
470
+ type: 'end',
471
+ expression: closeToken.raw,
472
+ })[0]);
473
+ return expressions;
459
474
  }
460
- if (catchToken) {
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
- ...catchToken,
518
+ ...eachToken,
463
519
  depth: token.depth,
464
520
  parentNode: token.parentNode,
465
- nodeName: 'await:catch',
521
+ nodeName: 'each',
466
522
  isFragment: false,
467
- }, originBlockNode.catch?.nodes, 'await:catch')[0]);
468
- }
469
- expressions.push(this.visitPsBlock({
470
- ...closeToken,
471
- depth: token.depth,
472
- parentNode: token.parentNode,
473
- nodeName: '/await',
474
- isFragment: false,
475
- }, undefined, 'end')[0]);
476
- return expressions;
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
- ...elseToken,
534
+ ...closeToken,
520
535
  depth: token.depth,
521
536
  parentNode: token.parentNode,
522
- nodeName: 'each:empty',
537
+ nodeName: '/each',
523
538
  isFragment: false,
524
- }, originBlockNode.fallback?.nodes, 'each:empty')[0]);
539
+ }, undefined, { type: 'end', expression: closeToken.raw })[0]);
540
+ return expressions;
525
541
  }
526
- expressions.push(this.visitPsBlock({
527
- ...closeToken,
528
- depth: token.depth,
529
- parentNode: token.parentNode,
530
- nodeName: '/each',
531
- isFragment: false,
532
- }, undefined, 'end')[0]);
533
- return expressions;
534
- }, _SvelteParser_traverseIfBlock = function _SvelteParser_traverseIfBlock(
535
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
536
- originBlockNode, start, type = 'if') {
537
- const result = [];
538
- const end = originBlockNode.consequent.nodes?.[0]?.start ?? originBlockNode.end;
539
- const tag = this.sliceFragment(start, end);
540
- const children = originBlockNode.consequent.nodes;
541
- result.push({ ...tag, children, type });
542
- if (originBlockNode.alternate) {
543
- if (originBlockNode.alternate.nodes?.[0]?.type === 'IfBlock') {
544
- const elseif = __classPrivateFieldGet(this, _SvelteParser_instances, "m", _SvelteParser_traverseIfBlock).call(this, originBlockNode.alternate.nodes[0], children.at(-1)?.end ?? start, 'elseif');
545
- result.push(...elseif);
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
- else {
548
- const start = children.at(-1)?.end ?? originBlockNode.end;
549
- const end = originBlockNode.alternate.nodes?.[0]?.start;
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
- result.push({
552
- ...tag,
553
- children: originBlockNode.alternate.nodes,
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
- return result;
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": "4.7.13",
3
+ "version": "5.0.0-alpha.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>",
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": "4.6.23",
33
- "@markuplint/ml-ast": "4.4.11",
34
- "@markuplint/parser-utils": "4.8.11",
35
- "svelte": "5.50.0"
35
+ "@markuplint/html-parser": "5.0.0-alpha.1",
36
+ "@markuplint/ml-ast": "5.0.0-alpha.1",
37
+ "@markuplint/parser-utils": "5.0.0-alpha.1",
38
+ "svelte": "5.53.2"
36
39
  },
37
- "gitHead": "193ee7c1262bbed95424e38efdf1a8e56ff049f4"
40
+ "gitHead": "78a295e73a097a1ce09c777c06fa21ab68136387"
38
41
  }