svelte 5.44.1 → 5.45.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.
@@ -0,0 +1,865 @@
1
+ /** @import { AST } from '#compiler'; */
2
+ /** @import { Context, Visitors } from 'esrap' */
3
+ import * as esrap from 'esrap';
4
+ import ts from 'esrap/languages/ts';
5
+ import { is_void } from '../../utils.js';
6
+
7
+ /** Threshold for when content should be formatted on separate lines */
8
+ const LINE_BREAK_THRESHOLD = 50;
9
+
10
+ /**
11
+ * `print` converts a Svelte AST node back into Svelte source code.
12
+ * It is primarily intended for tools that parse and transform components using the compiler’s modern AST representation.
13
+ *
14
+ * `print(ast)` requires an AST node produced by parse with modern: true, or any sub-node within that modern AST.
15
+ * The result contains the generated source and a corresponding source map.
16
+ * The output is valid Svelte, but formatting details such as whitespace or quoting may differ from the original.
17
+ * @param {AST.SvelteNode} ast
18
+ * @param {import('./types.js').Options | undefined} options
19
+ */
20
+ export function print(ast, options = undefined) {
21
+ return esrap.print(
22
+ ast,
23
+ /** @type {Visitors<AST.SvelteNode>} */ ({
24
+ ...ts({
25
+ comments: ast.type === 'Root' ? ast.comments : [],
26
+ getLeadingComments: options?.getLeadingComments,
27
+ getTrailingComments: options?.getTrailingComments
28
+ }),
29
+ ...svelte_visitors,
30
+ ...css_visitors
31
+ })
32
+ );
33
+ }
34
+
35
+ /**
36
+ * @param {Context} context
37
+ * @param {AST.SvelteNode} node
38
+ * @param {boolean} allow_inline
39
+ */
40
+ function block(context, node, allow_inline = false) {
41
+ const child_context = context.new();
42
+ child_context.visit(node);
43
+
44
+ if (child_context.empty()) {
45
+ return;
46
+ }
47
+
48
+ if (allow_inline && !child_context.multiline) {
49
+ context.append(child_context);
50
+ } else {
51
+ context.indent();
52
+ context.newline();
53
+ context.append(child_context);
54
+ context.dedent();
55
+ context.newline();
56
+ }
57
+ }
58
+
59
+ /**
60
+ * @param {AST.BaseElement['attributes']} attributes
61
+ * @param {Context} context
62
+ * @returns {boolean} true if attributes were formatted on multiple lines
63
+ */
64
+ function attributes(attributes, context) {
65
+ if (attributes.length === 0) {
66
+ return false;
67
+ }
68
+
69
+ // Measure total width of all attributes when rendered inline
70
+ const child_context = context.new();
71
+
72
+ for (const attribute of attributes) {
73
+ child_context.write(' ');
74
+ child_context.visit(attribute);
75
+ }
76
+
77
+ const multiline = child_context.measure() > LINE_BREAK_THRESHOLD;
78
+
79
+ if (multiline) {
80
+ context.indent();
81
+ for (const attribute of attributes) {
82
+ context.newline();
83
+ context.visit(attribute);
84
+ }
85
+ context.dedent();
86
+ context.newline();
87
+ } else {
88
+ context.append(child_context);
89
+ }
90
+
91
+ return multiline;
92
+ }
93
+
94
+ /**
95
+ * @param {AST.BaseElement} node
96
+ * @param {Context} context
97
+ */
98
+ function base_element(node, context) {
99
+ const child_context = context.new();
100
+
101
+ child_context.write('<' + node.name);
102
+
103
+ // Handle special Svelte components/elements that need 'this' attribute
104
+ if (node.type === 'SvelteComponent') {
105
+ child_context.write(' this={');
106
+ child_context.visit(/** @type {AST.SvelteComponent} */ (node).expression);
107
+ child_context.write('}');
108
+ } else if (node.type === 'SvelteElement') {
109
+ child_context.write(' this={');
110
+ child_context.visit(/** @type {AST.SvelteElement} */ (node).tag);
111
+ child_context.write('}');
112
+ }
113
+
114
+ const multiline_attributes = attributes(node.attributes, child_context);
115
+
116
+ const is_self_closing =
117
+ is_void(node.name) || (node.type === 'Component' && node.fragment.nodes.length === 0);
118
+ let multiline_content = false;
119
+
120
+ if (is_self_closing) {
121
+ child_context.write(`${multiline_attributes ? '' : ' '}/>`);
122
+ } else {
123
+ child_context.write('>');
124
+
125
+ // Process the element's content in a separate context for measurement
126
+ const content_context = child_context.new();
127
+ const allow_inline_content = child_context.measure() < LINE_BREAK_THRESHOLD;
128
+ block(content_context, node.fragment, allow_inline_content);
129
+
130
+ // Determine if content should be formatted on multiple lines
131
+ multiline_content = content_context.measure() > LINE_BREAK_THRESHOLD;
132
+
133
+ if (multiline_content) {
134
+ child_context.newline();
135
+
136
+ // Only indent if attributes are inline and content itself isn't already multiline
137
+ const should_indent = !multiline_attributes && !content_context.multiline;
138
+ if (should_indent) {
139
+ child_context.indent();
140
+ }
141
+
142
+ child_context.append(content_context);
143
+
144
+ if (should_indent) {
145
+ child_context.dedent();
146
+ }
147
+
148
+ child_context.newline();
149
+ } else {
150
+ child_context.append(content_context);
151
+ }
152
+
153
+ child_context.write(`</${node.name}>`);
154
+ }
155
+
156
+ const break_line_after = child_context.measure() > LINE_BREAK_THRESHOLD;
157
+
158
+ if ((multiline_content || multiline_attributes) && !context.empty()) {
159
+ context.newline();
160
+ }
161
+
162
+ context.append(child_context);
163
+
164
+ if (is_self_closing) return;
165
+ if (multiline_content || multiline_attributes || break_line_after) {
166
+ context.newline();
167
+ }
168
+ }
169
+
170
+ /** @type {Visitors<AST.SvelteNode>} */
171
+ const css_visitors = {
172
+ Atrule(node, context) {
173
+ context.write(`@${node.name}`);
174
+ if (node.prelude) context.write(` ${node.prelude}`);
175
+
176
+ if (node.block) {
177
+ context.write(' ');
178
+ context.visit(node.block);
179
+ } else {
180
+ context.write(';');
181
+ }
182
+ },
183
+
184
+ Block(node, context) {
185
+ context.write('{');
186
+
187
+ if (node.children.length > 0) {
188
+ context.indent();
189
+ context.newline();
190
+
191
+ let started = false;
192
+
193
+ for (const child of node.children) {
194
+ if (started) {
195
+ context.newline();
196
+ }
197
+
198
+ context.visit(child);
199
+
200
+ started = true;
201
+ }
202
+
203
+ context.dedent();
204
+ context.newline();
205
+ }
206
+
207
+ context.write('}');
208
+ },
209
+
210
+ ClassSelector(node, context) {
211
+ context.write(`.${node.name}`);
212
+ },
213
+
214
+ ComplexSelector(node, context) {
215
+ for (const selector of node.children) {
216
+ context.visit(selector);
217
+ }
218
+ },
219
+
220
+ Declaration(node, context) {
221
+ context.write(`${node.property}: ${node.value};`);
222
+ },
223
+
224
+ Nth(node, context) {
225
+ context.write(node.value);
226
+ },
227
+
228
+ PseudoClassSelector(node, context) {
229
+ context.write(`:${node.name}`);
230
+
231
+ if (node.args) {
232
+ context.write('(');
233
+
234
+ let started = false;
235
+
236
+ for (const arg of node.args.children) {
237
+ if (started) {
238
+ context.write(', ');
239
+ }
240
+
241
+ context.visit(arg);
242
+
243
+ started = true;
244
+ }
245
+
246
+ context.write(')');
247
+ }
248
+ },
249
+
250
+ PseudoElementSelector(node, context) {
251
+ context.write(`::${node.name}`);
252
+ },
253
+
254
+ RelativeSelector(node, context) {
255
+ if (node.combinator) {
256
+ if (node.combinator.name === ' ') {
257
+ context.write(' ');
258
+ } else {
259
+ context.write(` ${node.combinator.name} `);
260
+ }
261
+ }
262
+
263
+ for (const selector of node.selectors) {
264
+ context.visit(selector);
265
+ }
266
+ },
267
+
268
+ Rule(node, context) {
269
+ let started = false;
270
+
271
+ for (const selector of node.prelude.children) {
272
+ if (started) {
273
+ context.write(',');
274
+ context.newline();
275
+ }
276
+
277
+ context.visit(selector);
278
+ started = true;
279
+ }
280
+
281
+ context.write(' ');
282
+ context.visit(node.block);
283
+ },
284
+
285
+ SelectorList(node, context) {
286
+ let started = false;
287
+ for (const selector of node.children) {
288
+ if (started) {
289
+ context.write(', ');
290
+ }
291
+
292
+ context.visit(selector);
293
+ started = true;
294
+ }
295
+ },
296
+
297
+ TypeSelector(node, context) {
298
+ context.write(node.name);
299
+ }
300
+ };
301
+
302
+ /** @type {Visitors<AST.SvelteNode>} */
303
+ const svelte_visitors = {
304
+ Root(node, context) {
305
+ if (node.options) {
306
+ context.write('<svelte:options');
307
+
308
+ for (const attribute of node.options.attributes) {
309
+ context.write(' ');
310
+ context.visit(attribute);
311
+ }
312
+
313
+ context.write(' />');
314
+ }
315
+
316
+ let started = false;
317
+
318
+ for (const item of [node.module, node.instance, node.fragment, node.css]) {
319
+ if (!item) continue;
320
+
321
+ if (started) {
322
+ context.margin();
323
+ context.newline();
324
+ }
325
+
326
+ context.visit(item);
327
+ started = true;
328
+ }
329
+ },
330
+
331
+ Script(node, context) {
332
+ context.write('<script');
333
+ attributes(node.attributes, context);
334
+ context.write('>');
335
+ block(context, node.content);
336
+ context.write('</script>');
337
+ },
338
+
339
+ Fragment(node, context) {
340
+ /** @type {AST.SvelteNode[][]} */
341
+ const items = [];
342
+
343
+ /** @type {AST.SvelteNode[]} */
344
+ let sequence = [];
345
+
346
+ const flush = () => {
347
+ items.push(sequence);
348
+ sequence = [];
349
+ };
350
+
351
+ for (let i = 0; i < node.nodes.length; i += 1) {
352
+ let child_node = node.nodes[i];
353
+
354
+ const prev = node.nodes[i - 1];
355
+ const next = node.nodes[i + 1];
356
+
357
+ if (child_node.type === 'Text') {
358
+ child_node = { ...child_node }; // always clone, so we can safely mutate
359
+
360
+ child_node.data = child_node.data.replace(/[^\S]+/g, ' ');
361
+
362
+ // trim fragment
363
+ if (i === 0) {
364
+ child_node.data = child_node.data.trimStart();
365
+ }
366
+
367
+ if (i === node.nodes.length - 1) {
368
+ child_node.data = child_node.data.trimEnd();
369
+ }
370
+
371
+ if (child_node.data === '') {
372
+ continue;
373
+ }
374
+
375
+ if (child_node.data.startsWith(' ') && prev && prev.type !== 'ExpressionTag') {
376
+ flush();
377
+ child_node.data = child_node.data.trimStart();
378
+ }
379
+
380
+ if (child_node.data !== '') {
381
+ sequence.push({ ...child_node, data: child_node.data });
382
+
383
+ if (child_node.data.endsWith(' ') && next && next.type !== 'ExpressionTag') {
384
+ flush();
385
+ child_node.data = child_node.data.trimStart();
386
+ }
387
+ }
388
+ } else {
389
+ sequence.push(child_node);
390
+ }
391
+ }
392
+
393
+ flush();
394
+
395
+ let multiline = false;
396
+ let width = 0;
397
+
398
+ const child_contexts = items.map((sequence) => {
399
+ const child_context = context.new();
400
+
401
+ for (const node of sequence) {
402
+ child_context.visit(node);
403
+ multiline ||= child_context.multiline;
404
+ }
405
+
406
+ width += child_context.measure();
407
+
408
+ return child_context;
409
+ });
410
+
411
+ multiline ||= width > LINE_BREAK_THRESHOLD;
412
+
413
+ for (let i = 0; i < child_contexts.length; i += 1) {
414
+ const prev = child_contexts[i];
415
+ const next = child_contexts[i + 1];
416
+
417
+ context.append(prev);
418
+
419
+ if (next) {
420
+ if (prev.multiline || next.multiline) {
421
+ context.margin();
422
+ context.newline();
423
+ } else if (multiline) {
424
+ context.newline();
425
+ }
426
+ }
427
+ }
428
+ },
429
+
430
+ AnimateDirective(node, context) {
431
+ context.write(`animate:${node.name}`);
432
+ if (
433
+ node.expression !== null &&
434
+ !(node.expression.type === 'Identifier' && node.expression.name === node.name)
435
+ ) {
436
+ context.write('={');
437
+ context.visit(node.expression);
438
+ context.write('}');
439
+ }
440
+ },
441
+
442
+ AttachTag(node, context) {
443
+ context.write('{@attach ');
444
+ context.visit(node.expression);
445
+ context.write('}');
446
+ },
447
+
448
+ Attribute(node, context) {
449
+ context.write(node.name);
450
+
451
+ if (node.value === true) return;
452
+
453
+ context.write('=');
454
+
455
+ if (Array.isArray(node.value)) {
456
+ if (node.value.length > 1 || node.value[0].type === 'Text') {
457
+ context.write('"');
458
+ }
459
+
460
+ for (const chunk of node.value) {
461
+ context.visit(chunk);
462
+ }
463
+
464
+ if (node.value.length > 1 || node.value[0].type === 'Text') {
465
+ context.write('"');
466
+ }
467
+ } else {
468
+ context.visit(node.value);
469
+ }
470
+ },
471
+
472
+ AwaitBlock(node, context) {
473
+ context.write(`{#await `);
474
+ context.visit(node.expression);
475
+
476
+ if (node.pending) {
477
+ context.write('}');
478
+ block(context, node.pending);
479
+ context.write('{:');
480
+ } else {
481
+ context.write(' ');
482
+ }
483
+
484
+ if (node.then) {
485
+ context.write(node.value ? 'then ' : 'then');
486
+ if (node.value) context.visit(node.value);
487
+ context.write('}');
488
+
489
+ block(context, node.then);
490
+
491
+ if (node.catch) {
492
+ context.write('{:');
493
+ }
494
+ }
495
+
496
+ if (node.catch) {
497
+ context.write(node.value ? 'catch ' : 'catch');
498
+ if (node.error) context.visit(node.error);
499
+ context.write('}');
500
+
501
+ block(context, node.catch);
502
+ }
503
+
504
+ context.write('{/await}');
505
+ },
506
+
507
+ BindDirective(node, context) {
508
+ context.write(`bind:${node.name}`);
509
+
510
+ if (node.expression.type === 'Identifier' && node.expression.name === node.name) {
511
+ // shorthand
512
+ return;
513
+ }
514
+
515
+ context.write('={');
516
+
517
+ if (node.expression.type === 'SequenceExpression') {
518
+ context.visit(node.expression.expressions[0]);
519
+ context.write(', ');
520
+ context.visit(node.expression.expressions[1]);
521
+ } else {
522
+ context.visit(node.expression);
523
+ }
524
+
525
+ context.write('}');
526
+ },
527
+
528
+ ClassDirective(node, context) {
529
+ context.write(`class:${node.name}`);
530
+ if (
531
+ node.expression !== null &&
532
+ !(node.expression.type === 'Identifier' && node.expression.name === node.name)
533
+ ) {
534
+ context.write('={');
535
+ context.visit(node.expression);
536
+ context.write('}');
537
+ }
538
+ },
539
+
540
+ Comment(node, context) {
541
+ context.write('<!--' + node.data + '-->');
542
+ },
543
+
544
+ Component(node, context) {
545
+ base_element(node, context);
546
+ },
547
+
548
+ ConstTag(node, context) {
549
+ context.write('{@');
550
+ context.visit(node.declaration);
551
+ context.write('}');
552
+ },
553
+
554
+ DebugTag(node, context) {
555
+ context.write('{@debug ');
556
+ let started = false;
557
+ for (const identifier of node.identifiers) {
558
+ if (started) {
559
+ context.write(', ');
560
+ }
561
+ context.visit(identifier);
562
+ started = true;
563
+ }
564
+ context.write('}');
565
+ },
566
+
567
+ EachBlock(node, context) {
568
+ context.write('{#each ');
569
+ context.visit(node.expression);
570
+
571
+ if (node.context) {
572
+ context.write(' as ');
573
+ context.visit(node.context);
574
+ }
575
+
576
+ if (node.index) {
577
+ context.write(`, ${node.index}`);
578
+ }
579
+
580
+ if (node.key) {
581
+ context.write(' (');
582
+ context.visit(node.key);
583
+ context.write(')');
584
+ }
585
+
586
+ context.write('}');
587
+
588
+ block(context, node.body);
589
+
590
+ if (node.fallback) {
591
+ context.write('{:else}');
592
+ block(context, node.fallback);
593
+ }
594
+
595
+ context.write('{/each}');
596
+ },
597
+
598
+ ExpressionTag(node, context) {
599
+ context.write('{');
600
+ context.visit(node.expression);
601
+ context.write('}');
602
+ },
603
+
604
+ HtmlTag(node, context) {
605
+ context.write('{@html ');
606
+ context.visit(node.expression);
607
+ context.write('}');
608
+ },
609
+
610
+ IfBlock(node, context) {
611
+ if (node.elseif) {
612
+ context.write('{:else if ');
613
+ context.visit(node.test);
614
+ context.write('}');
615
+
616
+ block(context, node.consequent);
617
+ } else {
618
+ context.write('{#if ');
619
+ context.visit(node.test);
620
+ context.write('}');
621
+
622
+ block(context, node.consequent);
623
+ }
624
+
625
+ if (node.alternate !== null) {
626
+ if (
627
+ !(
628
+ node.alternate.nodes.length === 1 &&
629
+ node.alternate.nodes[0].type === 'IfBlock' &&
630
+ node.alternate.nodes[0].elseif
631
+ )
632
+ ) {
633
+ context.write('{:else}');
634
+ block(context, node.alternate);
635
+ } else {
636
+ context.visit(node.alternate);
637
+ }
638
+ }
639
+
640
+ if (!node.elseif) {
641
+ context.write('{/if}');
642
+ }
643
+ },
644
+
645
+ KeyBlock(node, context) {
646
+ context.write('{#key ');
647
+ context.visit(node.expression);
648
+ context.write('}');
649
+ block(context, node.fragment);
650
+ context.write('{/key}');
651
+ },
652
+
653
+ LetDirective(node, context) {
654
+ context.write(`let:${node.name}`);
655
+ if (
656
+ node.expression !== null &&
657
+ !(node.expression.type === 'Identifier' && node.expression.name === node.name)
658
+ ) {
659
+ context.write('={');
660
+ context.visit(node.expression);
661
+ context.write('}');
662
+ }
663
+ },
664
+
665
+ OnDirective(node, context) {
666
+ context.write(`on:${node.name}`);
667
+ for (const modifier of node.modifiers) {
668
+ context.write(`|${modifier}`);
669
+ }
670
+ if (
671
+ node.expression !== null &&
672
+ !(node.expression.type === 'Identifier' && node.expression.name === node.name)
673
+ ) {
674
+ context.write('={');
675
+ context.visit(node.expression);
676
+ context.write('}');
677
+ }
678
+ },
679
+
680
+ RegularElement(node, context) {
681
+ base_element(node, context);
682
+ },
683
+
684
+ RenderTag(node, context) {
685
+ context.write('{@render ');
686
+ context.visit(node.expression);
687
+ context.write('}');
688
+ },
689
+
690
+ SlotElement(node, context) {
691
+ base_element(node, context);
692
+ },
693
+
694
+ SnippetBlock(node, context) {
695
+ context.write('{#snippet ');
696
+ context.visit(node.expression);
697
+
698
+ if (node.typeParams) {
699
+ context.write(`<${node.typeParams}>`);
700
+ }
701
+
702
+ context.write('(');
703
+
704
+ for (let i = 0; i < node.parameters.length; i += 1) {
705
+ if (i > 0) context.write(', ');
706
+ context.visit(node.parameters[i]);
707
+ }
708
+
709
+ context.write(')}');
710
+ block(context, node.body);
711
+ context.write('{/snippet}');
712
+ },
713
+
714
+ SpreadAttribute(node, context) {
715
+ context.write('{...');
716
+ context.visit(node.expression);
717
+ context.write('}');
718
+ },
719
+
720
+ StyleDirective(node, context) {
721
+ context.write(`style:${node.name}`);
722
+ for (const modifier of node.modifiers) {
723
+ context.write(`|${modifier}`);
724
+ }
725
+
726
+ if (node.value === true) {
727
+ return;
728
+ }
729
+
730
+ context.write('=');
731
+
732
+ if (Array.isArray(node.value)) {
733
+ context.write('"');
734
+
735
+ for (const tag of node.value) {
736
+ context.visit(tag);
737
+ }
738
+
739
+ context.write('"');
740
+ } else {
741
+ context.visit(node.value);
742
+ }
743
+ },
744
+
745
+ StyleSheet(node, context) {
746
+ context.write('<style');
747
+ attributes(node.attributes, context);
748
+ context.write('>');
749
+
750
+ if (node.children.length > 0) {
751
+ context.indent();
752
+ context.newline();
753
+
754
+ let started = false;
755
+
756
+ for (const child of node.children) {
757
+ if (started) {
758
+ context.margin();
759
+ context.newline();
760
+ }
761
+
762
+ context.visit(child);
763
+ started = true;
764
+ }
765
+
766
+ context.dedent();
767
+ context.newline();
768
+ }
769
+
770
+ context.write('</style>');
771
+ },
772
+
773
+ SvelteBoundary(node, context) {
774
+ base_element(node, context);
775
+ },
776
+
777
+ SvelteComponent(node, context) {
778
+ context.write('<svelte:component');
779
+
780
+ context.write(' this={');
781
+ context.visit(node.expression);
782
+ context.write('}');
783
+ attributes(node.attributes, context);
784
+ if (node.fragment && node.fragment.nodes.length > 0) {
785
+ context.write('>');
786
+ block(context, node.fragment, true);
787
+ context.write(`</svelte:component>`);
788
+ } else {
789
+ context.write(' />');
790
+ }
791
+ },
792
+
793
+ SvelteDocument(node, context) {
794
+ base_element(node, context);
795
+ },
796
+
797
+ SvelteElement(node, context) {
798
+ context.write('<svelte:element ');
799
+
800
+ context.write('this={');
801
+ context.visit(node.tag);
802
+ context.write('}');
803
+ attributes(node.attributes, context);
804
+
805
+ if (node.fragment && node.fragment.nodes.length > 0) {
806
+ context.write('>');
807
+ block(context, node.fragment);
808
+ context.write(`</svelte:element>`);
809
+ } else {
810
+ context.write(' />');
811
+ }
812
+ },
813
+
814
+ SvelteFragment(node, context) {
815
+ base_element(node, context);
816
+ },
817
+
818
+ SvelteHead(node, context) {
819
+ base_element(node, context);
820
+ },
821
+
822
+ SvelteSelf(node, context) {
823
+ base_element(node, context);
824
+ },
825
+
826
+ SvelteWindow(node, context) {
827
+ base_element(node, context);
828
+ },
829
+
830
+ Text(node, context) {
831
+ context.write(node.data);
832
+ },
833
+
834
+ TitleElement(node, context) {
835
+ base_element(node, context);
836
+ },
837
+
838
+ TransitionDirective(node, context) {
839
+ const directive = node.intro && node.outro ? 'transition' : node.intro ? 'in' : 'out';
840
+ context.write(`${directive}:${node.name}`);
841
+ for (const modifier of node.modifiers) {
842
+ context.write(`|${modifier}`);
843
+ }
844
+ if (
845
+ node.expression !== null &&
846
+ !(node.expression.type === 'Identifier' && node.expression.name === node.name)
847
+ ) {
848
+ context.write('={');
849
+ context.visit(node.expression);
850
+ context.write('}');
851
+ }
852
+ },
853
+
854
+ UseDirective(node, context) {
855
+ context.write(`use:${node.name}`);
856
+ if (
857
+ node.expression !== null &&
858
+ !(node.expression.type === 'Identifier' && node.expression.name === node.name)
859
+ ) {
860
+ context.write('={');
861
+ context.visit(node.expression);
862
+ context.write('}');
863
+ }
864
+ }
865
+ };