@ripple-ts/prettier-plugin 0.2.153

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/src/index.js ADDED
@@ -0,0 +1,4778 @@
1
+ // @ts-nocheck
2
+ import { parse } from 'ripple/compiler';
3
+ import { doc } from 'prettier';
4
+
5
+ const { builders, utils } = doc;
6
+ const {
7
+ concat,
8
+ join,
9
+ line,
10
+ softline,
11
+ hardline,
12
+ group,
13
+ indent,
14
+ dedent,
15
+ ifBreak,
16
+ fill,
17
+ conditionalGroup,
18
+ breakParent,
19
+ indentIfBreak,
20
+ lineSuffix,
21
+ } = builders;
22
+ const { willBreak } = utils;
23
+
24
+ // Embed function - not needed for now
25
+ export function embed(path, options) {
26
+ return null;
27
+ }
28
+
29
+ export const languages = [
30
+ {
31
+ name: 'ripple',
32
+ parsers: ['ripple'],
33
+ extensions: ['.ripple'],
34
+ vscodeLanguageIds: ['ripple'],
35
+ },
36
+ ];
37
+
38
+ export const parsers = {
39
+ ripple: {
40
+ astFormat: 'ripple-ast',
41
+ parse(text, parsers, options) {
42
+ const ast = parse(text);
43
+ return ast;
44
+ },
45
+
46
+ locStart(node) {
47
+ return node.loc.start.index;
48
+ },
49
+
50
+ locEnd(node) {
51
+ return node.loc.end.index;
52
+ },
53
+ },
54
+ };
55
+
56
+ export const printers = {
57
+ 'ripple-ast': {
58
+ print(path, options, print, args) {
59
+ const node = path.getValue();
60
+ const parts = printRippleNode(node, path, options, print, args);
61
+ // If printRippleNode returns doc parts, return them directly
62
+ // If it returns a string, wrap it for consistency
63
+ // If it returns an array, concatenate it
64
+ if (Array.isArray(parts)) {
65
+ return concat(parts);
66
+ }
67
+ return typeof parts === 'string' ? parts : parts;
68
+ },
69
+ getVisitorKeys(node) {
70
+ const keys = Object.keys(node).filter((key) => {
71
+ return key === 'start' || key === 'end' || key === 'loc' || key === 'metadata' || 'css'
72
+ ? false
73
+ : typeof node[key] === 'object' && node[key] !== null;
74
+ });
75
+
76
+ return keys;
77
+ },
78
+ },
79
+ };
80
+
81
+ // Helper function to format string literals according to Prettier options
82
+ function formatStringLiteral(value, options) {
83
+ if (typeof value !== 'string') {
84
+ return JSON.stringify(value);
85
+ }
86
+
87
+ const quote = options.singleQuote ? "'" : '"';
88
+ const escapedValue = value
89
+ .replace(/\\/g, '\\\\')
90
+ .replace(new RegExp(quote, 'g'), '\\' + quote)
91
+ .replace(/\n/g, '\\n')
92
+ .replace(/\r/g, '\\r')
93
+ .replace(/\t/g, '\\t');
94
+
95
+ return quote + escapedValue + quote;
96
+ }
97
+
98
+ // Helper function to create indentation according to Prettier options
99
+ function createIndent(options, level = 1) {
100
+ if (options.useTabs) {
101
+ return '\t'.repeat(level);
102
+ } else {
103
+ return ' '.repeat((options.tabWidth || 2) * level);
104
+ }
105
+ }
106
+
107
+ // Helper function to add semicolons based on options.semi setting
108
+ function semi(options) {
109
+ return options.semi !== false ? ';' : '';
110
+ }
111
+
112
+ function wasOriginallySingleLine(node) {
113
+ if (!node || !node.loc || !node.loc.start || !node.loc.end) {
114
+ return false;
115
+ }
116
+
117
+ return node.loc.start.line === node.loc.end.line;
118
+ }
119
+
120
+ function isSingleLineObjectExpression(node) {
121
+ return wasOriginallySingleLine(node);
122
+ }
123
+
124
+ // Prettier-style helper functions
125
+ function hasComment(node) {
126
+ return !!(node.leadingComments || node.trailingComments || node.innerComments);
127
+ }
128
+
129
+ function getFunctionParameters(node) {
130
+ const parameters = [];
131
+ if (node.this) {
132
+ parameters.push(node.this);
133
+ }
134
+ if (node.params) {
135
+ parameters.push(...node.params);
136
+ }
137
+ if (node.rest) {
138
+ parameters.push(node.rest);
139
+ }
140
+ return parameters;
141
+ }
142
+
143
+ function iterateFunctionParametersPath(path, iteratee) {
144
+ const { node } = path;
145
+ let index = 0;
146
+ const callback = (paramPath) => iteratee(paramPath, index++);
147
+
148
+ if (node.this) {
149
+ path.call(callback, 'this');
150
+ }
151
+ if (node.params) {
152
+ path.each(callback, 'params');
153
+ }
154
+ if (node.rest) {
155
+ path.call(callback, 'rest');
156
+ }
157
+ }
158
+
159
+ function createSkip(characters) {
160
+ return (text, startIndex, options) => {
161
+ const backwards = Boolean(options && options.backwards);
162
+
163
+ if (startIndex === false) {
164
+ return false;
165
+ }
166
+
167
+ const length = text.length;
168
+ let cursor = startIndex;
169
+ while (cursor >= 0 && cursor < length) {
170
+ const character = text.charAt(cursor);
171
+ if (characters instanceof RegExp) {
172
+ if (!characters.test(character)) {
173
+ return cursor;
174
+ }
175
+ } else if (!characters.includes(character)) {
176
+ return cursor;
177
+ }
178
+ cursor = backwards ? cursor - 1 : cursor + 1;
179
+ }
180
+
181
+ if (cursor === -1 || cursor === length) {
182
+ return cursor;
183
+ }
184
+
185
+ return false;
186
+ };
187
+ }
188
+
189
+ const skipSpaces = createSkip(' \t');
190
+ const skipToLineEnd = createSkip(',; \t');
191
+ const skipEverythingButNewLine = createSkip(/[^\n\r\u2028\u2029]/u);
192
+
193
+ function isCharNewLine(character) {
194
+ return (
195
+ character === '\n' || character === '\r' || character === '\u2028' || character === '\u2029'
196
+ );
197
+ }
198
+
199
+ function isCharSpace(character) {
200
+ return character === ' ' || character === '\t';
201
+ }
202
+
203
+ function skipInlineComment(text, startIndex) {
204
+ if (startIndex === false) {
205
+ return false;
206
+ }
207
+
208
+ if (text.charAt(startIndex) === '/' && text.charAt(startIndex + 1) === '*') {
209
+ for (let i = startIndex + 2; i < text.length; i++) {
210
+ if (text.charAt(i) === '*' && text.charAt(i + 1) === '/') {
211
+ return i + 2;
212
+ }
213
+ }
214
+ }
215
+
216
+ return startIndex;
217
+ }
218
+
219
+ function skipNewline(text, startIndex, options) {
220
+ const backwards = Boolean(options && options.backwards);
221
+ if (startIndex === false) {
222
+ return false;
223
+ }
224
+
225
+ const character = text.charAt(startIndex);
226
+ if (backwards) {
227
+ if (text.charAt(startIndex - 1) === '\r' && character === '\n') {
228
+ return startIndex - 2;
229
+ }
230
+ if (isCharNewLine(character)) {
231
+ return startIndex - 1;
232
+ }
233
+ } else {
234
+ if (character === '\r' && text.charAt(startIndex + 1) === '\n') {
235
+ return startIndex + 2;
236
+ }
237
+ if (isCharNewLine(character)) {
238
+ return startIndex + 1;
239
+ }
240
+ }
241
+
242
+ return startIndex;
243
+ }
244
+
245
+ function skipTrailingComment(text, startIndex) {
246
+ if (startIndex === false) {
247
+ return false;
248
+ }
249
+
250
+ if (text.charAt(startIndex) === '/' && text.charAt(startIndex + 1) === '/') {
251
+ return skipEverythingButNewLine(text, startIndex);
252
+ }
253
+
254
+ return startIndex;
255
+ }
256
+
257
+ function getNodeEndIndex(node) {
258
+ if (node?.loc?.end && typeof node.loc.end.index === 'number') {
259
+ return node.loc.end.index;
260
+ }
261
+ if (typeof node?.end === 'number') {
262
+ return node.end;
263
+ }
264
+ if (Array.isArray(node?.range) && typeof node.range[1] === 'number') {
265
+ return node.range[1];
266
+ }
267
+ return null;
268
+ }
269
+
270
+ function isRegExpLiteral(node) {
271
+ return (
272
+ node &&
273
+ ((node.type === 'Literal' && !!node.regex) ||
274
+ node.type === 'RegExpLiteral' ||
275
+ (node.type === 'StringLiteral' &&
276
+ node.extra?.raw?.startsWith('/') &&
277
+ node.extra?.raw?.endsWith('/')))
278
+ );
279
+ }
280
+
281
+ function isCommentFollowedBySameLineParen(comment, options) {
282
+ if (!comment || !options || typeof options.originalText !== 'string') {
283
+ return false;
284
+ }
285
+
286
+ const text = options.originalText;
287
+ const endIndex = getNodeEndIndex(comment);
288
+ if (typeof endIndex !== 'number') {
289
+ return false;
290
+ }
291
+
292
+ let cursor = endIndex;
293
+ while (cursor < text.length) {
294
+ const character = text.charAt(cursor);
295
+ if (character === '(') {
296
+ return true;
297
+ }
298
+ if (isCharNewLine(character) || !isCharSpace(character)) {
299
+ return false;
300
+ }
301
+ cursor++;
302
+ }
303
+
304
+ return false;
305
+ }
306
+
307
+ function hasNewline(text, startIndex, options) {
308
+ const idx = skipSpaces(text, options && options.backwards ? startIndex - 1 : startIndex, options);
309
+ const idx2 = skipNewline(text, idx, options);
310
+ return idx !== idx2;
311
+ }
312
+
313
+ function isNextLineEmpty(node, options) {
314
+ if (!node || !options || !options.originalText) {
315
+ return false;
316
+ }
317
+
318
+ const text = options.originalText;
319
+ const resolveEndIndex = () => {
320
+ if (typeof options.locEnd === 'function') {
321
+ const value = options.locEnd(node);
322
+ if (typeof value === 'number') {
323
+ return value;
324
+ }
325
+ }
326
+ if (node.loc && node.loc.end) {
327
+ if (typeof node.loc.end.index === 'number') {
328
+ return node.loc.end.index;
329
+ }
330
+ if (typeof node.loc.end.offset === 'number') {
331
+ return node.loc.end.offset;
332
+ }
333
+ }
334
+ if (typeof node.end === 'number') {
335
+ return node.end;
336
+ }
337
+ return null;
338
+ };
339
+
340
+ let index = resolveEndIndex();
341
+ if (typeof index !== 'number') {
342
+ return false;
343
+ }
344
+
345
+ let previousIndex = null;
346
+ while (index !== previousIndex) {
347
+ previousIndex = index;
348
+ index = skipToLineEnd(text, index);
349
+ index = skipInlineComment(text, index);
350
+ index = skipSpaces(text, index);
351
+ }
352
+
353
+ index = skipTrailingComment(text, index);
354
+ index = skipNewline(text, index);
355
+ return index !== false && hasNewline(text, index);
356
+ }
357
+
358
+ function hasRestParameter(node) {
359
+ return !!node.rest;
360
+ }
361
+
362
+ function shouldPrintComma(options, level = 'all') {
363
+ switch (options.trailingComma) {
364
+ case 'none':
365
+ return false;
366
+ case 'es5':
367
+ return level === 'es5' || level === 'all';
368
+ case 'all':
369
+ return level === 'all';
370
+ default:
371
+ return false;
372
+ }
373
+ }
374
+
375
+ function canAttachLeadingCommentToPreviousElement(comment, previousNode, nextNode) {
376
+ if (!comment || !previousNode || !nextNode) {
377
+ return false;
378
+ }
379
+
380
+ const isBlockComment = comment.type === 'Block' || comment.type === 'CommentBlock';
381
+ if (!isBlockComment) {
382
+ return false;
383
+ }
384
+
385
+ if (!comment.loc || !previousNode.loc || !nextNode.loc) {
386
+ return false;
387
+ }
388
+
389
+ if (getBlankLinesBetweenNodes(previousNode, comment) > 0) {
390
+ return false;
391
+ }
392
+
393
+ if (getBlankLinesBetweenNodes(comment, nextNode) > 0) {
394
+ return false;
395
+ }
396
+
397
+ return true;
398
+ }
399
+
400
+ function buildInlineArrayCommentDoc(comments) {
401
+ if (!Array.isArray(comments) || comments.length === 0) {
402
+ return null;
403
+ }
404
+
405
+ const docs = [];
406
+ for (let index = 0; index < comments.length; index++) {
407
+ const comment = comments[index];
408
+ if (!comment) {
409
+ continue;
410
+ }
411
+
412
+ // Ensure spacing before the first comment and between subsequent ones.
413
+ docs.push(' ');
414
+ const isBlockComment = comment.type === 'Block' || comment.type === 'CommentBlock';
415
+ if (isBlockComment) {
416
+ docs.push('/*' + comment.value + '*/');
417
+ } else if (comment.type === 'Line') {
418
+ docs.push('//' + comment.value);
419
+ }
420
+ }
421
+
422
+ return docs.length > 0 ? concat(docs) : null;
423
+ }
424
+
425
+ function printRippleNode(node, path, options, print, args) {
426
+ if (!node || typeof node !== 'object') {
427
+ return String(node || '');
428
+ }
429
+
430
+ const parts = [];
431
+
432
+ const isInlineContext = args && args.isInlineContext;
433
+ const suppressLeadingComments = args && args.suppressLeadingComments;
434
+ const suppressExpressionLeadingComments = args && args.suppressExpressionLeadingComments;
435
+
436
+ // Check if this node is a direct child of Program (top-level)
437
+ const parentNode = path.getParentNode();
438
+ const isTopLevel = parentNode && parentNode.type === 'Program';
439
+
440
+ // For Text and Html nodes, don't add leading comments here - they should be handled
441
+ // as separate children within the element, not as part of the expression
442
+ const shouldSkipLeadingComments = node.type === 'Text' || node.type === 'Html';
443
+
444
+ // Handle leading comments
445
+ if (node.leadingComments && !shouldSkipLeadingComments && !suppressLeadingComments) {
446
+ for (let i = 0; i < node.leadingComments.length; i++) {
447
+ const comment = node.leadingComments[i];
448
+ const nextComment = node.leadingComments[i + 1];
449
+ const isLastComment = i === node.leadingComments.length - 1;
450
+
451
+ if (comment.type === 'Line') {
452
+ parts.push('//' + comment.value);
453
+ parts.push(hardline);
454
+
455
+ // Check if there should be blank lines between this comment and the next
456
+ if (nextComment) {
457
+ const blankLinesBetween = getBlankLinesBetweenNodes(comment, nextComment);
458
+ if (blankLinesBetween > 0) {
459
+ parts.push(hardline);
460
+ }
461
+ } else if (isLastComment) {
462
+ // Preserve a blank line between the last comment and the node if it existed
463
+ const blankLinesBetween = getBlankLinesBetweenNodes(comment, node);
464
+ if (blankLinesBetween > 0) {
465
+ parts.push(hardline);
466
+ }
467
+ }
468
+ } else if (comment.type === 'Block') {
469
+ parts.push('/*' + comment.value + '*/');
470
+
471
+ // Check if comment and node are on the same line (for inline JSDoc comments)
472
+ const isCommentInlineWithParen =
473
+ isLastComment && isCommentFollowedBySameLineParen(comment, options);
474
+ const isCommentOnSameLine =
475
+ isLastComment && comment.loc && node.loc && comment.loc.end.line === node.loc.start.line;
476
+ const shouldKeepOnSameLine = isCommentOnSameLine || isCommentInlineWithParen;
477
+
478
+ if (!isInlineContext && !shouldKeepOnSameLine) {
479
+ parts.push(hardline);
480
+
481
+ // Check if there should be blank lines between this comment and the next
482
+ if (nextComment) {
483
+ const blankLinesBetween = getBlankLinesBetweenNodes(comment, nextComment);
484
+ if (blankLinesBetween > 0) {
485
+ parts.push(hardline);
486
+ }
487
+ } else if (isLastComment) {
488
+ // Preserve a blank line between the last comment and the node if it existed
489
+ const blankLinesBetween = getBlankLinesBetweenNodes(comment, node);
490
+ if (blankLinesBetween > 0) {
491
+ parts.push(hardline);
492
+ }
493
+ }
494
+ } else {
495
+ parts.push(' ');
496
+ }
497
+ }
498
+ }
499
+ }
500
+
501
+ // Handle inner comments (for nodes with no children to attach to)
502
+ const innerCommentParts = [];
503
+ if (node.innerComments) {
504
+ for (const comment of node.innerComments) {
505
+ if (comment.type === 'Line') {
506
+ innerCommentParts.push('//' + comment.value);
507
+ } else if (comment.type === 'Block') {
508
+ innerCommentParts.push('/*' + comment.value + '*/');
509
+ }
510
+ }
511
+ }
512
+
513
+ let nodeContent;
514
+
515
+ switch (node.type) {
516
+ case 'Program': {
517
+ // Handle the body statements properly with whitespace preservation
518
+ const statements = [];
519
+ for (let i = 0; i < node.body.length; i++) {
520
+ const statement = path.call(print, 'body', i);
521
+ // If statement is an array, flatten it
522
+ if (Array.isArray(statement)) {
523
+ statements.push(concat(statement));
524
+ } else {
525
+ statements.push(statement);
526
+ }
527
+
528
+ // Add spacing between top-level statements based on original formatting
529
+ if (i < node.body.length - 1) {
530
+ const currentStmt = node.body[i];
531
+ const nextStmt = node.body[i + 1];
532
+
533
+ // Only add spacing when explicitly needed
534
+ if (shouldAddBlankLine(currentStmt, nextStmt)) {
535
+ statements.push(concat([line, line])); // blank line
536
+ } else {
537
+ statements.push(line); // single line break
538
+ }
539
+ }
540
+ }
541
+
542
+ // Prettier always adds a trailing newline to files
543
+ // Add it unless the code is completely empty
544
+ if (statements.length > 0) {
545
+ nodeContent = concat([...statements, hardline]);
546
+ } else {
547
+ nodeContent = concat(statements);
548
+ }
549
+ break;
550
+ }
551
+
552
+ case 'ImportDeclaration':
553
+ nodeContent = printImportDeclaration(node, path, options, print);
554
+ break;
555
+
556
+ case 'Component':
557
+ nodeContent = printComponent(node, path, options, print);
558
+ break;
559
+
560
+ case 'ExportNamedDeclaration':
561
+ nodeContent = printExportNamedDeclaration(node, path, options, print);
562
+ break;
563
+
564
+ case 'ExportDefaultDeclaration':
565
+ nodeContent = printExportDefaultDeclaration(node, path, options, print);
566
+ break;
567
+
568
+ case 'FunctionDeclaration':
569
+ nodeContent = printFunctionDeclaration(node, path, options, print);
570
+ break;
571
+
572
+ case 'IfStatement':
573
+ nodeContent = printIfStatement(node, path, options, print);
574
+ break;
575
+
576
+ case 'ForOfStatement':
577
+ nodeContent = printForOfStatement(node, path, options, print);
578
+ break;
579
+
580
+ case 'ForStatement':
581
+ nodeContent = printForStatement(node, path, options, print);
582
+ break;
583
+
584
+ case 'WhileStatement':
585
+ nodeContent = printWhileStatement(node, path, options, print);
586
+ break;
587
+
588
+ case 'DoWhileStatement':
589
+ nodeContent = printDoWhileStatement(node, path, options, print);
590
+ break;
591
+
592
+ case 'ClassDeclaration':
593
+ nodeContent = printClassDeclaration(node, path, options, print);
594
+ break;
595
+
596
+ case 'TryStatement':
597
+ nodeContent = printTryStatement(node, path, options, print);
598
+ break;
599
+
600
+ case 'ArrayExpression':
601
+ case 'TrackedArrayExpression': {
602
+ const prefix = node.type === 'TrackedArrayExpression' ? '#' : '';
603
+
604
+ if (!node.elements || node.elements.length === 0) {
605
+ nodeContent = prefix + '[]';
606
+ break;
607
+ }
608
+
609
+ // Check if any element is an object expression
610
+ let hasObjectElements = false;
611
+ for (let i = 0; i < node.elements.length; i++) {
612
+ const element = node.elements[i];
613
+ if (element && element.type === 'ObjectExpression') {
614
+ hasObjectElements = true;
615
+ break;
616
+ }
617
+ }
618
+ let shouldInlineObjects = false;
619
+
620
+ // Check if this array is inside an attribute
621
+ const isInAttribute = args && args.isInAttribute;
622
+ const suppressLeadingCommentIndices = new Set();
623
+ const inlineCommentsBetween = new Array(Math.max(node.elements.length - 1, 0)).fill(null);
624
+
625
+ for (let index = 0; index < node.elements.length - 1; index++) {
626
+ const currentElement = node.elements[index];
627
+ const nextElement = node.elements[index + 1];
628
+ if (
629
+ !nextElement ||
630
+ !nextElement.leadingComments ||
631
+ nextElement.leadingComments.length === 0
632
+ ) {
633
+ continue;
634
+ }
635
+
636
+ const canTransferAllLeadingComments = nextElement.leadingComments.every((comment) =>
637
+ canAttachLeadingCommentToPreviousElement(comment, currentElement, nextElement),
638
+ );
639
+
640
+ if (!canTransferAllLeadingComments) {
641
+ continue;
642
+ }
643
+
644
+ const inlineCommentDoc = buildInlineArrayCommentDoc(nextElement.leadingComments);
645
+ if (inlineCommentDoc) {
646
+ inlineCommentsBetween[index] = inlineCommentDoc;
647
+ suppressLeadingCommentIndices.add(index + 1);
648
+ }
649
+ }
650
+
651
+ // Check if all elements are objects with multiple properties
652
+ // In that case, each object should be on its own line
653
+ const objectElements = node.elements.filter((el) => el && el.type === 'ObjectExpression');
654
+ const allElementsAreObjects =
655
+ node.elements.length > 0 &&
656
+ node.elements.every((el) => el && el.type === 'ObjectExpression');
657
+ const allObjectsHaveMultipleProperties =
658
+ allElementsAreObjects &&
659
+ objectElements.length > 0 &&
660
+ objectElements.every((obj) => obj.properties && obj.properties.length > 1);
661
+
662
+ // For arrays of simple objects with only a few properties, try to keep compact
663
+ // But NOT if all objects have multiple properties
664
+ if (hasObjectElements && !allObjectsHaveMultipleProperties) {
665
+ shouldInlineObjects = true;
666
+ for (let i = 0; i < node.elements.length; i++) {
667
+ const element = node.elements[i];
668
+ if (element && element.type === 'ObjectExpression') {
669
+ if (!isSingleLineObjectExpression(element)) {
670
+ shouldInlineObjects = false;
671
+ break;
672
+ }
673
+ }
674
+ }
675
+ }
676
+
677
+ // Default printing - pass isInArray or isInAttribute context
678
+ const arrayWasSingleLine = wasOriginallySingleLine(node);
679
+ const shouldUseTrailingComma = options.trailingComma !== 'none';
680
+ const elements = path.map(
681
+ /**
682
+ * @param {any} elPath
683
+ * @param {number} index
684
+ */
685
+ (elPath, index) => {
686
+ const childNode = node.elements[index];
687
+ const childArgs = {};
688
+
689
+ if (suppressLeadingCommentIndices.has(index)) {
690
+ childArgs.suppressLeadingComments = true;
691
+ }
692
+
693
+ if (isInAttribute) {
694
+ childArgs.isInAttribute = true;
695
+ return print(elPath, childArgs);
696
+ }
697
+
698
+ if (
699
+ hasObjectElements &&
700
+ childNode &&
701
+ childNode.type === 'ObjectExpression' &&
702
+ shouldInlineObjects
703
+ ) {
704
+ childArgs.isInArray = true;
705
+ childArgs.allowInlineObject = true;
706
+ return print(elPath, childArgs);
707
+ }
708
+
709
+ if (hasObjectElements) {
710
+ childArgs.isInArray = true;
711
+ }
712
+
713
+ return Object.keys(childArgs).length > 0 ? print(elPath, childArgs) : print(elPath);
714
+ },
715
+ 'elements',
716
+ );
717
+
718
+ if (hasObjectElements && shouldInlineObjects && arrayWasSingleLine) {
719
+ const separator = concat([',', line]);
720
+ const trailing = shouldUseTrailingComma ? ifBreak(',', '') : '';
721
+ nodeContent = group(
722
+ concat([
723
+ prefix + '[',
724
+ indent(concat([softline, join(separator, elements), trailing])),
725
+ softline,
726
+ ']',
727
+ ]),
728
+ );
729
+ break;
730
+ }
731
+
732
+ // Arrays should inline all elements unless:
733
+ // 1. An element (not first) has blank line above it - then that element on new line with blank
734
+ // 2. Elements don't fit within printWidth
735
+ // 3. Array contains objects and every object has more than 1 property - each object on own line
736
+
737
+ // Check which elements have blank lines above them
738
+ const elementsWithBlankLineAbove = [];
739
+
740
+ // Check for blank line after opening bracket (before first element)
741
+ // This indicates the array should be collapsed, not preserved as multiline
742
+ let hasBlankLineAfterOpening = false;
743
+ if (node.elements.length > 0 && node.elements[0]) {
744
+ const firstElement = node.elements[0];
745
+ // Check if first element starts on a different line than the opening bracket
746
+ // and there's a blank line between them
747
+ if (firstElement.loc && node.loc) {
748
+ const bracketLine = node.loc.start.line;
749
+ const firstElementLine = firstElement.loc.start.line;
750
+ // If there's more than one line between bracket and first element, there's a blank line
751
+ if (firstElementLine - bracketLine > 1) {
752
+ hasBlankLineAfterOpening = true;
753
+ }
754
+ }
755
+ }
756
+
757
+ // Check for blank line before closing bracket (after last element)
758
+ let hasBlankLineBeforeClosing = false;
759
+ if (node.elements.length > 0 && node.elements[node.elements.length - 1]) {
760
+ const lastElement = node.elements[node.elements.length - 1];
761
+ if (lastElement.loc && node.loc) {
762
+ const lastElementLine = lastElement.loc.end.line;
763
+ const closingBracketLine = node.loc.end.line;
764
+ // If there's more than one line between last element and closing bracket, there's a blank line
765
+ if (closingBracketLine - lastElementLine > 1) {
766
+ hasBlankLineBeforeClosing = true;
767
+ }
768
+ }
769
+ }
770
+
771
+ for (let i = 1; i < node.elements.length; i++) {
772
+ const prevElement = node.elements[i - 1];
773
+ const currentElement = node.elements[i];
774
+ if (!prevElement || !currentElement) {
775
+ continue;
776
+ }
777
+
778
+ const leadingComments = currentElement.leadingComments || [];
779
+ if (leadingComments.length > 0) {
780
+ const firstComment = leadingComments[0];
781
+ const lastComment = leadingComments[leadingComments.length - 1];
782
+
783
+ const linesBeforeComment = getBlankLinesBetweenNodes(prevElement, firstComment);
784
+ const linesAfterComment = getBlankLinesBetweenNodes(lastComment, currentElement);
785
+
786
+ if (linesBeforeComment > 0 || linesAfterComment > 0) {
787
+ elementsWithBlankLineAbove.push(i);
788
+ }
789
+ continue;
790
+ }
791
+
792
+ if (getBlankLinesBetweenNodes(prevElement, currentElement) > 0) {
793
+ elementsWithBlankLineAbove.push(i);
794
+ }
795
+ }
796
+
797
+ const hasAnyBlankLines = elementsWithBlankLineAbove.length > 0;
798
+
799
+ // Check if any elements contain hard breaks (like multiline ternaries)
800
+ // Don't check willBreak() as that includes soft breaks from groups
801
+ // Only check for actual multiline content that forces breaking
802
+ const hasHardBreakingElements = node.elements.some((el) => {
803
+ if (!el) return false;
804
+ // Multiline ternaries are the main case that should force all elements on separate lines
805
+ return el.type === 'ConditionalExpression';
806
+ });
807
+
808
+ if (!hasAnyBlankLines && !allObjectsHaveMultipleProperties && !hasHardBreakingElements) {
809
+ // Check if array has inline comments between elements
810
+ const hasInlineComments = inlineCommentsBetween.some((comment) => comment !== null);
811
+
812
+ // For arrays originally formatted with one element per line (no blank lines between),
813
+ // preserve that formatting using join() with hardline - BUT only if no inline comments
814
+ // and no blank lines at boundaries
815
+ if (
816
+ !arrayWasSingleLine &&
817
+ !hasBlankLineAfterOpening &&
818
+ !hasBlankLineBeforeClosing &&
819
+ !hasInlineComments
820
+ ) {
821
+ const separator = concat([',', hardline]);
822
+ const trailingDoc = shouldUseTrailingComma ? ',' : '';
823
+ nodeContent = group(
824
+ concat([
825
+ prefix + '[',
826
+ indent(concat([hardline, join(separator, elements), trailingDoc])),
827
+ hardline,
828
+ ']',
829
+ ]),
830
+ );
831
+ break;
832
+ }
833
+
834
+ // For arrays that should collapse (single-line or blank after opening) or have comments,
835
+ // use fill() to pack elements
836
+ const fillParts = [];
837
+ let skipNextSeparator = false;
838
+ for (let index = 0; index < elements.length; index++) {
839
+ if (index > 0) {
840
+ if (skipNextSeparator) {
841
+ skipNextSeparator = false;
842
+ } else {
843
+ fillParts.push(line);
844
+ }
845
+ }
846
+
847
+ if (index < elements.length - 1) {
848
+ const inlineCommentDoc = inlineCommentsBetween[index];
849
+
850
+ if (inlineCommentDoc) {
851
+ // Build comment without leading space for separate-line version
852
+ const nextElement = node.elements[index + 1];
853
+ const commentParts = [];
854
+ if (nextElement && nextElement.leadingComments) {
855
+ for (const comment of nextElement.leadingComments) {
856
+ const isBlockComment =
857
+ comment.type === 'Block' || comment.type === 'CommentBlock';
858
+ if (isBlockComment) {
859
+ commentParts.push('/*' + comment.value + '*/');
860
+ } else if (comment.type === 'Line') {
861
+ commentParts.push('//' + comment.value);
862
+ }
863
+ }
864
+ }
865
+ const commentDocNoSpace = commentParts.length > 0 ? concat(commentParts) : '';
866
+
867
+ // Provide conditional rendering: inline if it fits, otherwise on separate line
868
+ fillParts.push(
869
+ conditionalGroup([
870
+ // Try inline first (with space before comment)
871
+ concat([elements[index], ',', inlineCommentDoc, hardline]),
872
+ // If doesn't fit, put comment on next line (without leading space)
873
+ concat([elements[index], ',', hardline, commentDocNoSpace, hardline]),
874
+ ]),
875
+ );
876
+ skipNextSeparator = true;
877
+ } else {
878
+ fillParts.push(group(concat([elements[index], ','])));
879
+ skipNextSeparator = false;
880
+ }
881
+ } else {
882
+ fillParts.push(elements[index]);
883
+ skipNextSeparator = false;
884
+ }
885
+ }
886
+
887
+ const trailingDoc = shouldUseTrailingComma ? ifBreak(',', '') : '';
888
+ nodeContent = group(
889
+ concat([
890
+ prefix + '[',
891
+ indent(concat([softline, fill(fillParts), trailingDoc])),
892
+ softline,
893
+ ']',
894
+ ]),
895
+ );
896
+ break;
897
+ }
898
+
899
+ // If array has breaking elements (multiline ternaries, functions, etc.)
900
+ // use join() to put each element on its own line, per Prettier spec
901
+ if (hasHardBreakingElements) {
902
+ const separator = concat([',', line]);
903
+ const parts = [];
904
+ for (let index = 0; index < elements.length; index++) {
905
+ parts.push(elements[index]);
906
+ }
907
+ const trailingDoc = shouldUseTrailingComma ? ifBreak(',', '') : '';
908
+ nodeContent = group(
909
+ concat([
910
+ prefix + '[',
911
+ indent(concat([softline, join(separator, parts), trailingDoc])),
912
+ softline,
913
+ ']',
914
+ ]),
915
+ );
916
+ break;
917
+ }
918
+
919
+ // If array has multi-property objects, force each object on its own line
920
+ // Objects that were originally inline can stay inline if they fit printWidth
921
+ // Objects that were originally multi-line should stay multi-line
922
+ if (allObjectsHaveMultipleProperties) {
923
+ const inlineElements = path.map((elPath, index) => {
924
+ const obj = node.elements[index];
925
+ const wasObjSingleLine =
926
+ obj && obj.type === 'ObjectExpression' && wasOriginallySingleLine(obj);
927
+ return print(elPath, {
928
+ isInArray: true,
929
+ allowInlineObject: wasObjSingleLine,
930
+ });
931
+ }, 'elements');
932
+ const separator = concat([',', hardline]);
933
+ const trailingDoc = shouldUseTrailingComma ? ifBreak(',', '') : '';
934
+ nodeContent = group(
935
+ concat([
936
+ prefix + '[',
937
+ indent(concat([hardline, join(separator, inlineElements), trailingDoc])),
938
+ hardline,
939
+ ']',
940
+ ]),
941
+ );
942
+ break;
943
+ }
944
+
945
+ // Has blank lines - format with blank lines preserved
946
+ // Group elements between blank lines together so they can inline
947
+ const contentParts = [];
948
+
949
+ // Split elements into groups separated by blank lines
950
+ const groups = [];
951
+ let currentGroup = [];
952
+
953
+ for (let i = 0; i < elements.length; i++) {
954
+ const hasBlankLineAbove = elementsWithBlankLineAbove.includes(i);
955
+
956
+ if (hasBlankLineAbove && currentGroup.length > 0) {
957
+ // Save current group and start new one
958
+ groups.push(currentGroup);
959
+ currentGroup = [i];
960
+ } else {
961
+ currentGroup.push(i);
962
+ }
963
+ }
964
+
965
+ // Don't forget the last group
966
+ if (currentGroup.length > 0) {
967
+ groups.push(currentGroup);
968
+ }
969
+
970
+ // Now output each group
971
+ for (let groupIdx = 0; groupIdx < groups.length; groupIdx++) {
972
+ const group_indices = groups[groupIdx];
973
+
974
+ // Add blank line before this group (except first group)
975
+ if (groupIdx > 0) {
976
+ contentParts.push(hardline);
977
+ contentParts.push(hardline);
978
+ }
979
+
980
+ // Build the group elements
981
+ // Use fill() to automatically pack as many elements as fit per line
982
+ // IMPORTANT: Each element+comma needs to be grouped for proper width calculation
983
+ const fillParts = [];
984
+ for (let i = 0; i < group_indices.length; i++) {
985
+ const elemIdx = group_indices[i];
986
+ const isLastInArray = elemIdx === elements.length - 1;
987
+
988
+ if (i > 0) {
989
+ fillParts.push(line);
990
+ }
991
+ // Wrap element+comma in group so fill() measures them together including breaks
992
+ // But don't add comma to the very last element (it gets trailing comma separately)
993
+ if (isLastInArray && shouldUseTrailingComma) {
994
+ fillParts.push(group(elements[elemIdx]));
995
+ } else {
996
+ fillParts.push(group(concat([elements[elemIdx], ','])));
997
+ }
998
+ }
999
+
1000
+ contentParts.push(fill(fillParts));
1001
+ }
1002
+
1003
+ // Add trailing comma only if the last element didn't already have one
1004
+ if (shouldUseTrailingComma) {
1005
+ contentParts.push(',');
1006
+ }
1007
+
1008
+ // Array with blank lines - format as multi-line
1009
+ // Use simple group that will break to fit within printWidth
1010
+ nodeContent = group(
1011
+ concat([prefix + '[', indent(concat([line, concat(contentParts)])), line, ']']),
1012
+ );
1013
+ break;
1014
+ }
1015
+
1016
+ case 'ObjectExpression':
1017
+ case 'TrackedObjectExpression':
1018
+ nodeContent = printObjectExpression(node, path, options, print, args);
1019
+ break;
1020
+
1021
+ case 'ClassBody':
1022
+ nodeContent = printClassBody(node, path, options, print);
1023
+ break;
1024
+
1025
+ case 'PropertyDefinition':
1026
+ nodeContent = printPropertyDefinition(node, path, options, print);
1027
+ break;
1028
+
1029
+ case 'MethodDefinition':
1030
+ nodeContent = printMethodDefinition(node, path, options, print);
1031
+ break;
1032
+
1033
+ case 'PrivateIdentifier':
1034
+ nodeContent = '#' + node.name;
1035
+ break;
1036
+
1037
+ case 'AssignmentExpression': {
1038
+ // Print left side with noBreakInside context to keep calls compact
1039
+ let leftPart = path.call((p) => print(p, { noBreakInside: true }), 'left');
1040
+ // Preserve parentheses around the left side when present
1041
+ if (node.left.metadata?.parenthesized) {
1042
+ leftPart = concat(['(', leftPart, ')']);
1043
+ }
1044
+ // For CallExpression on the right with JSDoc comments, use fluid layout strategy
1045
+ const rightSide = path.call(print, 'right');
1046
+
1047
+ // Use fluid layout for assignments: allows breaking after operator first
1048
+ const groupId = Symbol('assignment');
1049
+ nodeContent = group([
1050
+ group(leftPart),
1051
+ ' ',
1052
+ node.operator,
1053
+ group(indent(line), { id: groupId }),
1054
+ indentIfBreak(rightSide, { groupId }),
1055
+ ]);
1056
+ break;
1057
+ }
1058
+
1059
+ case 'MemberExpression':
1060
+ nodeContent = printMemberExpression(node, path, options, print);
1061
+ break;
1062
+
1063
+ case 'Super':
1064
+ nodeContent = 'super';
1065
+ break;
1066
+
1067
+ case 'ThisExpression':
1068
+ nodeContent = 'this';
1069
+ break;
1070
+
1071
+ case 'ChainExpression':
1072
+ nodeContent = path.call(print, 'expression');
1073
+ break;
1074
+
1075
+ case 'CallExpression': {
1076
+ const parts = [];
1077
+ const calleePart = path.call(print, 'callee');
1078
+ parts.push(calleePart);
1079
+
1080
+ if (node.optional) {
1081
+ parts.push('?.');
1082
+ }
1083
+
1084
+ // Add TypeScript generics if present
1085
+ if (node.typeArguments) {
1086
+ parts.push(path.call(print, 'typeArguments'));
1087
+ } else if (node.typeParameters) {
1088
+ parts.push(path.call(print, 'typeParameters'));
1089
+ }
1090
+
1091
+ const argsDoc = printCallArguments(path, options, print);
1092
+ parts.push(argsDoc);
1093
+
1094
+ let callContent = concat(parts);
1095
+
1096
+ // Preserve parentheses for type-annotated call expressions
1097
+ // When parenthesized with leading comments, use grouping to allow breaking
1098
+ if (node.metadata?.parenthesized) {
1099
+ const hasLeadingComments = node.leadingComments && node.leadingComments.length > 0;
1100
+ if (hasLeadingComments) {
1101
+ // Group with softline to allow breaking after opening paren
1102
+ callContent = group(
1103
+ concat(['(', indent(concat([softline, callContent])), softline, ')']),
1104
+ );
1105
+ } else {
1106
+ callContent = concat(['(', callContent, ')']);
1107
+ }
1108
+ }
1109
+ nodeContent = callContent;
1110
+ break;
1111
+ }
1112
+
1113
+ case 'AwaitExpression': {
1114
+ const parts = ['await ', path.call(print, 'argument')];
1115
+ nodeContent = concat(parts);
1116
+ break;
1117
+ }
1118
+
1119
+ case 'TrackedExpression': {
1120
+ const parts = ['@(', path.call(print, 'argument'), ')'];
1121
+ nodeContent = concat(parts);
1122
+ break;
1123
+ }
1124
+
1125
+ case 'TrackedMapExpression': {
1126
+ // Format: #Map(arg1, arg2, ...)
1127
+ // When used with 'new', the arguments are empty and belong to NewExpression
1128
+ if (!node.arguments || node.arguments.length === 0) {
1129
+ nodeContent = '#Map';
1130
+ } else {
1131
+ const args = path.map(print, 'arguments');
1132
+ nodeContent = concat(['#Map(', join(concat([',', line]), args), ')']);
1133
+ }
1134
+ break;
1135
+ }
1136
+
1137
+ case 'TrackedSetExpression': {
1138
+ // Format: #Set(arg1, arg2, ...)
1139
+ // When used with 'new', the arguments are empty and belong to NewExpression
1140
+ if (!node.arguments || node.arguments.length === 0) {
1141
+ nodeContent = '#Set';
1142
+ } else {
1143
+ const args = path.map(print, 'arguments');
1144
+ nodeContent = concat(['#Set(', join(concat([',', line]), args), ')']);
1145
+ }
1146
+ break;
1147
+ }
1148
+
1149
+ case 'UnaryExpression':
1150
+ nodeContent = printUnaryExpression(node, path, options, print);
1151
+ break;
1152
+
1153
+ case 'YieldExpression':
1154
+ nodeContent = printYieldExpression(node, path, options, print);
1155
+ break;
1156
+
1157
+ case 'TSAsExpression': {
1158
+ nodeContent = concat([
1159
+ path.call(print, 'expression'),
1160
+ ' as ',
1161
+ path.call(print, 'typeAnnotation'),
1162
+ ]);
1163
+ break;
1164
+ }
1165
+
1166
+ case 'NewExpression':
1167
+ nodeContent = printNewExpression(node, path, options, print);
1168
+ break;
1169
+
1170
+ case 'TemplateLiteral':
1171
+ nodeContent = printTemplateLiteral(node, path, options, print);
1172
+ break;
1173
+
1174
+ case 'TaggedTemplateExpression':
1175
+ nodeContent = printTaggedTemplateExpression(node, path, options, print);
1176
+ break;
1177
+
1178
+ case 'ThrowStatement':
1179
+ nodeContent = printThrowStatement(node, path, options, print);
1180
+ break;
1181
+
1182
+ case 'TSInterfaceDeclaration':
1183
+ nodeContent = printTSInterfaceDeclaration(node, path, options, print);
1184
+ break;
1185
+
1186
+ case 'TSTypeAliasDeclaration':
1187
+ nodeContent = printTSTypeAliasDeclaration(node, path, options, print);
1188
+ break;
1189
+
1190
+ case 'TSEnumDeclaration':
1191
+ nodeContent = printTSEnumDeclaration(node, path, options, print);
1192
+ break;
1193
+
1194
+ case 'TSTypeParameterDeclaration':
1195
+ nodeContent = printTSTypeParameterDeclaration(node, path, options, print);
1196
+ break;
1197
+
1198
+ case 'TSTypeParameter':
1199
+ nodeContent = printTSTypeParameter(node, path, options, print);
1200
+ break;
1201
+
1202
+ case 'TSTypeParameterInstantiation':
1203
+ nodeContent = printTSTypeParameterInstantiation(node, path, options, print);
1204
+ break;
1205
+
1206
+ case 'TSSymbolKeyword':
1207
+ nodeContent = 'symbol';
1208
+ break;
1209
+
1210
+ case 'TSAnyKeyword':
1211
+ nodeContent = 'any';
1212
+ break;
1213
+
1214
+ case 'TSUnknownKeyword':
1215
+ nodeContent = 'unknown';
1216
+ break;
1217
+
1218
+ case 'TSNeverKeyword':
1219
+ nodeContent = 'never';
1220
+ break;
1221
+
1222
+ case 'TSVoidKeyword':
1223
+ nodeContent = 'void';
1224
+ break;
1225
+
1226
+ case 'TSUndefinedKeyword':
1227
+ nodeContent = 'undefined';
1228
+ break;
1229
+
1230
+ case 'TSNullKeyword':
1231
+ nodeContent = 'null';
1232
+ break;
1233
+
1234
+ case 'TSNumberKeyword':
1235
+ nodeContent = 'number';
1236
+ break;
1237
+
1238
+ case 'TSBigIntKeyword':
1239
+ nodeContent = 'bigint';
1240
+ break;
1241
+
1242
+ case 'TSObjectKeyword':
1243
+ nodeContent = 'object';
1244
+ break;
1245
+
1246
+ case 'TSBooleanKeyword':
1247
+ nodeContent = 'boolean';
1248
+ break;
1249
+
1250
+ case 'TSStringKeyword':
1251
+ nodeContent = 'string';
1252
+ break;
1253
+
1254
+ case 'EmptyStatement':
1255
+ nodeContent = '';
1256
+ break;
1257
+
1258
+ case 'TSInterfaceBody':
1259
+ nodeContent = printTSInterfaceBody(node, path, options, print);
1260
+ break;
1261
+
1262
+ case 'SwitchStatement':
1263
+ nodeContent = printSwitchStatement(node, path, options, print);
1264
+ break;
1265
+
1266
+ case 'SwitchCase':
1267
+ nodeContent = printSwitchCase(node, path, options, print);
1268
+ break;
1269
+
1270
+ case 'BreakStatement':
1271
+ nodeContent = printBreakStatement(node, path, options, print);
1272
+ break;
1273
+
1274
+ case 'ContinueStatement':
1275
+ nodeContent = printContinueStatement(node, path, options, print);
1276
+ break;
1277
+
1278
+ case 'DebuggerStatement':
1279
+ nodeContent = printDebuggerStatement(node, path, options, print);
1280
+ break;
1281
+
1282
+ case 'SequenceExpression':
1283
+ nodeContent = printSequenceExpression(node, path, options, print);
1284
+ break;
1285
+
1286
+ case 'SpreadElement': {
1287
+ const argumentDoc = path.call(print, 'argument');
1288
+ nodeContent = concat(['...', argumentDoc]);
1289
+ break;
1290
+ }
1291
+ case 'RestElement': {
1292
+ const parts = ['...', path.call(print, 'argument')];
1293
+ nodeContent = concat(parts);
1294
+ break;
1295
+ }
1296
+ case 'VariableDeclaration':
1297
+ nodeContent = printVariableDeclaration(node, path, options, print);
1298
+ break;
1299
+
1300
+ case 'ExpressionStatement': {
1301
+ // Object literals at statement position need parentheses to avoid ambiguity with blocks
1302
+ const needsParens =
1303
+ node.expression.type === 'ObjectExpression' ||
1304
+ node.expression.type === 'TrackedObjectExpression';
1305
+ if (needsParens) {
1306
+ nodeContent = concat(['(', path.call(print, 'expression'), ')', semi(options)]);
1307
+ } else {
1308
+ nodeContent = concat([path.call(print, 'expression'), semi(options)]);
1309
+ }
1310
+ break;
1311
+ }
1312
+ case 'RefAttribute':
1313
+ nodeContent = concat(['{ref ', path.call(print, 'argument'), '}']);
1314
+ break;
1315
+
1316
+ case 'SpreadAttribute': {
1317
+ const parts = ['{...', path.call(print, 'argument'), '}'];
1318
+ nodeContent = concat(parts);
1319
+ break;
1320
+ }
1321
+
1322
+ case 'Identifier': {
1323
+ // Simple case - just return the name directly like Prettier core
1324
+ const trackedPrefix = node.tracked ? '@' : '';
1325
+ let identifierContent;
1326
+ if (node.typeAnnotation) {
1327
+ identifierContent = concat([
1328
+ trackedPrefix + node.name,
1329
+ ': ',
1330
+ path.call(print, 'typeAnnotation'),
1331
+ ]);
1332
+ } else {
1333
+ identifierContent = trackedPrefix + node.name;
1334
+ }
1335
+ // Preserve parentheses for type-cast identifiers, but only if:
1336
+ // 1. The identifier itself is marked as parenthesized
1337
+ // 2. The parent is NOT handling parentheses itself (MemberExpression, AssignmentExpression, etc.)
1338
+ const parent = path.getParentNode();
1339
+ const parentHandlesParens =
1340
+ parent &&
1341
+ (parent.type === 'MemberExpression' ||
1342
+ (parent.type === 'AssignmentExpression' && parent.left === node));
1343
+ const shouldAddParens = node.metadata?.parenthesized && !parentHandlesParens;
1344
+ if (shouldAddParens) {
1345
+ nodeContent = concat(['(', identifierContent, ')']);
1346
+ } else {
1347
+ nodeContent = identifierContent;
1348
+ }
1349
+ break;
1350
+ }
1351
+
1352
+ case 'Literal':
1353
+ // Handle regex literals specially
1354
+ if (node.regex) {
1355
+ // Regex literal: use the raw representation
1356
+ nodeContent = node.raw || `/${node.regex.pattern}/${node.regex.flags}`;
1357
+ } else {
1358
+ // String, number, boolean, or null literal
1359
+ nodeContent = formatStringLiteral(node.value, options);
1360
+ }
1361
+ break;
1362
+
1363
+ case 'ArrowFunctionExpression':
1364
+ nodeContent = printArrowFunction(node, path, options, print);
1365
+ break;
1366
+
1367
+ case 'FunctionExpression':
1368
+ nodeContent = printFunctionExpression(node, path, options, print);
1369
+ break;
1370
+
1371
+ case 'BlockStatement': {
1372
+ // Apply the same block formatting pattern as component bodies
1373
+ if (!node.body || node.body.length === 0) {
1374
+ // Handle innerComments for empty blocks
1375
+ if (innerCommentParts.length > 0) {
1376
+ nodeContent = group([
1377
+ '{',
1378
+ indent([hardline, join(hardline, innerCommentParts)]),
1379
+ hardline,
1380
+ '}',
1381
+ ]);
1382
+ break;
1383
+ }
1384
+ nodeContent = '{}';
1385
+ break;
1386
+ }
1387
+
1388
+ // Process statements and handle spacing using shouldAddBlankLine
1389
+ const statements = [];
1390
+ for (let i = 0; i < node.body.length; i++) {
1391
+ const statement = path.call(print, 'body', i);
1392
+ statements.push(statement);
1393
+
1394
+ // Handle blank lines between statements
1395
+ if (i < node.body.length - 1) {
1396
+ const currentStmt = node.body[i];
1397
+ const nextStmt = node.body[i + 1];
1398
+
1399
+ if (shouldAddBlankLine(currentStmt, nextStmt)) {
1400
+ statements.push(hardline, hardline); // Blank line = two hardlines
1401
+ } else {
1402
+ statements.push(hardline); // Normal line break
1403
+ }
1404
+ }
1405
+ }
1406
+
1407
+ // Use proper block statement pattern
1408
+ nodeContent = group(['{', indent([hardline, concat(statements)]), hardline, '}']);
1409
+ break;
1410
+ }
1411
+
1412
+ case 'ServerBlock': {
1413
+ const blockContent = path.call(print, 'body');
1414
+ nodeContent = concat(['#server ', blockContent]);
1415
+ break;
1416
+ }
1417
+
1418
+ case 'ReturnStatement': {
1419
+ const parts = ['return'];
1420
+ if (node.argument) {
1421
+ parts.push(' ');
1422
+ parts.push(path.call(print, 'argument'));
1423
+ }
1424
+ parts.push(semi(options));
1425
+ nodeContent = concat(parts);
1426
+ break;
1427
+ }
1428
+
1429
+ case 'BinaryExpression': {
1430
+ // Check if we're in an assignment/declaration context where parent handles indentation
1431
+ const parent = path.getParentNode();
1432
+ const shouldNotIndent =
1433
+ parent &&
1434
+ (parent.type === 'VariableDeclarator' ||
1435
+ parent.type === 'AssignmentExpression' ||
1436
+ parent.type === 'AssignmentPattern');
1437
+
1438
+ // Don't add indent if we're in a conditional test context
1439
+ if (args?.isConditionalTest) {
1440
+ nodeContent = group(
1441
+ concat([
1442
+ path.call((childPath) => print(childPath, { isConditionalTest: true }), 'left'),
1443
+ ' ',
1444
+ node.operator,
1445
+ concat([
1446
+ line,
1447
+ path.call((childPath) => print(childPath, { isConditionalTest: true }), 'right'),
1448
+ ]),
1449
+ ]),
1450
+ );
1451
+ } else if (shouldNotIndent) {
1452
+ // In assignment context, don't add indent - parent will handle it
1453
+ nodeContent = group(
1454
+ concat([
1455
+ path.call(print, 'left'),
1456
+ ' ',
1457
+ node.operator,
1458
+ concat([line, path.call(print, 'right')]),
1459
+ ]),
1460
+ );
1461
+ } else {
1462
+ nodeContent = group(
1463
+ concat([
1464
+ path.call(print, 'left'),
1465
+ ' ',
1466
+ node.operator,
1467
+ indent(concat([line, path.call(print, 'right')])),
1468
+ ]),
1469
+ );
1470
+ }
1471
+ break;
1472
+ }
1473
+ case 'LogicalExpression':
1474
+ // Don't add indent if we're in a conditional test context
1475
+ if (args?.isConditionalTest) {
1476
+ nodeContent = group(
1477
+ concat([
1478
+ path.call((childPath) => print(childPath, { isConditionalTest: true }), 'left'),
1479
+ ' ',
1480
+ node.operator,
1481
+ concat([
1482
+ line,
1483
+ path.call((childPath) => print(childPath, { isConditionalTest: true }), 'right'),
1484
+ ]),
1485
+ ]),
1486
+ );
1487
+ } else {
1488
+ nodeContent = group(
1489
+ concat([
1490
+ path.call(print, 'left'),
1491
+ ' ',
1492
+ node.operator,
1493
+ indent(concat([line, path.call(print, 'right')])),
1494
+ ]),
1495
+ );
1496
+ }
1497
+ break;
1498
+
1499
+ case 'ConditionalExpression': {
1500
+ // Use Prettier's grouping to handle line breaking when exceeding printWidth
1501
+ // For the test expression, if it's a LogicalExpression or BinaryExpression,
1502
+ // tell it not to add its own indentation since we're in a conditional context
1503
+ const testNeedsContext =
1504
+ node.test.type === 'LogicalExpression' || node.test.type === 'BinaryExpression';
1505
+ const testDoc = testNeedsContext
1506
+ ? path.call((childPath) => print(childPath, { isConditionalTest: true }), 'test')
1507
+ : path.call(print, 'test');
1508
+
1509
+ // Check if we have nested ternaries (but not if they're parenthesized, which keeps them inline)
1510
+ const hasUnparenthesizedNestedConditional =
1511
+ (node.consequent.type === 'ConditionalExpression' &&
1512
+ !node.consequent.metadata?.parenthesized) ||
1513
+ (node.alternate.type === 'ConditionalExpression' &&
1514
+ !node.alternate.metadata?.parenthesized);
1515
+
1516
+ // If we have unparenthesized nested ternaries, tell the children they're nested
1517
+ const consequentDoc =
1518
+ hasUnparenthesizedNestedConditional &&
1519
+ node.consequent.type === 'ConditionalExpression' &&
1520
+ !node.consequent.metadata?.parenthesized
1521
+ ? path.call((childPath) => print(childPath, { isNestedConditional: true }), 'consequent')
1522
+ : path.call(print, 'consequent');
1523
+ const alternateDoc =
1524
+ hasUnparenthesizedNestedConditional &&
1525
+ node.alternate.type === 'ConditionalExpression' &&
1526
+ !node.alternate.metadata?.parenthesized
1527
+ ? path.call((childPath) => print(childPath, { isNestedConditional: true }), 'alternate')
1528
+ : path.call(print, 'alternate');
1529
+
1530
+ // Check if the consequent or alternate will break
1531
+ const consequentBreaks = willBreak(consequentDoc);
1532
+ const alternateBreaks = willBreak(alternateDoc);
1533
+
1534
+ // Helper to determine if a node type already handles its own indentation
1535
+ const hasOwnIndentation = (nodeType) => {
1536
+ return nodeType === 'BinaryExpression' || nodeType === 'LogicalExpression';
1537
+ };
1538
+
1539
+ let result;
1540
+ // If either branch breaks OR we have unparenthesized nested ternaries OR we're already nested, use multiline format
1541
+ if (
1542
+ consequentBreaks ||
1543
+ alternateBreaks ||
1544
+ hasUnparenthesizedNestedConditional ||
1545
+ args?.isNestedConditional
1546
+ ) {
1547
+ // Only add extra indent if the expression doesn't handle its own indentation
1548
+ // AND it's not a nested conditional (which already gets indented by its parent)
1549
+ const shouldIndentConsequent =
1550
+ !hasOwnIndentation(node.consequent.type) &&
1551
+ node.consequent.type !== 'ConditionalExpression';
1552
+ const shouldIndentAlternate =
1553
+ !hasOwnIndentation(node.alternate.type) &&
1554
+ node.alternate.type !== 'ConditionalExpression';
1555
+
1556
+ result = concat([
1557
+ testDoc,
1558
+ indent(
1559
+ concat([line, '? ', shouldIndentConsequent ? indent(consequentDoc) : consequentDoc]),
1560
+ ),
1561
+ indent(concat([line, ': ', shouldIndentAlternate ? indent(alternateDoc) : alternateDoc])),
1562
+ ]);
1563
+ } else {
1564
+ // Otherwise try inline first, then multiline if it doesn't fit
1565
+ const shouldIndentConsequent =
1566
+ !hasOwnIndentation(node.consequent.type) &&
1567
+ node.consequent.type !== 'ConditionalExpression';
1568
+ const shouldIndentAlternate =
1569
+ !hasOwnIndentation(node.alternate.type) &&
1570
+ node.alternate.type !== 'ConditionalExpression';
1571
+
1572
+ result = conditionalGroup([
1573
+ // Try inline first
1574
+ concat([testDoc, ' ? ', consequentDoc, ' : ', alternateDoc]),
1575
+ // If inline doesn't fit, use multiline
1576
+ concat([
1577
+ testDoc,
1578
+ indent(
1579
+ concat([line, '? ', shouldIndentConsequent ? indent(consequentDoc) : consequentDoc]),
1580
+ ),
1581
+ indent(
1582
+ concat([line, ': ', shouldIndentAlternate ? indent(alternateDoc) : alternateDoc]),
1583
+ ),
1584
+ ]),
1585
+ ]);
1586
+ }
1587
+
1588
+ // Wrap in parentheses if metadata indicates they were present
1589
+ if (node.metadata?.parenthesized) {
1590
+ result = concat(['(', result, ')']);
1591
+ }
1592
+
1593
+ nodeContent = result;
1594
+ break;
1595
+ }
1596
+
1597
+ case 'UpdateExpression':
1598
+ if (node.prefix) {
1599
+ nodeContent = concat([node.operator, path.call(print, 'argument')]);
1600
+ } else {
1601
+ nodeContent = concat([path.call(print, 'argument'), node.operator]);
1602
+ }
1603
+ break;
1604
+
1605
+ case 'TSArrayType': {
1606
+ const parts = [path.call(print, 'elementType'), '[]'];
1607
+ nodeContent = concat(parts);
1608
+ break;
1609
+ }
1610
+
1611
+ case 'MemberExpression':
1612
+ nodeContent = printMemberExpression(node, path, options, print);
1613
+ break;
1614
+
1615
+ case 'ObjectPattern':
1616
+ nodeContent = printObjectPattern(node, path, options, print);
1617
+ break;
1618
+
1619
+ case 'ArrayPattern':
1620
+ nodeContent = printArrayPattern(node, path, options, print);
1621
+ break;
1622
+
1623
+ case 'Property':
1624
+ nodeContent = printProperty(node, path, options, print);
1625
+ break;
1626
+
1627
+ case 'VariableDeclarator':
1628
+ nodeContent = printVariableDeclarator(node, path, options, print);
1629
+ break;
1630
+
1631
+ case 'AssignmentPattern':
1632
+ nodeContent = printAssignmentPattern(node, path, options, print);
1633
+ break;
1634
+
1635
+ case 'TSTypeAnnotation': {
1636
+ nodeContent = path.call(print, 'typeAnnotation');
1637
+ break;
1638
+ }
1639
+
1640
+ case 'TSTypeLiteral':
1641
+ nodeContent = printTSTypeLiteral(node, path, options, print);
1642
+ break;
1643
+
1644
+ case 'TSPropertySignature':
1645
+ nodeContent = printTSPropertySignature(node, path, options, print);
1646
+ break;
1647
+
1648
+ case 'TSEnumMember':
1649
+ nodeContent = printTSEnumMember(node, path, options, print);
1650
+ break;
1651
+
1652
+ case 'TSLiteralType':
1653
+ nodeContent = path.call(print, 'literal');
1654
+ break;
1655
+
1656
+ case 'TSUnionType': {
1657
+ const types = path.map(print, 'types');
1658
+ nodeContent = join(' | ', types);
1659
+ break;
1660
+ }
1661
+
1662
+ case 'TSIntersectionType': {
1663
+ const types = path.map(print, 'types');
1664
+ nodeContent = join(' & ', types);
1665
+ break;
1666
+ }
1667
+
1668
+ case 'TSTypeReference':
1669
+ nodeContent = printTSTypeReference(node, path, options, print);
1670
+ break;
1671
+
1672
+ case 'TSTypeOperator': {
1673
+ const operator = node.operator;
1674
+ const type = path.call(print, 'typeAnnotation');
1675
+ nodeContent = `${operator} ${type}`;
1676
+ break;
1677
+ }
1678
+
1679
+ case 'TSTypeQuery': {
1680
+ const expr = path.call(print, 'exprName');
1681
+ nodeContent = concat(['typeof ', expr]);
1682
+ break;
1683
+ }
1684
+
1685
+ case 'TSFunctionType': {
1686
+ const parts = [];
1687
+
1688
+ // Handle parameters
1689
+ parts.push('(');
1690
+ if (node.parameters && node.parameters.length > 0) {
1691
+ const params = path.map(print, 'parameters');
1692
+ for (let i = 0; i < params.length; i++) {
1693
+ if (i > 0) parts.push(', ');
1694
+ parts.push(params[i]);
1695
+ }
1696
+ }
1697
+ parts.push(')');
1698
+
1699
+ // Handle return type
1700
+ parts.push(' => ');
1701
+ if (node.returnType) {
1702
+ parts.push(path.call(print, 'returnType'));
1703
+ } else if (node.typeAnnotation) {
1704
+ parts.push(path.call(print, 'typeAnnotation'));
1705
+ }
1706
+
1707
+ nodeContent = concat(parts);
1708
+ break;
1709
+ }
1710
+
1711
+ case 'TSTupleType':
1712
+ nodeContent = printTSTupleType(node, path, options, print);
1713
+ break;
1714
+
1715
+ case 'TSIndexSignature':
1716
+ nodeContent = printTSIndexSignature(node, path, options, print);
1717
+ break;
1718
+
1719
+ case 'TSConstructorType':
1720
+ nodeContent = printTSConstructorType(node, path, options, print);
1721
+ break;
1722
+
1723
+ case 'TSConditionalType':
1724
+ nodeContent = printTSConditionalType(node, path, options, print);
1725
+ break;
1726
+
1727
+ case 'TSMappedType':
1728
+ nodeContent = printTSMappedType(node, path, options, print);
1729
+ break;
1730
+
1731
+ case 'TSQualifiedName':
1732
+ nodeContent = printTSQualifiedName(node, path, options, print);
1733
+ break;
1734
+
1735
+ case 'TSIndexedAccessType':
1736
+ nodeContent = printTSIndexedAccessType(node, path, options, print);
1737
+ break;
1738
+
1739
+ case 'TSParenthesizedType': {
1740
+ nodeContent = concat(['(', path.call(print, 'typeAnnotation'), ')']);
1741
+ break;
1742
+ }
1743
+
1744
+ case 'TSExpressionWithTypeArguments': {
1745
+ const parts = [];
1746
+ parts.push(path.call(print, 'expression'));
1747
+
1748
+ if (node.typeParameters) {
1749
+ parts.push(path.call(print, 'typeParameters'));
1750
+ }
1751
+
1752
+ nodeContent = concat(parts);
1753
+ break;
1754
+ }
1755
+
1756
+ case 'Element':
1757
+ nodeContent = printElement(node, path, options, print);
1758
+ break;
1759
+
1760
+ case 'TsxCompat':
1761
+ nodeContent = printTsxCompat(node, path, options, print);
1762
+ break;
1763
+
1764
+ case 'JSXElement':
1765
+ nodeContent = printJSXElement(node, path, options, print);
1766
+ break;
1767
+
1768
+ case 'JSXFragment':
1769
+ nodeContent = printJSXFragment(node, path, options, print);
1770
+ break;
1771
+
1772
+ case 'JSXText':
1773
+ nodeContent = node.value;
1774
+ break;
1775
+
1776
+ case 'JSXEmptyExpression':
1777
+ // JSXEmptyExpression represents the empty expression in {/* comment */}
1778
+ // The comments are attached as innerComments by the parser
1779
+ if (innerCommentParts.length > 0) {
1780
+ nodeContent = concat(innerCommentParts);
1781
+ } else {
1782
+ nodeContent = '';
1783
+ }
1784
+ break;
1785
+
1786
+ case 'StyleSheet':
1787
+ nodeContent = printStyleSheet(node, path, options, print);
1788
+ break;
1789
+ case 'Rule':
1790
+ nodeContent = printCSSRule(node, path, options, print);
1791
+ break;
1792
+
1793
+ case 'Declaration':
1794
+ nodeContent = printCSSDeclaration(node, path, options, print);
1795
+ break;
1796
+
1797
+ case 'Atrule':
1798
+ nodeContent = printCSSAtrule(node, path, options, print);
1799
+ break;
1800
+
1801
+ case 'SelectorList':
1802
+ nodeContent = printCSSSelectorList(node, path, options, print);
1803
+ break;
1804
+
1805
+ case 'ComplexSelector':
1806
+ nodeContent = printCSSComplexSelector(node, path, options, print);
1807
+ break;
1808
+
1809
+ case 'RelativeSelector':
1810
+ nodeContent = printCSSRelativeSelector(node, path, options, print);
1811
+ break;
1812
+
1813
+ case 'TypeSelector':
1814
+ nodeContent = printCSSTypeSelector(node, path, options, print);
1815
+ break;
1816
+
1817
+ case 'IdSelector':
1818
+ nodeContent = printCSSIdSelector(node, path, options, print);
1819
+ break;
1820
+
1821
+ case 'ClassSelector':
1822
+ nodeContent = printCSSClassSelector(node, path, options, print);
1823
+ break;
1824
+
1825
+ case 'NestingSelector':
1826
+ nodeContent = printCSSNestingSelector(node, path, options, print);
1827
+ break;
1828
+
1829
+ case 'Block':
1830
+ nodeContent = printCSSBlock(node, path, options, print);
1831
+ break;
1832
+
1833
+ case 'Attribute':
1834
+ nodeContent = printAttribute(node, path, options, print);
1835
+ break;
1836
+
1837
+ case 'Text': {
1838
+ const expressionDoc = suppressExpressionLeadingComments
1839
+ ? path.call((exprPath) => print(exprPath, { suppressLeadingComments: true }), 'expression')
1840
+ : path.call(print, 'expression');
1841
+ nodeContent = concat(['{', expressionDoc, '}']);
1842
+ break;
1843
+ }
1844
+
1845
+ case 'Html': {
1846
+ const expressionDoc = suppressExpressionLeadingComments
1847
+ ? path.call((exprPath) => print(exprPath, { suppressLeadingComments: true }), 'expression')
1848
+ : path.call(print, 'expression');
1849
+ nodeContent = concat(['{html ', expressionDoc, '}']);
1850
+ break;
1851
+ }
1852
+
1853
+ default:
1854
+ // Fallback for unknown node types
1855
+ console.warn('Unknown node type:', node.type);
1856
+ nodeContent = '/* Unknown: ' + node.type + ' */';
1857
+ break;
1858
+ }
1859
+
1860
+ // Handle trailing comments
1861
+ if (node.trailingComments) {
1862
+ const trailingParts = [];
1863
+ let previousComment = null;
1864
+
1865
+ for (let i = 0; i < node.trailingComments.length; i++) {
1866
+ const comment = node.trailingComments[i];
1867
+ const isInlineComment = Boolean(
1868
+ node.loc && comment.loc && node.loc.end.line === comment.loc.start.line,
1869
+ );
1870
+
1871
+ const commentDoc =
1872
+ comment.type === 'Line' ? '//' + comment.value : '/*' + comment.value + '*/';
1873
+
1874
+ if (isInlineComment) {
1875
+ if (comment.type === 'Line') {
1876
+ trailingParts.push(lineSuffix([' ', commentDoc]));
1877
+ trailingParts.push(breakParent);
1878
+ } else {
1879
+ trailingParts.push(' ' + commentDoc);
1880
+ }
1881
+ } else {
1882
+ const refs = [];
1883
+ refs.push(hardline);
1884
+
1885
+ const blankLinesBetween = previousComment
1886
+ ? getBlankLinesBetweenNodes(previousComment, comment)
1887
+ : getBlankLinesBetweenNodes(node, comment);
1888
+ if (blankLinesBetween > 0) {
1889
+ refs.push(hardline);
1890
+ }
1891
+
1892
+ if (comment.type === 'Line') {
1893
+ refs.push(commentDoc);
1894
+ trailingParts.push(lineSuffix(refs));
1895
+ } else {
1896
+ refs.push(commentDoc);
1897
+ trailingParts.push(lineSuffix(refs));
1898
+ }
1899
+ }
1900
+
1901
+ previousComment = comment;
1902
+ }
1903
+
1904
+ if (trailingParts.length > 0) {
1905
+ parts.push(nodeContent);
1906
+ parts.push(...trailingParts);
1907
+ return concat(parts);
1908
+ }
1909
+ } // Return with or without leading comments
1910
+ if (parts.length > 0) {
1911
+ // Don't add blank line between leading comments and node
1912
+ // because they're meant to be attached together
1913
+ parts.push(nodeContent);
1914
+ return concat(parts);
1915
+ }
1916
+
1917
+ return nodeContent;
1918
+ }
1919
+
1920
+ function printImportDeclaration(node, path, options, print) {
1921
+ // Use Prettier's doc builders for proper cursor tracking
1922
+ const parts = ['import'];
1923
+
1924
+ // Handle type imports
1925
+ if (node.importKind === 'type') {
1926
+ parts.push(' type');
1927
+ }
1928
+
1929
+ if (node.specifiers && node.specifiers.length > 0) {
1930
+ const defaultImports = [];
1931
+ const namedImports = [];
1932
+ const namespaceImports = [];
1933
+
1934
+ node.specifiers.forEach((spec) => {
1935
+ if (spec.type === 'ImportDefaultSpecifier') {
1936
+ defaultImports.push(spec.local.name);
1937
+ } else if (spec.type === 'ImportSpecifier') {
1938
+ // Handle inline type imports: import { type Component } from 'ripple'
1939
+ const typePrefix = spec.importKind === 'type' ? 'type ' : '';
1940
+ const importName =
1941
+ spec.imported.name === spec.local.name
1942
+ ? typePrefix + spec.local.name
1943
+ : typePrefix + spec.imported.name + ' as ' + spec.local.name;
1944
+ namedImports.push(importName);
1945
+ } else if (spec.type === 'ImportNamespaceSpecifier') {
1946
+ namespaceImports.push('* as ' + spec.local.name);
1947
+ }
1948
+ });
1949
+
1950
+ // Build import clause properly
1951
+ const importParts = [];
1952
+ if (defaultImports.length > 0) {
1953
+ importParts.push(defaultImports.join(', '));
1954
+ }
1955
+ if (namespaceImports.length > 0) {
1956
+ importParts.push(namespaceImports.join(', '));
1957
+ }
1958
+ if (namedImports.length > 0) {
1959
+ importParts.push('{ ' + namedImports.join(', ') + ' }');
1960
+ }
1961
+
1962
+ parts.push(' ' + importParts.join(', ') + ' from');
1963
+ }
1964
+
1965
+ parts.push(' ' + formatStringLiteral(node.source.value, options) + semi(options));
1966
+
1967
+ // Return as single string for proper cursor tracking
1968
+ return parts;
1969
+ }
1970
+
1971
+ function printExportNamedDeclaration(node, path, options, print) {
1972
+ if (node.declaration) {
1973
+ const parts = [];
1974
+ parts.push('export ');
1975
+ parts.push(path.call(print, 'declaration'));
1976
+ return parts;
1977
+ } else if (node.specifiers && node.specifiers.length > 0) {
1978
+ const specifiers = node.specifiers.map((spec) => {
1979
+ if (spec.exported.name === spec.local.name) {
1980
+ return spec.local.name;
1981
+ } else {
1982
+ return spec.local.name + ' as ' + spec.exported.name;
1983
+ }
1984
+ });
1985
+
1986
+ const parts = ['export { '];
1987
+ for (let i = 0; i < specifiers.length; i++) {
1988
+ if (i > 0) parts.push(', ');
1989
+ parts.push(specifiers[i]);
1990
+ }
1991
+ parts.push(' }');
1992
+
1993
+ if (node.source) {
1994
+ parts.push(' from ');
1995
+ parts.push(formatStringLiteral(node.source.value, options));
1996
+ }
1997
+ parts.push(semi(options));
1998
+
1999
+ return parts;
2000
+ }
2001
+
2002
+ return 'export';
2003
+ }
2004
+
2005
+ function printComponent(node, path, options, print) {
2006
+ // Use arrays instead of string concatenation
2007
+ const signatureParts = ['component ', node.id.name];
2008
+
2009
+ // Add TypeScript generics if present
2010
+ if (node.typeParameters) {
2011
+ const typeParams = path.call(print, 'typeParameters');
2012
+ if (Array.isArray(typeParams)) {
2013
+ signatureParts.push(...typeParams);
2014
+ } else {
2015
+ signatureParts.push(typeParams);
2016
+ }
2017
+ }
2018
+
2019
+ // Print parameters using shared function
2020
+ const paramsPart = printFunctionParameters(path, options, print);
2021
+ signatureParts.push(group(paramsPart)); // Build body content using the same pattern as BlockStatement
2022
+ const statements = [];
2023
+
2024
+ for (let i = 0; i < node.body.length; i++) {
2025
+ const statement = path.call(print, 'body', i);
2026
+ statements.push(statement);
2027
+
2028
+ // Handle blank lines between statements
2029
+ if (i < node.body.length - 1) {
2030
+ const currentStmt = node.body[i];
2031
+ const nextStmt = node.body[i + 1];
2032
+
2033
+ // Use shouldAddBlankLine to determine spacing
2034
+ if (shouldAddBlankLine(currentStmt, nextStmt)) {
2035
+ statements.push(hardline, hardline); // Blank line = two hardlines
2036
+ } else {
2037
+ statements.push(hardline); // Normal line break
2038
+ }
2039
+ }
2040
+ }
2041
+
2042
+ // Process statements to add them to contentParts
2043
+ const contentParts = [];
2044
+ if (statements.length > 0) {
2045
+ contentParts.push(concat(statements));
2046
+ }
2047
+
2048
+ // Build script content using Prettier document builders
2049
+ let scriptContent = null;
2050
+ if (node.script && node.script.source) {
2051
+ const script = node.script.source.trim();
2052
+
2053
+ // Build the complete script block as a formatted string
2054
+ // Include proper indentation for component level
2055
+ let scriptString = ' <script>\n';
2056
+ const scriptLines = script.split('\n');
2057
+ for (const line of scriptLines) {
2058
+ if (line.trim()) {
2059
+ scriptString += ' ' + line + '\n';
2060
+ } else {
2061
+ scriptString += '\n';
2062
+ }
2063
+ }
2064
+ scriptString += ' </script>';
2065
+
2066
+ scriptContent = [scriptString];
2067
+ }
2068
+
2069
+ // Use Prettier's standard block statement pattern
2070
+ const parts = [concat(signatureParts), ' {'];
2071
+
2072
+ if (statements.length > 0 || scriptContent) {
2073
+ // Build all content that goes inside the component body
2074
+ const allContent = [];
2075
+
2076
+ // Build content manually with proper spacing
2077
+ let contentParts = [];
2078
+
2079
+ // Add statements
2080
+ if (statements.length > 0) {
2081
+ // The statements array contains statements separated by line breaks
2082
+ // We need to use join to properly handle the line breaks
2083
+ contentParts.push(concat(statements));
2084
+ }
2085
+
2086
+ // Add script content
2087
+ if (scriptContent) {
2088
+ if (contentParts.length > 0) {
2089
+ // Always add blank line before script for separation of concerns
2090
+ contentParts.push(hardline);
2091
+ }
2092
+ // Script content is manually indented
2093
+ contentParts.push(...scriptContent);
2094
+ }
2095
+
2096
+ // Join content parts
2097
+ const joinedContent = contentParts.length > 0 ? concat(contentParts) : '';
2098
+
2099
+ // Apply component-level indentation
2100
+ const indentedContent = joinedContent ? indent([hardline, joinedContent]) : indent([hardline]);
2101
+
2102
+ // Add the body and closing brace
2103
+ parts.push(indentedContent, hardline, '}');
2104
+ } else {
2105
+ // Empty component body
2106
+ parts[1] = ' {}';
2107
+ }
2108
+
2109
+ return concat(parts);
2110
+ }
2111
+
2112
+ function printVariableDeclaration(node, path, options, print) {
2113
+ const kind = node.kind || 'let';
2114
+
2115
+ // Don't add semicolon ONLY if this is part of a for loop header
2116
+ // - ForStatement: the init part
2117
+ // - ForOfStatement: the left part
2118
+ const parentNode = path.getParentNode();
2119
+ const isForLoopInit =
2120
+ (parentNode && parentNode.type === 'ForStatement' && parentNode.init === node) ||
2121
+ (parentNode && parentNode.type === 'ForOfStatement' && parentNode.left === node);
2122
+
2123
+ const declarations = path.map(print, 'declarations');
2124
+ const declarationParts = join(', ', declarations);
2125
+
2126
+ if (!isForLoopInit) {
2127
+ return concat([kind, ' ', declarationParts, semi(options)]);
2128
+ }
2129
+
2130
+ return concat([kind, ' ', declarationParts]);
2131
+ }
2132
+
2133
+ function printFunctionExpression(node, path, options, print) {
2134
+ const parts = [];
2135
+
2136
+ // Handle async functions
2137
+ if (node.async) {
2138
+ parts.push('async ');
2139
+ }
2140
+
2141
+ parts.push('function');
2142
+
2143
+ // Handle generator functions
2144
+ if (node.generator) {
2145
+ parts.push('*');
2146
+ }
2147
+
2148
+ // Function name (if any)
2149
+ if (node.id) {
2150
+ parts.push(' ');
2151
+ parts.push(node.id.name);
2152
+ }
2153
+
2154
+ // Add TypeScript generics if present
2155
+ if (node.typeParameters) {
2156
+ // Only add space if there's no function name
2157
+ if (!node.id) {
2158
+ parts.push(' ');
2159
+ }
2160
+ const typeParams = path.call(print, 'typeParameters');
2161
+ if (Array.isArray(typeParams)) {
2162
+ parts.push(...typeParams);
2163
+ } else {
2164
+ parts.push(typeParams);
2165
+ }
2166
+ } else if (!node.id) {
2167
+ // If no name and no type parameters, add space before params
2168
+ parts.push(' ');
2169
+ }
2170
+
2171
+ // Print parameters using shared function
2172
+ const paramsPart = printFunctionParameters(path, options, print);
2173
+ parts.push(group(paramsPart)); // Handle return type annotation
2174
+ if (node.returnType) {
2175
+ parts.push(': ', path.call(print, 'returnType'));
2176
+ }
2177
+
2178
+ parts.push(' ');
2179
+ parts.push(path.call(print, 'body'));
2180
+
2181
+ return concat(parts);
2182
+ }
2183
+
2184
+ function printArrowFunction(node, path, options, print) {
2185
+ const parts = [];
2186
+
2187
+ if (node.async) {
2188
+ parts.push('async ');
2189
+ }
2190
+
2191
+ // Add TypeScript generics if present
2192
+ if (node.typeParameters) {
2193
+ const typeParams = path.call(print, 'typeParameters');
2194
+ if (Array.isArray(typeParams)) {
2195
+ parts.push(...typeParams);
2196
+ } else {
2197
+ parts.push(typeParams);
2198
+ }
2199
+ }
2200
+
2201
+ // Handle single param without parens (when arrowParens !== 'always')
2202
+ // Note: can't use single param syntax if there are type parameters or return type
2203
+ if (
2204
+ options.arrowParens !== 'always' &&
2205
+ node.params &&
2206
+ node.params.length === 1 &&
2207
+ node.params[0].type === 'Identifier' &&
2208
+ !node.params[0].typeAnnotation &&
2209
+ !node.returnType &&
2210
+ !node.typeParameters
2211
+ ) {
2212
+ parts.push(path.call(print, 'params', 0));
2213
+ } else {
2214
+ // Print parameters using shared function
2215
+ const paramsPart = printFunctionParameters(path, options, print);
2216
+ parts.push(group(paramsPart));
2217
+ } // Handle return type annotation
2218
+ if (node.returnType) {
2219
+ parts.push(': ', path.call(print, 'returnType'));
2220
+ }
2221
+
2222
+ parts.push(' => ');
2223
+
2224
+ // For block statements, print the body directly to get proper formatting
2225
+ if (node.body.type === 'BlockStatement') {
2226
+ parts.push(path.call(print, 'body'));
2227
+ } else {
2228
+ // For expression bodies, check if we need to wrap in parens
2229
+ // Wrap ObjectExpression in parens to avoid ambiguity with block statements
2230
+ if (node.body.type === 'ObjectExpression') {
2231
+ parts.push('(');
2232
+ parts.push(path.call(print, 'body'));
2233
+ parts.push(')');
2234
+ } else {
2235
+ parts.push(path.call(print, 'body'));
2236
+ }
2237
+ }
2238
+
2239
+ return concat(parts);
2240
+ }
2241
+
2242
+ function printExportDefaultDeclaration(node, path, options, print) {
2243
+ const parts = [];
2244
+ parts.push('export default ');
2245
+ parts.push(path.call(print, 'declaration'));
2246
+ return parts;
2247
+ }
2248
+
2249
+ function shouldHugTheOnlyFunctionParameter(node) {
2250
+ if (!node) {
2251
+ return false;
2252
+ }
2253
+ const parameters = getFunctionParameters(node);
2254
+ if (parameters.length !== 1) {
2255
+ return false;
2256
+ }
2257
+ const [parameter] = parameters;
2258
+ return (
2259
+ !hasComment(parameter) &&
2260
+ (parameter.type === 'ObjectPattern' ||
2261
+ parameter.type === 'ArrayPattern' ||
2262
+ (parameter.type === 'Identifier' &&
2263
+ parameter.typeAnnotation &&
2264
+ (parameter.typeAnnotation.type === 'TypeAnnotation' ||
2265
+ parameter.typeAnnotation.type === 'TSTypeAnnotation')))
2266
+ );
2267
+ }
2268
+
2269
+ function printFunctionParameters(path, options, print) {
2270
+ const functionNode = path.node;
2271
+ const parameters = getFunctionParameters(functionNode);
2272
+
2273
+ if (parameters.length === 0) {
2274
+ return ['(', ')'];
2275
+ }
2276
+
2277
+ const shouldHugParameters = shouldHugTheOnlyFunctionParameter(functionNode);
2278
+ const printed = [];
2279
+
2280
+ iterateFunctionParametersPath(path, (parameterPath, index) => {
2281
+ const isLastParameter = index === parameters.length - 1;
2282
+
2283
+ if (isLastParameter && functionNode.rest) {
2284
+ printed.push('...');
2285
+ }
2286
+
2287
+ printed.push(print());
2288
+
2289
+ if (!isLastParameter) {
2290
+ printed.push(',');
2291
+ if (shouldHugParameters) {
2292
+ printed.push(' ');
2293
+ } else if (isNextLineEmpty(parameters[index], options)) {
2294
+ printed.push(hardline, hardline);
2295
+ } else {
2296
+ printed.push(line);
2297
+ }
2298
+ }
2299
+ });
2300
+
2301
+ const hasNotParameterDecorator = parameters.every(
2302
+ (node) => !node.decorators || node.decorators.length === 0,
2303
+ );
2304
+
2305
+ if (shouldHugParameters && hasNotParameterDecorator) {
2306
+ return ['(', ...printed, ')'];
2307
+ }
2308
+
2309
+ return [
2310
+ '(',
2311
+ indent([softline, ...printed]),
2312
+ ifBreak(shouldPrintComma(options, 'all') && !hasRestParameter(functionNode) ? ',' : ''),
2313
+ softline,
2314
+ ')',
2315
+ ];
2316
+ }
2317
+
2318
+ function isSpreadLike(node) {
2319
+ return node && (node.type === 'SpreadElement' || node.type === 'RestElement');
2320
+ }
2321
+
2322
+ function isBlockLikeFunction(node) {
2323
+ if (!node) {
2324
+ return false;
2325
+ }
2326
+ if (node.type === 'FunctionExpression') {
2327
+ return true;
2328
+ }
2329
+ if (node.type === 'ArrowFunctionExpression') {
2330
+ return node.body && node.body.type === 'BlockStatement';
2331
+ }
2332
+ return false;
2333
+ }
2334
+
2335
+ function shouldHugLastArgument(args, argumentBreakFlags) {
2336
+ if (!args || args.length === 0) {
2337
+ return false;
2338
+ }
2339
+
2340
+ const lastIndex = args.length - 1;
2341
+ const lastArg = args[lastIndex];
2342
+
2343
+ if (isSpreadLike(lastArg)) {
2344
+ return false;
2345
+ }
2346
+
2347
+ if (!isBlockLikeFunction(lastArg)) {
2348
+ return false;
2349
+ }
2350
+
2351
+ if (hasComment(lastArg)) {
2352
+ return false;
2353
+ }
2354
+
2355
+ for (let index = 0; index < lastIndex; index++) {
2356
+ const argument = args[index];
2357
+ if (
2358
+ isSpreadLike(argument) ||
2359
+ hasComment(argument) ||
2360
+ isBlockLikeFunction(argument) ||
2361
+ isRegExpLiteral(argument) ||
2362
+ argumentBreakFlags[index]
2363
+ ) {
2364
+ return false;
2365
+ }
2366
+ }
2367
+
2368
+ return true;
2369
+ }
2370
+
2371
+ // Check if arguments contain arrow functions with block bodies that should be hugged
2372
+ function shouldHugArrowFunctions(args) {
2373
+ if (!args || args.length === 0) {
2374
+ return false;
2375
+ }
2376
+
2377
+ // Only hug when the first argument is the block-like callback and there
2378
+ // are no other block-like callbacks later in the list. This mirrors how
2379
+ // Prettier keeps patterns like useEffect(() => {}, deps) inline while
2380
+ // allowing suffix callbacks (e.g. foo(regex, () => {})) to expand.
2381
+ const firstBlockIndex = args.findIndex((arg) => isBlockLikeFunction(arg));
2382
+ if (firstBlockIndex !== 0) {
2383
+ return false;
2384
+ }
2385
+
2386
+ for (let index = 1; index < args.length; index++) {
2387
+ if (isBlockLikeFunction(args[index])) {
2388
+ return false;
2389
+ }
2390
+ }
2391
+
2392
+ return firstBlockIndex === 0;
2393
+ }
2394
+
2395
+ function printCallArguments(path, options, print) {
2396
+ const { node } = path;
2397
+ const args = node.arguments || [];
2398
+
2399
+ if (args.length === 0) {
2400
+ return '()';
2401
+ }
2402
+
2403
+ // Check if last argument can be expanded (object or array)
2404
+ const finalArg = args[args.length - 1];
2405
+ const couldExpandLastArg =
2406
+ finalArg &&
2407
+ (finalArg.type === 'ObjectExpression' ||
2408
+ finalArg.type === 'TrackedObjectExpression' ||
2409
+ finalArg.type === 'ArrayExpression' ||
2410
+ finalArg.type === 'TrackedArrayExpression') &&
2411
+ !hasComment(finalArg);
2412
+
2413
+ const printedArguments = [];
2414
+ const argumentDocs = [];
2415
+ const argumentBreakFlags = [];
2416
+ let anyArgumentHasEmptyLine = false;
2417
+
2418
+ path.each((argumentPath, index) => {
2419
+ const isLast = index === args.length - 1;
2420
+ const argumentNode = args[index];
2421
+ const printOptions = isBlockLikeFunction(argumentNode) ? undefined : { isInlineContext: true };
2422
+
2423
+ // Print normally (not with expandLastArg yet - we'll do that later if needed)
2424
+ const argumentDoc = print(argumentPath, printOptions);
2425
+
2426
+ argumentDocs.push(argumentDoc);
2427
+ // Arrow functions with block bodies have internal breaks but shouldn't
2428
+ // cause the call arguments to break - they stay inline with the call
2429
+ const shouldTreatAsBreaking = willBreak(argumentDoc) && !isBlockLikeFunction(argumentNode);
2430
+ argumentBreakFlags.push(shouldTreatAsBreaking);
2431
+
2432
+ if (!isLast) {
2433
+ if (isNextLineEmpty(argumentNode, options)) {
2434
+ anyArgumentHasEmptyLine = true;
2435
+ printedArguments.push(concat([argumentDoc, ',', hardline, hardline]));
2436
+ } else {
2437
+ printedArguments.push(concat([argumentDoc, ',', line]));
2438
+ }
2439
+ } else {
2440
+ printedArguments.push(argumentDoc);
2441
+ }
2442
+ }, 'arguments');
2443
+ const trailingComma = shouldPrintComma(options, 'all') ? ',' : '';
2444
+
2445
+ // Special case: single array argument should keep opening bracket inline
2446
+ const isSingleArrayArgument =
2447
+ args.length === 1 &&
2448
+ args[0] &&
2449
+ (args[0].type === 'ArrayExpression' || args[0].type === 'TrackedArrayExpression');
2450
+
2451
+ if (isSingleArrayArgument) {
2452
+ // Don't use group() - just concat to allow array to control its own breaking
2453
+ // For single argument, no trailing comma needed
2454
+ return concat(['(', argumentDocs[0], ')']);
2455
+ } // Check if we should hug arrow functions (keep params inline even when body breaks)
2456
+ const shouldHugArrows = shouldHugArrowFunctions(args);
2457
+ let huggedArrowDoc = null;
2458
+
2459
+ // For arrow functions, we want to keep params on same line as opening paren
2460
+ // but allow the block body to break naturally
2461
+ if (shouldHugArrows && !anyArgumentHasEmptyLine) {
2462
+ // Build a version that keeps arguments inline with opening paren
2463
+ const huggedParts = ['('];
2464
+
2465
+ for (let index = 0; index < args.length; index++) {
2466
+ if (index > 0) {
2467
+ huggedParts.push(', ');
2468
+ }
2469
+ huggedParts.push(argumentDocs[index]);
2470
+ }
2471
+
2472
+ huggedParts.push(')');
2473
+ huggedArrowDoc = concat(huggedParts);
2474
+ }
2475
+
2476
+ // Build standard breaking version with indentation
2477
+ const contents = [
2478
+ '(',
2479
+ indent([softline, ...printedArguments]),
2480
+ ifBreak(trailingComma),
2481
+ softline,
2482
+ ')',
2483
+ ];
2484
+
2485
+ const shouldForceBreak = anyArgumentHasEmptyLine;
2486
+ const shouldBreakForContent = argumentDocs.some((docPart) => docPart && willBreak(docPart));
2487
+
2488
+ const groupedContents = group(contents, {
2489
+ shouldBreak: shouldForceBreak || shouldBreakForContent,
2490
+ });
2491
+
2492
+ if (huggedArrowDoc) {
2493
+ return conditionalGroup([huggedArrowDoc, groupedContents]);
2494
+ }
2495
+
2496
+ const lastIndex = args.length - 1;
2497
+ const lastArg = args[lastIndex];
2498
+ const lastArgDoc = argumentDocs[lastIndex];
2499
+ const lastArgBreaks = lastArgDoc ? willBreak(lastArgDoc) : false;
2500
+ const previousArgsBreak =
2501
+ lastIndex > 0 ? argumentBreakFlags.slice(0, lastIndex).some(Boolean) : false;
2502
+ const isExpandableLastArgType =
2503
+ lastArg &&
2504
+ (lastArg.type === 'ObjectExpression' ||
2505
+ lastArg.type === 'TrackedObjectExpression' ||
2506
+ lastArg.type === 'ArrayExpression' ||
2507
+ lastArg.type === 'TrackedArrayExpression');
2508
+
2509
+ // Check if we should expand the last argument (like Prettier's shouldExpandLastArg)
2510
+ const shouldExpandLast =
2511
+ args.length > 1 && couldExpandLastArg && !previousArgsBreak && !anyArgumentHasEmptyLine;
2512
+
2513
+ if (shouldExpandLast) {
2514
+ const headArgs = argumentDocs.slice(0, -1);
2515
+
2516
+ // Re-print the last arg with expandLastArg: true
2517
+ const expandedLastArg = path.call(
2518
+ (argPath) => print(argPath, { isInlineContext: true, expandLastArg: true }),
2519
+ 'arguments',
2520
+ lastIndex,
2521
+ );
2522
+
2523
+ // Build the inline version: head args inline + expanded last arg
2524
+ const inlinePartsWithExpanded = ['('];
2525
+ for (let index = 0; index < headArgs.length; index++) {
2526
+ if (index > 0) {
2527
+ inlinePartsWithExpanded.push(', ');
2528
+ }
2529
+ inlinePartsWithExpanded.push(headArgs[index]);
2530
+ }
2531
+ if (headArgs.length > 0) {
2532
+ inlinePartsWithExpanded.push(', ');
2533
+ }
2534
+ inlinePartsWithExpanded.push(group(expandedLastArg, { shouldBreak: true }));
2535
+ inlinePartsWithExpanded.push(')');
2536
+
2537
+ return conditionalGroup([
2538
+ // Try with normal formatting first
2539
+ concat(['(', ...argumentDocs.flatMap((doc, i) => (i > 0 ? [', ', doc] : [doc])), ')']),
2540
+ // Then try with expanded last arg
2541
+ concat(inlinePartsWithExpanded),
2542
+ // Finally fall back to all args broken out
2543
+ groupedContents,
2544
+ ]);
2545
+ }
2546
+
2547
+ const canInlineLastArg =
2548
+ args.length > 1 &&
2549
+ isExpandableLastArgType &&
2550
+ lastArgBreaks &&
2551
+ !previousArgsBreak &&
2552
+ !anyArgumentHasEmptyLine &&
2553
+ !hasComment(lastArg);
2554
+
2555
+ if (canInlineLastArg) {
2556
+ const inlineParts = ['('];
2557
+ for (let index = 0; index < argumentDocs.length; index++) {
2558
+ if (index > 0) {
2559
+ inlineParts.push(', ');
2560
+ }
2561
+ inlineParts.push(argumentDocs[index]);
2562
+ }
2563
+ inlineParts.push(')');
2564
+
2565
+ return conditionalGroup([concat(inlineParts), groupedContents]);
2566
+ }
2567
+
2568
+ if (!anyArgumentHasEmptyLine && shouldHugLastArgument(args, argumentBreakFlags)) {
2569
+ const lastIndex = args.length - 1;
2570
+ const inlineParts = ['('];
2571
+
2572
+ for (let index = 0; index < lastIndex; index++) {
2573
+ if (index > 0) {
2574
+ inlineParts.push(', ');
2575
+ }
2576
+ inlineParts.push(argumentDocs[index]);
2577
+ }
2578
+
2579
+ if (lastIndex > 0) {
2580
+ inlineParts.push(', ');
2581
+ }
2582
+
2583
+ inlineParts.push(argumentDocs[lastIndex]);
2584
+ inlineParts.push(')');
2585
+
2586
+ return conditionalGroup([group(inlineParts), groupedContents]);
2587
+ }
2588
+
2589
+ return groupedContents;
2590
+ }
2591
+
2592
+ function printFunctionDeclaration(node, path, options, print) {
2593
+ const parts = [];
2594
+
2595
+ // Handle async functions
2596
+ if (node.async) {
2597
+ parts.push('async ');
2598
+ }
2599
+
2600
+ parts.push('function');
2601
+
2602
+ // Handle generator functions
2603
+ if (node.generator) {
2604
+ parts.push('*');
2605
+ }
2606
+
2607
+ parts.push(' ');
2608
+ parts.push(node.id.name);
2609
+
2610
+ // Add TypeScript generics if present
2611
+ if (node.typeParameters) {
2612
+ const typeParams = path.call(print, 'typeParameters');
2613
+ if (Array.isArray(typeParams)) {
2614
+ parts.push(...typeParams);
2615
+ } else {
2616
+ parts.push(typeParams);
2617
+ }
2618
+ }
2619
+
2620
+ // Print parameters using shared function
2621
+ const paramsPart = printFunctionParameters(path, options, print);
2622
+ parts.push(group(paramsPart));
2623
+
2624
+ // Handle return type annotation
2625
+ if (node.returnType) {
2626
+ parts.push(': ', path.call(print, 'returnType'));
2627
+ }
2628
+
2629
+ parts.push(' ');
2630
+ parts.push(path.call(print, 'body'));
2631
+
2632
+ return parts;
2633
+ }
2634
+
2635
+ function printIfStatement(node, path, options, print) {
2636
+ const test = path.call(print, 'test');
2637
+ const consequent = path.call(print, 'consequent');
2638
+
2639
+ // Use group to allow breaking the test when it doesn't fit
2640
+ const testDoc = group(concat(['if (', indent(concat([softline, test])), softline, ')']));
2641
+
2642
+ const parts = [testDoc, ' ', consequent];
2643
+
2644
+ if (node.alternate) {
2645
+ parts.push(' else ', path.call(print, 'alternate'));
2646
+ }
2647
+
2648
+ return concat(parts);
2649
+ }
2650
+
2651
+ function printForOfStatement(node, path, options, print) {
2652
+ const parts = [];
2653
+ parts.push('for (');
2654
+ parts.push(path.call(print, 'left'));
2655
+ parts.push(' of ');
2656
+ parts.push(path.call(print, 'right'));
2657
+
2658
+ // Handle Ripple-specific index syntax
2659
+ if (node.index) {
2660
+ parts.push('; index ');
2661
+ parts.push(path.call(print, 'index'));
2662
+ }
2663
+
2664
+ if (node.key) {
2665
+ parts.push('; key ');
2666
+ parts.push(path.call(print, 'key'));
2667
+ }
2668
+
2669
+ parts.push(') ');
2670
+ parts.push(path.call(print, 'body'));
2671
+
2672
+ return parts;
2673
+ }
2674
+
2675
+ function printForStatement(node, path, options, print) {
2676
+ const parts = [];
2677
+ parts.push('for (');
2678
+
2679
+ // Handle init part
2680
+ if (node.init) {
2681
+ parts.push(path.call(print, 'init'));
2682
+ }
2683
+ parts.push(';');
2684
+
2685
+ // Handle test part
2686
+ if (node.test) {
2687
+ parts.push(' ');
2688
+ parts.push(path.call(print, 'test'));
2689
+ }
2690
+ parts.push(';');
2691
+
2692
+ // Handle update part
2693
+ if (node.update) {
2694
+ parts.push(' ');
2695
+ parts.push(path.call(print, 'update'));
2696
+ }
2697
+
2698
+ parts.push(') ');
2699
+ parts.push(path.call(print, 'body'));
2700
+
2701
+ return parts;
2702
+ }
2703
+
2704
+ // Updated for-loop formatting
2705
+ function printWhileStatement(node, path, options, print) {
2706
+ const parts = [];
2707
+ parts.push('while (');
2708
+ parts.push(path.call(print, 'test'));
2709
+ parts.push(') ');
2710
+ parts.push(path.call(print, 'body'));
2711
+
2712
+ return parts;
2713
+ }
2714
+
2715
+ function printDoWhileStatement(node, path, options, print) {
2716
+ const parts = [];
2717
+ parts.push('do ');
2718
+ parts.push(path.call(print, 'body'));
2719
+ parts.push(' while (');
2720
+ parts.push(path.call(print, 'test'));
2721
+ parts.push(')');
2722
+
2723
+ return parts;
2724
+ }
2725
+
2726
+ function printObjectExpression(node, path, options, print, args) {
2727
+ const skip_offset = node.type === 'TrackedObjectExpression' ? 2 : 1;
2728
+ const open_brace = node.type === 'TrackedObjectExpression' ? '#{' : '{';
2729
+ if (!node.properties || node.properties.length === 0) {
2730
+ return open_brace + '}';
2731
+ }
2732
+
2733
+ // Check if there are blank lines between any properties
2734
+ let hasBlankLinesBetweenProperties = false;
2735
+ for (let i = 0; i < node.properties.length - 1; i++) {
2736
+ const current = node.properties[i];
2737
+ const next = node.properties[i + 1];
2738
+ if (current && next && getBlankLinesBetweenNodes(current, next) > 0) {
2739
+ hasBlankLinesBetweenProperties = true;
2740
+ break;
2741
+ }
2742
+ }
2743
+
2744
+ // Check if object was originally multi-line
2745
+ let isOriginallyMultiLine = false;
2746
+ if (node.loc && node.loc.start && node.loc.end) {
2747
+ isOriginallyMultiLine = node.loc.start.line !== node.loc.end.line;
2748
+ }
2749
+
2750
+ // Also check for blank lines at edges (after { or before })
2751
+ // If the original code has blank lines anywhere in the object, format multi-line
2752
+ let hasAnyBlankLines = hasBlankLinesBetweenProperties;
2753
+ if (!hasAnyBlankLines && node.properties.length > 0 && options.originalText) {
2754
+ const firstProp = node.properties[0];
2755
+ const lastProp = node.properties[node.properties.length - 1];
2756
+
2757
+ // Check for blank line after opening brace (before first property)
2758
+ if (firstProp && node.loc && node.loc.start) {
2759
+ hasAnyBlankLines = getBlankLinesBetweenPositions(
2760
+ node.loc.start.offset(skip_offset),
2761
+ firstProp.loc.start,
2762
+ );
2763
+ }
2764
+
2765
+ // Check for blank line before closing brace (after last property)
2766
+ if (!hasAnyBlankLines && lastProp && node.loc && node.loc.end) {
2767
+ hasAnyBlankLines = getBlankLinesBetweenPositions(
2768
+ lastProp.loc.end,
2769
+ node.loc.end.offset(-1), // -1 to skip the '}'
2770
+ );
2771
+ }
2772
+ }
2773
+
2774
+ // Check if we should try to format inline
2775
+ const isInArray = args && args.isInArray;
2776
+ const isInAttribute = args && args.isInAttribute;
2777
+ const isSimple = node.properties.length <= 2;
2778
+ // Only 1-property objects are considered very simple for compact formatting
2779
+ const isVerySimple = node.properties.length === 1;
2780
+
2781
+ // Use AST builders and respect trailing commas
2782
+ const properties = path.map(print, 'properties');
2783
+ const shouldUseTrailingComma = options.trailingComma !== 'none' && properties.length > 0;
2784
+
2785
+ // For arrays: very simple (1-prop) objects can be inline, 2-prop objects always multiline
2786
+ // For attributes: force inline for simple objects
2787
+ // BUT: if there are ANY blank lines in the object (between props or at edges), always use multi-line
2788
+ if (isSimple && (isInArray || isInAttribute) && !hasAnyBlankLines) {
2789
+ if (isInArray) {
2790
+ if (isVerySimple) {
2791
+ // 1-property objects: force inline with spaces
2792
+ return concat([open_brace, ' ', properties[0], ' ', '}']);
2793
+ }
2794
+ // 2-property objects: let normal formatting handle it (will be multiline)
2795
+ // Fall through to default multiline formatting below
2796
+ } else {
2797
+ // For attributes, force inline without spaces
2798
+ const parts = [open_brace];
2799
+ for (let i = 0; i < properties.length; i++) {
2800
+ if (i > 0) parts.push(', ');
2801
+ parts.push(properties[i]);
2802
+ }
2803
+ parts.push('}');
2804
+ return concat(parts);
2805
+ }
2806
+ }
2807
+
2808
+ if (args && args.allowInlineObject) {
2809
+ const separator = concat([',', line]);
2810
+ const propertyDoc = join(separator, properties);
2811
+ const spacing = options.bracketSpacing === false ? softline : line;
2812
+ const trailingDoc = shouldUseTrailingComma ? ifBreak(',', '') : '';
2813
+
2814
+ return group(
2815
+ concat([open_brace, indent(concat([spacing, propertyDoc, trailingDoc])), spacing, '}']),
2816
+ );
2817
+ }
2818
+
2819
+ // For objects that were originally inline (single-line) and don't have blank lines,
2820
+ // and aren't in arrays, allow inline formatting if it fits printWidth
2821
+ // This handles cases like `const T0: t17 = { x: 1 };` staying inline when it fits
2822
+ // The group() will automatically break to multi-line if it doesn't fit
2823
+ if (!hasAnyBlankLines && !isOriginallyMultiLine && !isInArray) {
2824
+ const separator = concat([',', line]);
2825
+ const propertyDoc = join(separator, properties);
2826
+ const spacing = options.bracketSpacing === false ? softline : line;
2827
+ const trailingDoc = shouldUseTrailingComma ? ifBreak(',', '') : '';
2828
+
2829
+ return group(
2830
+ concat([open_brace, indent(concat([spacing, propertyDoc, trailingDoc])), spacing, '}']),
2831
+ );
2832
+ }
2833
+
2834
+ let content = [hardline];
2835
+ if (properties.length > 0) {
2836
+ // Build properties with blank line preservation
2837
+ const propertyParts = [];
2838
+ for (let i = 0; i < properties.length; i++) {
2839
+ if (i > 0) {
2840
+ propertyParts.push(',');
2841
+
2842
+ // Check for blank lines between properties and preserve them
2843
+ const prevProp = node.properties[i - 1];
2844
+ const currentProp = node.properties[i];
2845
+ if (prevProp && currentProp && getBlankLinesBetweenNodes(prevProp, currentProp) > 0) {
2846
+ propertyParts.push(hardline);
2847
+ propertyParts.push(hardline); // Two hardlines = blank line
2848
+ } else {
2849
+ propertyParts.push(hardline);
2850
+ }
2851
+ }
2852
+ propertyParts.push(properties[i]);
2853
+ }
2854
+
2855
+ content.push(concat(propertyParts));
2856
+ if (shouldUseTrailingComma) {
2857
+ content.push(',');
2858
+ }
2859
+ content.push(hardline);
2860
+ }
2861
+
2862
+ return group([open_brace, indent(content.slice(0, -1)), content[content.length - 1], '}']);
2863
+ }
2864
+
2865
+ function printClassDeclaration(node, path, options, print) {
2866
+ const parts = [];
2867
+ parts.push('class ');
2868
+ parts.push(node.id.name);
2869
+
2870
+ // Add TypeScript generics if present
2871
+ if (node.typeParameters) {
2872
+ const typeParams = path.call(print, 'typeParameters');
2873
+ if (Array.isArray(typeParams)) {
2874
+ parts.push(...typeParams);
2875
+ } else {
2876
+ parts.push(typeParams);
2877
+ }
2878
+ }
2879
+
2880
+ if (node.superClass) {
2881
+ parts.push(' extends ');
2882
+ parts.push(path.call(print, 'superClass'));
2883
+ }
2884
+
2885
+ parts.push(' ');
2886
+ parts.push(path.call(print, 'body'));
2887
+
2888
+ return parts;
2889
+ }
2890
+
2891
+ function printTryStatement(node, path, options, print) {
2892
+ const parts = [];
2893
+ parts.push('try ');
2894
+ parts.push(path.call(print, 'block'));
2895
+
2896
+ if (node.handler) {
2897
+ parts.push(' catch');
2898
+ if (node.handler.param) {
2899
+ parts.push(' (');
2900
+ parts.push(path.call(print, 'handler', 'param'));
2901
+ parts.push(')');
2902
+ }
2903
+ parts.push(' ');
2904
+ parts.push(path.call(print, 'handler', 'body'));
2905
+ }
2906
+
2907
+ if (node.finalizer) {
2908
+ parts.push(' finally ');
2909
+ parts.push(path.call(print, 'finalizer'));
2910
+ }
2911
+
2912
+ if (node.pending) {
2913
+ parts.push(' pending ');
2914
+ parts.push(path.call(print, 'pending'));
2915
+ }
2916
+
2917
+ return parts;
2918
+ }
2919
+
2920
+ function printClassBody(node, path, options, print) {
2921
+ if (!node.body || node.body.length === 0) {
2922
+ return '{}';
2923
+ }
2924
+
2925
+ const members = path.map(print, 'body');
2926
+
2927
+ // Use AST builders for proper formatting
2928
+ return group(['{', indent(concat([line, join(concat([line, line]), members)])), line, '}']);
2929
+ }
2930
+
2931
+ function printPropertyDefinition(node, path, options, print) {
2932
+ const parts = [];
2933
+
2934
+ // Access modifiers (public, private, protected)
2935
+ if (node.accessibility) {
2936
+ parts.push(node.accessibility);
2937
+ parts.push(' ');
2938
+ }
2939
+
2940
+ // Static keyword
2941
+ if (node.static) {
2942
+ parts.push('static ');
2943
+ }
2944
+
2945
+ // Readonly keyword
2946
+ if (node.readonly) {
2947
+ parts.push('readonly ');
2948
+ }
2949
+
2950
+ // Property name
2951
+ parts.push(path.call(print, 'key'));
2952
+
2953
+ // Optional marker
2954
+ if (node.optional) {
2955
+ parts.push('?');
2956
+ }
2957
+
2958
+ // Type annotation
2959
+ if (node.typeAnnotation) {
2960
+ parts.push(': ');
2961
+ parts.push(path.call(print, 'typeAnnotation'));
2962
+ }
2963
+
2964
+ // Initializer
2965
+ if (node.value) {
2966
+ parts.push(' = ');
2967
+ parts.push(path.call(print, 'value'));
2968
+ }
2969
+
2970
+ parts.push(semi(options));
2971
+
2972
+ return concat(parts);
2973
+ }
2974
+
2975
+ function printMethodDefinition(node, path, options, print) {
2976
+ const parts = [];
2977
+
2978
+ // Access modifiers (public, private, protected)
2979
+ if (node.accessibility) {
2980
+ parts.push(node.accessibility);
2981
+ parts.push(' ');
2982
+ }
2983
+
2984
+ // Static keyword
2985
+ if (node.static) {
2986
+ parts.push('static ');
2987
+ }
2988
+
2989
+ // Async keyword
2990
+ if (node.value && node.value.async) {
2991
+ parts.push('async ');
2992
+ }
2993
+
2994
+ // Method kind and name
2995
+ if (node.kind === 'constructor') {
2996
+ parts.push('constructor');
2997
+ } else if (node.kind === 'get') {
2998
+ parts.push('get ');
2999
+ parts.push(path.call(print, 'key'));
3000
+ } else if (node.kind === 'set') {
3001
+ parts.push('set ');
3002
+ parts.push(path.call(print, 'key'));
3003
+ } else {
3004
+ parts.push(path.call(print, 'key'));
3005
+ }
3006
+
3007
+ // Add TypeScript generics if present (always on the method node, not on value)
3008
+ if (node.typeParameters) {
3009
+ const typeParams = path.call(print, 'typeParameters');
3010
+ if (Array.isArray(typeParams)) {
3011
+ parts.push(...typeParams);
3012
+ } else {
3013
+ parts.push(typeParams);
3014
+ }
3015
+ }
3016
+
3017
+ // Parameters - use proper path.map for TypeScript support
3018
+ parts.push('(');
3019
+ if (node.value && node.value.params && node.value.params.length > 0) {
3020
+ const params = path.map(print, 'value', 'params');
3021
+ for (let i = 0; i < params.length; i++) {
3022
+ if (i > 0) parts.push(', ');
3023
+ parts.push(params[i]);
3024
+ }
3025
+ }
3026
+ parts.push(')');
3027
+
3028
+ // Return type
3029
+ if (node.value && node.value.returnType) {
3030
+ parts.push(': ', path.call(print, 'value', 'returnType'));
3031
+ }
3032
+
3033
+ // Method body
3034
+ parts.push(' ');
3035
+ if (node.value && node.value.body) {
3036
+ parts.push(path.call(print, 'value', 'body'));
3037
+ } else {
3038
+ parts.push('{}');
3039
+ }
3040
+
3041
+ return concat(parts);
3042
+ }
3043
+
3044
+ function printMemberExpression(node, path, options, print) {
3045
+ let objectPart = path.call(print, 'object');
3046
+ // Preserve parentheses around the object when present
3047
+ if (node.object.metadata?.parenthesized) {
3048
+ objectPart = concat(['(', objectPart, ')']);
3049
+ }
3050
+ const propertyPart = path.call(print, 'property');
3051
+
3052
+ let result;
3053
+ if (node.computed) {
3054
+ const openBracket = node.optional ? '?.[' : '[';
3055
+ result = concat([objectPart, openBracket, propertyPart, ']']);
3056
+ } else {
3057
+ const separator = node.optional ? '?.' : '.';
3058
+ result = concat([objectPart, separator, propertyPart]);
3059
+ }
3060
+
3061
+ // Preserve parentheses around the entire member expression when present
3062
+ if (node.metadata?.parenthesized) {
3063
+ // Check if there are leading comments - if so, use group with softlines to allow breaking
3064
+ const hasLeadingComments = node.leadingComments && node.leadingComments.length > 0;
3065
+ if (hasLeadingComments) {
3066
+ result = group(concat(['(', indent(concat([softline, result])), softline, ')']));
3067
+ } else {
3068
+ result = concat(['(', result, ')']);
3069
+ }
3070
+ }
3071
+
3072
+ return result;
3073
+ }
3074
+
3075
+ function printUnaryExpression(node, path, options, print) {
3076
+ const parts = [];
3077
+
3078
+ if (node.prefix) {
3079
+ parts.push(node.operator);
3080
+ // Add space for word operators like 'void', 'typeof', 'delete'
3081
+ const needsSpace = /^[a-z]/.test(node.operator);
3082
+ if (needsSpace) {
3083
+ parts.push(' ');
3084
+ }
3085
+ parts.push(path.call(print, 'argument'));
3086
+ } else {
3087
+ parts.push(path.call(print, 'argument'));
3088
+ parts.push(node.operator);
3089
+ }
3090
+
3091
+ return concat(parts);
3092
+ }
3093
+
3094
+ function printYieldExpression(node, path, options, print) {
3095
+ const parts = [];
3096
+ parts.push('yield');
3097
+
3098
+ if (node.delegate) {
3099
+ parts.push('*');
3100
+ }
3101
+
3102
+ if (node.argument) {
3103
+ parts.push(' ');
3104
+ parts.push(path.call(print, 'argument'));
3105
+ }
3106
+
3107
+ return parts;
3108
+ }
3109
+
3110
+ function printNewExpression(node, path, options, print) {
3111
+ const parts = [];
3112
+ parts.push('new ');
3113
+ parts.push(path.call(print, 'callee'));
3114
+
3115
+ // Handle TypeScript type parameters/arguments
3116
+ if (node.typeArguments) {
3117
+ parts.push(path.call(print, 'typeArguments'));
3118
+ } else if (node.typeParameters) {
3119
+ parts.push(path.call(print, 'typeParameters'));
3120
+ }
3121
+
3122
+ if (node.arguments && node.arguments.length > 0) {
3123
+ parts.push('(');
3124
+ const argList = path.map(print, 'arguments');
3125
+ for (let i = 0; i < argList.length; i++) {
3126
+ if (i > 0) parts.push(', ');
3127
+ parts.push(argList[i]);
3128
+ }
3129
+ parts.push(')');
3130
+ } else {
3131
+ parts.push('()');
3132
+ }
3133
+
3134
+ return parts;
3135
+ }
3136
+
3137
+ function printTemplateLiteral(node, path, options, print) {
3138
+ const parts = [];
3139
+ parts.push('`');
3140
+
3141
+ for (let i = 0; i < node.expressions.length; i++) {
3142
+ parts.push(node.quasis[i].value.raw);
3143
+
3144
+ const expression = node.expressions[i];
3145
+ const expressionDoc = path.call(print, 'expressions', i);
3146
+
3147
+ // Check if the expression will break (e.g., ternary, binary, logical)
3148
+ const needsBreaking =
3149
+ expression.type === 'ConditionalExpression' ||
3150
+ expression.type === 'BinaryExpression' ||
3151
+ expression.type === 'LogicalExpression' ||
3152
+ willBreak(expressionDoc);
3153
+
3154
+ if (needsBreaking) {
3155
+ // For expressions that break, use group with indent to format nicely
3156
+ parts.push(group(concat(['${', indent(concat([softline, expressionDoc])), softline, '}'])));
3157
+ } else {
3158
+ // For simple expressions, keep them inline
3159
+ parts.push('${');
3160
+ parts.push(expressionDoc);
3161
+ parts.push('}');
3162
+ }
3163
+ }
3164
+
3165
+ // Add the final quasi (text after the last expression)
3166
+ if (node.quasis.length > node.expressions.length) {
3167
+ parts.push(node.quasis[node.quasis.length - 1].value.raw);
3168
+ }
3169
+
3170
+ parts.push('`');
3171
+ return parts;
3172
+ }
3173
+
3174
+ function printTaggedTemplateExpression(node, path, options, print) {
3175
+ const parts = [];
3176
+ parts.push(path.call(print, 'tag'));
3177
+ parts.push(path.call(print, 'quasi'));
3178
+ return parts;
3179
+ }
3180
+
3181
+ function printThrowStatement(node, path, options, print) {
3182
+ const parts = [];
3183
+ parts.push('throw ');
3184
+ parts.push(path.call(print, 'argument'));
3185
+ parts.push(semi(options));
3186
+ return parts;
3187
+ }
3188
+
3189
+ function printTSInterfaceDeclaration(node, path, options, print) {
3190
+ const parts = [];
3191
+ parts.push('interface ');
3192
+ parts.push(node.id.name);
3193
+
3194
+ if (node.typeParameters) {
3195
+ parts.push(path.call(print, 'typeParameters'));
3196
+ }
3197
+
3198
+ // Handle extends clause
3199
+ if (node.extends && node.extends.length > 0) {
3200
+ parts.push(' extends ');
3201
+ const extendsTypes = path.map(print, 'extends');
3202
+ parts.push(join(', ', extendsTypes));
3203
+ }
3204
+
3205
+ parts.push(' ');
3206
+ parts.push(path.call(print, 'body'));
3207
+
3208
+ return concat(parts);
3209
+ }
3210
+
3211
+ function printTSInterfaceBody(node, path, options, print) {
3212
+ if (!node.body || node.body.length === 0) {
3213
+ return '{}';
3214
+ }
3215
+
3216
+ const members = path.map(print, 'body');
3217
+
3218
+ // Add semicolons to all members
3219
+ const membersWithSemicolons = members.map((member) => concat([member, semi(options)]));
3220
+
3221
+ return group(['{', indent([hardline, join(hardline, membersWithSemicolons)]), hardline, '}']);
3222
+ }
3223
+
3224
+ function printTSTypeAliasDeclaration(node, path, options, print) {
3225
+ const parts = [];
3226
+ parts.push('type ');
3227
+ parts.push(node.id.name);
3228
+
3229
+ if (node.typeParameters) {
3230
+ parts.push(path.call(print, 'typeParameters'));
3231
+ }
3232
+
3233
+ parts.push(' = ');
3234
+ parts.push(path.call(print, 'typeAnnotation'));
3235
+ parts.push(semi(options));
3236
+
3237
+ return parts;
3238
+ }
3239
+
3240
+ function printTSEnumDeclaration(node, path, options, print) {
3241
+ const parts = [];
3242
+
3243
+ // Handle 'const enum' vs 'enum'
3244
+ if (node.const) {
3245
+ parts.push('const ');
3246
+ }
3247
+
3248
+ parts.push('enum ');
3249
+ parts.push(node.id.name);
3250
+ parts.push(' ');
3251
+
3252
+ // Print enum body
3253
+ if (!node.members || node.members.length === 0) {
3254
+ parts.push('{}');
3255
+ } else {
3256
+ const members = path.map(print, 'members');
3257
+ const membersWithCommas = [];
3258
+
3259
+ for (let i = 0; i < members.length; i++) {
3260
+ membersWithCommas.push(members[i]);
3261
+ if (i < members.length - 1) {
3262
+ membersWithCommas.push(',');
3263
+ membersWithCommas.push(hardline);
3264
+ }
3265
+ }
3266
+
3267
+ parts.push(
3268
+ group([
3269
+ '{',
3270
+ indent([hardline, concat(membersWithCommas)]),
3271
+ options.trailingComma !== 'none' ? ',' : '',
3272
+ hardline,
3273
+ '}',
3274
+ ]),
3275
+ );
3276
+ }
3277
+
3278
+ return concat(parts);
3279
+ }
3280
+
3281
+ function printTSEnumMember(node, path, options, print) {
3282
+ const parts = [];
3283
+
3284
+ // Print the key (id)
3285
+ if (node.id.type === 'Identifier') {
3286
+ parts.push(node.id.name);
3287
+ } else {
3288
+ // Handle computed or string literal keys
3289
+ parts.push(path.call(print, 'id'));
3290
+ }
3291
+
3292
+ // Print the initializer if present
3293
+ if (node.initializer) {
3294
+ parts.push(' = ');
3295
+ parts.push(path.call(print, 'initializer'));
3296
+ }
3297
+
3298
+ return concat(parts);
3299
+ }
3300
+
3301
+ function printTSTypeParameterDeclaration(node, path, options, print) {
3302
+ if (!node.params || node.params.length === 0) {
3303
+ return '';
3304
+ }
3305
+
3306
+ const parts = [];
3307
+ parts.push('<');
3308
+ const paramList = path.map(print, 'params');
3309
+ for (let i = 0; i < paramList.length; i++) {
3310
+ if (i > 0) parts.push(', ');
3311
+ parts.push(paramList[i]);
3312
+ }
3313
+ parts.push('>');
3314
+ return parts;
3315
+ }
3316
+
3317
+ function printTSTypeParameter(node, path, options, print) {
3318
+ const parts = [];
3319
+ parts.push(node.name);
3320
+
3321
+ if (node.constraint) {
3322
+ parts.push(' extends ');
3323
+ parts.push(path.call(print, 'constraint'));
3324
+ }
3325
+
3326
+ if (node.default) {
3327
+ parts.push(' = ');
3328
+ parts.push(path.call(print, 'default'));
3329
+ }
3330
+
3331
+ return parts;
3332
+ }
3333
+
3334
+ function printTSTypeParameterInstantiation(node, path, options, print) {
3335
+ if (!node.params || node.params.length === 0) {
3336
+ return '';
3337
+ }
3338
+
3339
+ const parts = [];
3340
+ parts.push('<');
3341
+ const paramList = path.map(print, 'params');
3342
+ for (let i = 0; i < paramList.length; i++) {
3343
+ if (i > 0) parts.push(', ');
3344
+ parts.push(paramList[i]);
3345
+ }
3346
+ parts.push('>');
3347
+ return concat(parts);
3348
+ }
3349
+
3350
+ function printSwitchStatement(node, path, options, print) {
3351
+ const discriminantDoc = group(
3352
+ concat(['switch (', indent([softline, path.call(print, 'discriminant')]), softline, ')']),
3353
+ );
3354
+
3355
+ const cases = [];
3356
+ for (let i = 0; i < node.cases.length; i++) {
3357
+ const caseDoc = [path.call(print, 'cases', i)];
3358
+ if (i < node.cases.length - 1 && isNextLineEmpty(node.cases[i], options)) {
3359
+ caseDoc.push(hardline);
3360
+ }
3361
+ cases.push(concat(caseDoc));
3362
+ }
3363
+
3364
+ const bodyDoc =
3365
+ cases.length > 0 ? concat([indent([hardline, join(hardline, cases)]), hardline]) : hardline;
3366
+
3367
+ return concat([discriminantDoc, ' {', bodyDoc, '}']);
3368
+ }
3369
+
3370
+ function printSwitchCase(node, path, options, print) {
3371
+ const header = node.test ? concat(['case ', path.call(print, 'test'), ':']) : 'default:';
3372
+
3373
+ const consequents = node.consequent || [];
3374
+ const printedConsequents = [];
3375
+ const referencedConsequents = [];
3376
+
3377
+ for (let i = 0; i < consequents.length; i++) {
3378
+ const child = consequents[i];
3379
+ if (!child || child.type === 'EmptyStatement') {
3380
+ continue;
3381
+ }
3382
+ referencedConsequents.push(child);
3383
+ printedConsequents.push(path.call(print, 'consequent', i));
3384
+ }
3385
+
3386
+ let bodyDoc = null;
3387
+ if (printedConsequents.length > 0) {
3388
+ const singleBlock =
3389
+ printedConsequents.length === 1 && referencedConsequents[0].type === 'BlockStatement';
3390
+ if (singleBlock) {
3391
+ bodyDoc = concat([' ', printedConsequents[0]]);
3392
+ } else {
3393
+ bodyDoc = indent([hardline, join(hardline, printedConsequents)]);
3394
+ }
3395
+ }
3396
+
3397
+ let trailingDoc = null;
3398
+ if (node.trailingComments && node.trailingComments.length > 0) {
3399
+ const commentDocs = [];
3400
+ let previousNode =
3401
+ referencedConsequents.length > 0
3402
+ ? referencedConsequents[referencedConsequents.length - 1]
3403
+ : node;
3404
+
3405
+ for (let i = 0; i < node.trailingComments.length; i++) {
3406
+ const comment = node.trailingComments[i];
3407
+ const blankLines = previousNode ? getBlankLinesBetweenNodes(previousNode, comment) : 0;
3408
+ commentDocs.push(hardline);
3409
+ for (let j = 0; j < blankLines; j++) {
3410
+ commentDocs.push(hardline);
3411
+ }
3412
+ const commentDoc =
3413
+ comment.type === 'Line'
3414
+ ? concat(['//', comment.value])
3415
+ : concat(['/*', comment.value, '*/']);
3416
+ commentDocs.push(commentDoc);
3417
+ previousNode = comment;
3418
+ }
3419
+
3420
+ trailingDoc = concat(commentDocs);
3421
+ delete node.trailingComments;
3422
+ }
3423
+
3424
+ const parts = [header];
3425
+ if (bodyDoc) {
3426
+ parts.push(bodyDoc);
3427
+ }
3428
+ if (trailingDoc) {
3429
+ parts.push(trailingDoc);
3430
+ }
3431
+
3432
+ return concat(parts);
3433
+ }
3434
+
3435
+ function printBreakStatement(node, path, options, print) {
3436
+ const parts = [];
3437
+ parts.push('break');
3438
+ if (node.label) {
3439
+ parts.push(' ');
3440
+ parts.push(path.call(print, 'label'));
3441
+ }
3442
+ parts.push(semi(options));
3443
+ return parts;
3444
+ }
3445
+
3446
+ function printContinueStatement(node, path, options, print) {
3447
+ const parts = [];
3448
+ parts.push('continue');
3449
+ if (node.label) {
3450
+ parts.push(' ');
3451
+ parts.push(path.call(print, 'label'));
3452
+ }
3453
+ parts.push(semi(options));
3454
+ return parts;
3455
+ }
3456
+
3457
+ function printDebuggerStatement(node, path, options, print) {
3458
+ return 'debugger' + semi(options);
3459
+ }
3460
+
3461
+ function printSequenceExpression(node, path, options, print) {
3462
+ const parts = [];
3463
+ parts.push('(');
3464
+ const exprList = path.map(print, 'expressions');
3465
+ for (let i = 0; i < exprList.length; i++) {
3466
+ if (i > 0) parts.push(', ');
3467
+ parts.push(exprList[i]);
3468
+ }
3469
+ parts.push(')');
3470
+ return parts;
3471
+ }
3472
+
3473
+ function getBlankLinesBetweenPositions(current_pos, next_pos) {
3474
+ const line_gap = next_pos.line - current_pos.line;
3475
+
3476
+ // lineGap = 1 means adjacent lines (no blank lines)
3477
+ // lineGap = 2 means one blank line between them
3478
+ // lineGap = 3 means two blank lines between them, etc.
3479
+ return Math.max(0, line_gap - 1);
3480
+ }
3481
+
3482
+ function getBlankLinesBetweenNodes(currentNode, nextNode) {
3483
+ // Return the number of blank lines between two nodes based on their location
3484
+ if (
3485
+ currentNode.loc &&
3486
+ nextNode?.loc &&
3487
+ typeof currentNode.loc.end?.line === 'number' &&
3488
+ typeof nextNode.loc.start?.line === 'number'
3489
+ ) {
3490
+ return getBlankLinesBetweenPositions(currentNode.loc.end, nextNode.loc.start);
3491
+ }
3492
+
3493
+ // If no location info, assume no whitespace
3494
+ return 0;
3495
+ }
3496
+
3497
+ function shouldAddBlankLine(currentNode, nextNode) {
3498
+ // Simplified blank line logic:
3499
+ // 1. Check if there was originally 1+ blank lines between nodes
3500
+ // 2. If yes, preserve exactly 1 blank line (collapse multiple to one)
3501
+ // 3. Only exception: add blank line after imports when followed by non-imports
3502
+ // (this is standard Prettier behavior)
3503
+
3504
+ // Determine the source node for whitespace checking
3505
+ // If currentNode has trailing comments, use the last one
3506
+ let sourceNode = currentNode;
3507
+ if (currentNode.trailingComments && currentNode.trailingComments.length > 0) {
3508
+ sourceNode = currentNode.trailingComments[currentNode.trailingComments.length - 1];
3509
+ }
3510
+
3511
+ // If nextNode has leading comments, check whitespace between source node and first comment
3512
+ // Otherwise check whitespace between source node and next node
3513
+ let targetNode = nextNode;
3514
+ if (nextNode.leadingComments && nextNode.leadingComments.length > 0) {
3515
+ targetNode = nextNode.leadingComments[0];
3516
+ }
3517
+
3518
+ // Check if there was original whitespace between the nodes
3519
+ const originalBlankLines = getBlankLinesBetweenNodes(sourceNode, targetNode);
3520
+
3521
+ // Special case: Always add blank line after import declarations when followed by non-imports
3522
+ // This is standard Prettier behavior for separating imports from code
3523
+ if (currentNode.type === 'ImportDeclaration' && nextNode.type !== 'ImportDeclaration') {
3524
+ return true;
3525
+ }
3526
+
3527
+ // Default behavior: preserve blank line if one or more existed originally
3528
+ return originalBlankLines > 0;
3529
+ }
3530
+
3531
+ function printObjectPattern(node, path, options, print) {
3532
+ const propList = path.map(print, 'properties');
3533
+ if (propList.length === 0) {
3534
+ if (node.typeAnnotation) {
3535
+ return concat(['{}', ': ', path.call(print, 'typeAnnotation')]);
3536
+ }
3537
+ return '{}';
3538
+ }
3539
+
3540
+ const allowTrailingComma =
3541
+ node.properties &&
3542
+ node.properties.length > 0 &&
3543
+ node.properties[node.properties.length - 1].type !== 'RestElement';
3544
+
3545
+ const trailingCommaDoc =
3546
+ allowTrailingComma && options.trailingComma !== 'none' ? ifBreak(',', '') : '';
3547
+
3548
+ // When the pattern has a type annotation, we need to format them together
3549
+ // so they break at the same time
3550
+ if (node.typeAnnotation) {
3551
+ const typeAnn = node.typeAnnotation.typeAnnotation;
3552
+
3553
+ // If it's a TSTypeLiteral, format both object and type
3554
+ if (typeAnn && typeAnn.type === 'TSTypeLiteral') {
3555
+ const typeMembers = path.call(
3556
+ (path) => path.map(print, 'members'),
3557
+ 'typeAnnotation',
3558
+ 'typeAnnotation',
3559
+ );
3560
+
3561
+ // Use softline for proper spacing - will become space when inline, line when breaking
3562
+ // Format type members with semicolons between AND after the last member
3563
+ const typeMemberDocs = join(concat([';', line]), typeMembers);
3564
+
3565
+ // Don't wrap in group - let the outer params group control breaking
3566
+ const objectDoc = concat([
3567
+ '{',
3568
+ indent(concat([line, join(concat([',', line]), propList), trailingCommaDoc])),
3569
+ line,
3570
+ '}',
3571
+ ]);
3572
+ const typeDoc =
3573
+ typeMembers.length === 0
3574
+ ? '{}'
3575
+ : concat(['{', indent(concat([line, typeMemberDocs, ifBreak(';', '')])), line, '}']);
3576
+
3577
+ // Return combined
3578
+ return concat([objectDoc, ': ', typeDoc]);
3579
+ }
3580
+
3581
+ // For other type annotations, just concatenate
3582
+ const objectContent = group(
3583
+ concat([
3584
+ '{',
3585
+ indent(concat([line, join(concat([',', line]), propList), trailingCommaDoc])),
3586
+ line,
3587
+ '}',
3588
+ ]),
3589
+ );
3590
+ return concat([objectContent, ': ', path.call(print, 'typeAnnotation')]);
3591
+ }
3592
+
3593
+ // No type annotation - just format the object pattern
3594
+ const objectContent = group(
3595
+ concat([
3596
+ '{',
3597
+ indent(concat([line, join(concat([',', line]), propList), trailingCommaDoc])),
3598
+ line,
3599
+ '}',
3600
+ ]),
3601
+ );
3602
+
3603
+ return objectContent;
3604
+ }
3605
+
3606
+ function printArrayPattern(node, path, options, print) {
3607
+ const parts = [];
3608
+ parts.push('[');
3609
+ const elementList = path.map(print, 'elements');
3610
+ for (let i = 0; i < elementList.length; i++) {
3611
+ if (i > 0) parts.push(', ');
3612
+ parts.push(elementList[i]);
3613
+ }
3614
+ parts.push(']');
3615
+
3616
+ if (node.typeAnnotation) {
3617
+ parts.push(': ');
3618
+ parts.push(path.call(print, 'typeAnnotation'));
3619
+ }
3620
+
3621
+ return concat(parts);
3622
+ }
3623
+
3624
+ function printProperty(node, path, options, print) {
3625
+ if (node.shorthand) {
3626
+ // For shorthand properties, if value is AssignmentPattern, print the value (which includes the default)
3627
+ // Otherwise just print the key
3628
+ if (node.value.type === 'AssignmentPattern') {
3629
+ return path.call(print, 'value');
3630
+ }
3631
+ return path.call(print, 'key');
3632
+ }
3633
+
3634
+ const parts = [];
3635
+
3636
+ // Handle method shorthand: increment() {} instead of increment: function() {}
3637
+ if (node.method && node.value.type === 'FunctionExpression') {
3638
+ const methodParts = [];
3639
+ const funcValue = node.value;
3640
+
3641
+ // Handle async and generator
3642
+ if (funcValue.async) {
3643
+ methodParts.push('async ');
3644
+ }
3645
+
3646
+ // Print key (with computed property brackets if needed)
3647
+ if (node.computed) {
3648
+ methodParts.push('[', path.call(print, 'key'), ']');
3649
+ } else if (node.key.type === 'Literal' && typeof node.key.value === 'string') {
3650
+ // Check if the key is a valid identifier that doesn't need quotes
3651
+ const key = node.key.value;
3652
+ const isValidIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key);
3653
+ if (isValidIdentifier) {
3654
+ methodParts.push(key);
3655
+ } else {
3656
+ methodParts.push(formatStringLiteral(key, options));
3657
+ }
3658
+ } else {
3659
+ methodParts.push(path.call(print, 'key'));
3660
+ }
3661
+
3662
+ if (funcValue.generator) {
3663
+ methodParts.push('*');
3664
+ }
3665
+
3666
+ // Print parameters by calling into the value path
3667
+ const paramsPart = path.call(
3668
+ (valuePath) => printFunctionParameters(valuePath, options, print),
3669
+ 'value',
3670
+ );
3671
+ methodParts.push(group(paramsPart));
3672
+
3673
+ // Handle return type annotation
3674
+ if (funcValue.returnType) {
3675
+ methodParts.push(': ', path.call(print, 'value', 'returnType'));
3676
+ }
3677
+
3678
+ methodParts.push(' ', path.call(print, 'value', 'body'));
3679
+ return concat(methodParts);
3680
+ }
3681
+
3682
+ // Handle property key - if it's a Literal (quoted string in source),
3683
+ // check if it needs quotes or can be unquoted
3684
+ if (node.computed) {
3685
+ // Computed property: [key]
3686
+ parts.push('[', path.call(print, 'key'), ']');
3687
+ } else if (node.key.type === 'Literal' && typeof node.key.value === 'string') {
3688
+ // Check if the key is a valid identifier that doesn't need quotes
3689
+ const key = node.key.value;
3690
+ const isValidIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key);
3691
+
3692
+ if (isValidIdentifier) {
3693
+ // Don't quote valid identifiers
3694
+ parts.push(key);
3695
+ } else {
3696
+ // Quote keys that need it (e.g., contain special characters)
3697
+ parts.push(formatStringLiteral(key, options));
3698
+ }
3699
+ } else {
3700
+ // For non-literal keys, print normally
3701
+ parts.push(path.call(print, 'key'));
3702
+ }
3703
+
3704
+ parts.push(': ');
3705
+ parts.push(path.call(print, 'value'));
3706
+ return concat(parts);
3707
+ }
3708
+
3709
+ function printVariableDeclarator(node, path, options, print) {
3710
+ if (node.init) {
3711
+ const id = path.call(print, 'id');
3712
+ const init = path.call(print, 'init');
3713
+
3714
+ // For conditional expressions that will break, put them on a new line
3715
+ const isTernary = node.init.type === 'ConditionalExpression';
3716
+ if (isTernary) {
3717
+ // Check if the ternary will break by checking if it has complex branches
3718
+ // or if the doc builder indicates it will break
3719
+ const ternaryWillBreak = willBreak(init);
3720
+
3721
+ // Also check if either branch is a CallExpression (which typically breaks)
3722
+ const hasComplexBranch =
3723
+ node.init.consequent.type === 'CallExpression' ||
3724
+ node.init.alternate.type === 'CallExpression';
3725
+
3726
+ // Check if test is a LogicalExpression or BinaryExpression with complex operators
3727
+ const hasComplexTest =
3728
+ node.init.test.type === 'LogicalExpression' || node.init.test.type === 'BinaryExpression';
3729
+
3730
+ // Check if there are nested ternaries
3731
+ const hasNestedTernary =
3732
+ node.init.consequent.type === 'ConditionalExpression' ||
3733
+ node.init.alternate.type === 'ConditionalExpression';
3734
+
3735
+ if (ternaryWillBreak || hasComplexBranch || hasComplexTest || hasNestedTernary) {
3736
+ return concat([id, ' =', indent(concat([line, init]))]);
3737
+ }
3738
+ }
3739
+
3740
+ // For arrays/objects with blank lines, use conditionalGroup to try both layouts
3741
+ // Prettier will break the declaration if keeping it inline doesn't fit
3742
+ const isArray =
3743
+ node.init.type === 'ArrayExpression' || node.init.type === 'TrackedArrayExpression';
3744
+ const isObject =
3745
+ node.init.type === 'ObjectExpression' || node.init.type === 'TrackedObjectExpression';
3746
+
3747
+ if (isArray || isObject) {
3748
+ const items = isArray ? node.init.elements || [] : node.init.properties || [];
3749
+ let hasBlankLines = false;
3750
+
3751
+ if (isArray) {
3752
+ for (let i = 1; i < items.length; i++) {
3753
+ const prevElement = items[i - 1];
3754
+ const currentElement = items[i];
3755
+ if (
3756
+ prevElement &&
3757
+ currentElement &&
3758
+ getBlankLinesBetweenNodes(prevElement, currentElement) > 0
3759
+ ) {
3760
+ hasBlankLines = true;
3761
+ break;
3762
+ }
3763
+ }
3764
+ } else {
3765
+ for (let i = 0; i < items.length - 1; i++) {
3766
+ const current = items[i];
3767
+ const next = items[i + 1];
3768
+ if (current && next && getBlankLinesBetweenNodes(current, next) > 0) {
3769
+ hasBlankLines = true;
3770
+ break;
3771
+ }
3772
+ }
3773
+ }
3774
+
3775
+ if (hasBlankLines) {
3776
+ // Provide two alternatives: inline vs broken
3777
+ // Prettier picks the broken version if inline doesn't fit
3778
+ return conditionalGroup([
3779
+ // Try inline first
3780
+ concat([id, ' = ', init]),
3781
+ // Fall back to broken with extra indent
3782
+ concat([id, ' =', indent(concat([line, init]))]),
3783
+ ]);
3784
+ }
3785
+ }
3786
+
3787
+ // For BinaryExpression or LogicalExpression, use break-after-operator layout
3788
+ // This allows the expression to break naturally based on print width
3789
+ const isBinaryish =
3790
+ node.init.type === 'BinaryExpression' || node.init.type === 'LogicalExpression';
3791
+ if (isBinaryish) {
3792
+ // Use Prettier's break-after-operator strategy: break after = and let the expression break naturally
3793
+ const init = path.call(print, 'init');
3794
+ return group([group(id), ' =', group(indent(concat([line, init])))]);
3795
+ }
3796
+ // For CallExpression inits, use fluid layout strategy to break after = if needed
3797
+ const isCallExpression = node.init.type === 'CallExpression';
3798
+ if (isCallExpression) {
3799
+ // Always use fluid layout for call expressions
3800
+ // This allows breaking after = when the whole line doesn't fit
3801
+ {
3802
+ // Use fluid layout: break right side first, then break after = if needed
3803
+ const groupId = Symbol('declaration');
3804
+ return group([
3805
+ group(id),
3806
+ ' =',
3807
+ group(indent(line), { id: groupId }),
3808
+ indentIfBreak(init, { groupId }),
3809
+ ]);
3810
+ }
3811
+ }
3812
+
3813
+ // Default: simple inline format with space
3814
+ // Use group to allow breaking if needed - but keep inline when it fits
3815
+ return group(concat([id, ' = ', init]));
3816
+ }
3817
+
3818
+ return path.call(print, 'id');
3819
+ }
3820
+
3821
+ function printAssignmentPattern(node, path, options, print) {
3822
+ // Handle default parameters like: count: number = 0
3823
+ return concat([path.call(print, 'left'), ' = ', path.call(print, 'right')]);
3824
+ }
3825
+
3826
+ function printTSTypeLiteral(node, path, options, print) {
3827
+ if (!node.members || node.members.length === 0) {
3828
+ return '{}';
3829
+ }
3830
+
3831
+ const members = path.map(print, 'members');
3832
+ const inlineMembers = members.map((member, index) =>
3833
+ index < members.length - 1 ? concat([member, ';']) : member,
3834
+ );
3835
+ const multilineMembers = members.map((member) => concat([member, ';']));
3836
+
3837
+ const inlineDoc = group(
3838
+ concat(['{', indent(concat([line, join(line, inlineMembers)])), line, '}']),
3839
+ );
3840
+
3841
+ const multilineDoc = group(
3842
+ concat(['{', indent(concat([hardline, join(hardline, multilineMembers)])), hardline, '}']),
3843
+ );
3844
+
3845
+ return conditionalGroup(
3846
+ wasOriginallySingleLine(node) ? [inlineDoc, multilineDoc] : [multilineDoc, inlineDoc],
3847
+ );
3848
+ }
3849
+
3850
+ function printTSPropertySignature(node, path, options, print) {
3851
+ const parts = [];
3852
+ parts.push(path.call(print, 'key'));
3853
+
3854
+ if (node.optional) {
3855
+ parts.push('?');
3856
+ }
3857
+
3858
+ if (node.typeAnnotation) {
3859
+ parts.push(': ');
3860
+ parts.push(path.call(print, 'typeAnnotation'));
3861
+ }
3862
+
3863
+ return concat(parts);
3864
+ }
3865
+
3866
+ function printTSTypeReference(node, path, options, print) {
3867
+ const parts = [path.call(print, 'typeName')];
3868
+
3869
+ // Handle both typeArguments and typeParameters (different AST variations)
3870
+ if (node.typeArguments) {
3871
+ parts.push('<');
3872
+ const typeArgs = path.map(print, 'typeArguments', 'params');
3873
+ for (let i = 0; i < typeArgs.length; i++) {
3874
+ if (i > 0) parts.push(', ');
3875
+ parts.push(typeArgs[i]);
3876
+ }
3877
+ parts.push('>');
3878
+ } else if (node.typeParameters) {
3879
+ parts.push('<');
3880
+ const typeParams = path.map(print, 'typeParameters', 'params');
3881
+ for (let i = 0; i < typeParams.length; i++) {
3882
+ if (i > 0) parts.push(', ');
3883
+ parts.push(typeParams[i]);
3884
+ }
3885
+ parts.push('>');
3886
+ }
3887
+
3888
+ return concat(parts);
3889
+ }
3890
+
3891
+ function printTSTupleType(node, path, options, print) {
3892
+ const parts = ['['];
3893
+ const elements = node.elementTypes ? path.map(print, 'elementTypes') : [];
3894
+ for (let i = 0; i < elements.length; i++) {
3895
+ if (i > 0) parts.push(', ');
3896
+ parts.push(elements[i]);
3897
+ }
3898
+ parts.push(']');
3899
+ return concat(parts);
3900
+ }
3901
+
3902
+ function printTSIndexSignature(node, path, options, print) {
3903
+ const parts = [];
3904
+ if (node.readonly === true || node.readonly === 'plus' || node.readonly === '+') {
3905
+ parts.push('readonly ');
3906
+ } else if (node.readonly === 'minus' || node.readonly === '-') {
3907
+ parts.push('-readonly ');
3908
+ }
3909
+
3910
+ parts.push('[');
3911
+ const params = node.parameters ? path.map(print, 'parameters') : [];
3912
+ for (let i = 0; i < params.length; i++) {
3913
+ if (i > 0) parts.push(', ');
3914
+ parts.push(params[i]);
3915
+ }
3916
+ parts.push(']');
3917
+
3918
+ if (node.typeAnnotation) {
3919
+ parts.push(': ');
3920
+ parts.push(path.call(print, 'typeAnnotation'));
3921
+ }
3922
+
3923
+ return concat(parts);
3924
+ }
3925
+
3926
+ function printTSConstructorType(node, path, options, print) {
3927
+ const parts = [];
3928
+ parts.push('new ');
3929
+ parts.push('(');
3930
+ const hasParams = Array.isArray(node.params) && node.params.length > 0;
3931
+ const hasParameters = Array.isArray(node.parameters) && node.parameters.length > 0;
3932
+ if (hasParams || hasParameters) {
3933
+ const params = hasParams ? path.map(print, 'params') : path.map(print, 'parameters');
3934
+ for (let i = 0; i < params.length; i++) {
3935
+ if (i > 0) parts.push(', ');
3936
+ parts.push(params[i]);
3937
+ }
3938
+ }
3939
+ parts.push(')');
3940
+ parts.push(' => ');
3941
+ if (node.returnType) {
3942
+ parts.push(path.call(print, 'returnType'));
3943
+ } else if (node.typeAnnotation) {
3944
+ parts.push(path.call(print, 'typeAnnotation'));
3945
+ }
3946
+ return concat(parts);
3947
+ }
3948
+
3949
+ function printTSConditionalType(node, path, options, print) {
3950
+ const parts = [];
3951
+ parts.push(path.call(print, 'checkType'));
3952
+ parts.push(' extends ');
3953
+ parts.push(path.call(print, 'extendsType'));
3954
+ parts.push(' ? ');
3955
+ parts.push(path.call(print, 'trueType'));
3956
+ parts.push(' : ');
3957
+ parts.push(path.call(print, 'falseType'));
3958
+ return concat(parts);
3959
+ }
3960
+
3961
+ function printTSMappedType(node, path, options, print) {
3962
+ const readonlyMod =
3963
+ node.readonly === true || node.readonly === 'plus' || node.readonly === '+'
3964
+ ? 'readonly '
3965
+ : node.readonly === 'minus' || node.readonly === '-'
3966
+ ? '-readonly '
3967
+ : '';
3968
+
3969
+ let optionalMod = '';
3970
+ if (node.optional === true || node.optional === 'plus' || node.optional === '+') {
3971
+ optionalMod = '?';
3972
+ } else if (node.optional === 'minus' || node.optional === '-') {
3973
+ optionalMod = '-?';
3974
+ }
3975
+
3976
+ const innerParts = [];
3977
+ const typeParam = node.typeParameter;
3978
+ innerParts.push('[');
3979
+ if (typeParam) {
3980
+ // name
3981
+ innerParts.push(typeParam.name);
3982
+ innerParts.push(' in ');
3983
+ if (typeParam.constraint) {
3984
+ innerParts.push(path.call(print, 'typeParameter', 'constraint'));
3985
+ } else {
3986
+ innerParts.push(path.call(print, 'typeParameter'));
3987
+ }
3988
+ if (node.nameType) {
3989
+ innerParts.push(' as ');
3990
+ innerParts.push(path.call(print, 'nameType'));
3991
+ }
3992
+ }
3993
+ innerParts.push(']');
3994
+ innerParts.push(optionalMod);
3995
+ if (node.typeAnnotation) {
3996
+ innerParts.push(': ');
3997
+ innerParts.push(path.call(print, 'typeAnnotation'));
3998
+ }
3999
+
4000
+ return group(['{ ', readonlyMod, concat(innerParts), ' }']);
4001
+ }
4002
+
4003
+ function printTSQualifiedName(node, path, options, print) {
4004
+ return concat([path.call(print, 'left'), '.', path.call(print, 'right')]);
4005
+ }
4006
+
4007
+ function printTSIndexedAccessType(node, path, options, print) {
4008
+ return concat([path.call(print, 'objectType'), '[', path.call(print, 'indexType'), ']']);
4009
+ }
4010
+
4011
+ function printStyleSheet(node, path, options, print) {
4012
+ // StyleSheet contains CSS rules in the 'body' property
4013
+ if (node.body && node.body.length > 0) {
4014
+ const cssItems = [];
4015
+
4016
+ // Process each item in the stylesheet body
4017
+ for (let i = 0; i < node.body.length; i++) {
4018
+ const item = path.call(print, 'body', i);
4019
+ if (item) {
4020
+ cssItems.push(item);
4021
+ }
4022
+ }
4023
+
4024
+ // Structure the CSS with proper indentation and spacing
4025
+ // Check for blank lines between CSS items and preserve them
4026
+ const result = [];
4027
+ for (let i = 0; i < cssItems.length; i++) {
4028
+ result.push(cssItems[i]);
4029
+ if (i < cssItems.length - 1) {
4030
+ // Check if there are blank lines between current and next item
4031
+ const currentItem = node.body[i];
4032
+ const nextItem = node.body[i + 1];
4033
+
4034
+ // Check for blank lines in the original CSS source between rules
4035
+ let hasBlankLine = false;
4036
+ if (
4037
+ node.source &&
4038
+ typeof currentItem.end === 'number' &&
4039
+ typeof nextItem.start === 'number'
4040
+ ) {
4041
+ const textBetween = node.source.substring(currentItem.end, nextItem.start);
4042
+ // Count newlines in the text between the rules
4043
+ const newlineCount = (textBetween.match(/\n/g) || []).length;
4044
+ // If there are 2 or more newlines, there's at least one blank line
4045
+ hasBlankLine = newlineCount >= 2;
4046
+ }
4047
+ if (hasBlankLine) {
4048
+ // If there are blank lines, add an extra hardline (to create a blank line)
4049
+ result.push(hardline, hardline);
4050
+ } else {
4051
+ result.push(hardline);
4052
+ }
4053
+ }
4054
+ }
4055
+
4056
+ return concat(result);
4057
+ }
4058
+
4059
+ // If no body, return empty string
4060
+ return '';
4061
+ }
4062
+
4063
+ function printCSSRule(node, path, options, print) {
4064
+ // CSS Rule has prelude (selector) and block (declarations)
4065
+ const selector = path.call(print, 'prelude');
4066
+ const block = path.call(print, 'block');
4067
+
4068
+ return group([selector, ' {', indent([hardline, block]), hardline, '}']);
4069
+ }
4070
+
4071
+ function printCSSDeclaration(node, path, options, print) {
4072
+ // CSS Declaration has property and value
4073
+ const parts = [node.property];
4074
+
4075
+ if (node.value) {
4076
+ parts.push(': ');
4077
+ const value = path.call(print, 'value');
4078
+ parts.push(value);
4079
+ }
4080
+
4081
+ parts.push(';');
4082
+ return concat(parts);
4083
+ }
4084
+
4085
+ function printCSSAtrule(node, path, options, print) {
4086
+ // CSS At-rule like @media, @keyframes, etc.
4087
+ const parts = ['@', node.name];
4088
+
4089
+ if (node.prelude) {
4090
+ parts.push(' ');
4091
+ const prelude = path.call(print, 'prelude');
4092
+ parts.push(prelude);
4093
+ }
4094
+
4095
+ if (node.block) {
4096
+ const block = path.call(print, 'block');
4097
+ parts.push(' {');
4098
+ parts.push(indent([hardline, block]));
4099
+ parts.push(hardline, '}');
4100
+ } else {
4101
+ parts.push(';');
4102
+ }
4103
+
4104
+ return group(parts);
4105
+ }
4106
+
4107
+ function printCSSSelectorList(node, path, options, print) {
4108
+ // SelectorList contains multiple selectors
4109
+ if (node.children && node.children.length > 0) {
4110
+ const selectors = [];
4111
+ for (let i = 0; i < node.children.length; i++) {
4112
+ const selector = path.call(print, 'children', i);
4113
+ selectors.push(selector);
4114
+ }
4115
+ // Join selectors with comma and line break for proper CSS formatting
4116
+ return join([',', hardline], selectors);
4117
+ }
4118
+ return '';
4119
+ }
4120
+
4121
+ function printCSSComplexSelector(node, path, options, print) {
4122
+ // ComplexSelector contains selector components
4123
+ if (node.children && node.children.length > 0) {
4124
+ const selectorParts = [];
4125
+ for (let i = 0; i < node.children.length; i++) {
4126
+ const part = path.call(print, 'children', i);
4127
+ selectorParts.push(part);
4128
+ }
4129
+ return concat(selectorParts);
4130
+ }
4131
+ return '';
4132
+ }
4133
+
4134
+ function printCSSRelativeSelector(node, path, options, print) {
4135
+ // RelativeSelector contains selector components in the 'selectors' property
4136
+ const parts = [];
4137
+
4138
+ // Print combinator if it exists (e.g., +, >, ~, or space)
4139
+ if (node.combinator) {
4140
+ if (node.combinator.name === ' ') {
4141
+ // Space combinator (descendant selector)
4142
+ parts.push(' ');
4143
+ } else {
4144
+ // Other combinators (+, >, ~)
4145
+ parts.push(' ', node.combinator.name, ' ');
4146
+ }
4147
+ }
4148
+
4149
+ if (node.selectors && node.selectors.length > 0) {
4150
+ const selectorParts = [];
4151
+ for (let i = 0; i < node.selectors.length; i++) {
4152
+ const part = path.call(print, 'selectors', i);
4153
+ selectorParts.push(part);
4154
+ }
4155
+ parts.push(...selectorParts);
4156
+ }
4157
+
4158
+ return concat(parts);
4159
+ }
4160
+
4161
+ function printCSSTypeSelector(node, path, options, print) {
4162
+ // TypeSelector for element names like 'div', 'body', 'p', etc.
4163
+ return node.name || '';
4164
+ }
4165
+
4166
+ function printCSSIdSelector(node, path, options, print) {
4167
+ // IdSelector for #id
4168
+ return concat(['#', node.name || '']);
4169
+ }
4170
+
4171
+ function printCSSClassSelector(node, path, options, print) {
4172
+ // ClassSelector for .class
4173
+ return concat(['.', node.name || '']);
4174
+ }
4175
+
4176
+ function printCSSNestingSelector(node, path, options, print) {
4177
+ // NestingSelector for & (parent reference in nested CSS)
4178
+ return '&';
4179
+ }
4180
+
4181
+ function printCSSBlock(node, path, options, print) {
4182
+ // CSS Block contains declarations
4183
+ if (node.children && node.children.length > 0) {
4184
+ const declarations = [];
4185
+ for (let i = 0; i < node.children.length; i++) {
4186
+ const decl = path.call(print, 'children', i);
4187
+ if (decl) {
4188
+ declarations.push(decl);
4189
+ }
4190
+ }
4191
+ return join(hardline, declarations);
4192
+ }
4193
+ return '';
4194
+ }
4195
+
4196
+ function shouldInlineSingleChild(parentNode, firstChild, childDoc) {
4197
+ if (!firstChild || childDoc == null) {
4198
+ return false;
4199
+ }
4200
+
4201
+ if (typeof childDoc === 'string') {
4202
+ return childDoc.length <= 20 && !childDoc.includes('\n');
4203
+ }
4204
+
4205
+ // Always inline simple text content and JSX expressions if they fit
4206
+ if (
4207
+ firstChild.type === 'Text' ||
4208
+ firstChild.type === 'Html' ||
4209
+ firstChild.type === 'JSXExpressionContainer'
4210
+ ) {
4211
+ return true;
4212
+ }
4213
+
4214
+ // Respect original formatting for elements: if parent was originally multi-line, keep it multi-line
4215
+ // This follows Prettier's philosophy for decorators and objects
4216
+ if (!wasOriginallySingleLine(parentNode)) {
4217
+ return false;
4218
+ }
4219
+
4220
+ if (
4221
+ (firstChild.type === 'Element' || firstChild.type === 'JSXElement') &&
4222
+ firstChild.selfClosing
4223
+ ) {
4224
+ return !parentNode.attributes || parentNode.attributes.length === 0;
4225
+ }
4226
+
4227
+ return false;
4228
+ }
4229
+
4230
+ function getElementLeadingComments(node) {
4231
+ const fromMetadata = node?.metadata?.elementLeadingComments;
4232
+ if (Array.isArray(fromMetadata)) {
4233
+ return fromMetadata;
4234
+ }
4235
+ return [];
4236
+ }
4237
+
4238
+ function createElementLevelCommentParts(comments) {
4239
+ if (!comments || comments.length === 0) {
4240
+ return [];
4241
+ }
4242
+
4243
+ const parts = [];
4244
+
4245
+ for (let i = 0; i < comments.length; i++) {
4246
+ const comment = comments[i];
4247
+ const nextComment = comments[i + 1];
4248
+
4249
+ if (comment.type === 'Line') {
4250
+ parts.push('//' + comment.value);
4251
+ parts.push(hardline);
4252
+ } else if (comment.type === 'Block') {
4253
+ parts.push('/*' + comment.value + '*/');
4254
+ parts.push(hardline);
4255
+ }
4256
+
4257
+ if (nextComment) {
4258
+ const blankLinesBetween = getBlankLinesBetweenNodes(comment, nextComment);
4259
+ if (blankLinesBetween > 0) {
4260
+ parts.push(hardline);
4261
+ }
4262
+ }
4263
+ }
4264
+
4265
+ return parts;
4266
+ }
4267
+
4268
+ function printTsxCompat(node, path, options, print) {
4269
+ const tagName = `<tsx:${node.kind}>`;
4270
+ const closingTagName = `</tsx:${node.kind}>`;
4271
+
4272
+ const hasChildren = Array.isArray(node.children) && node.children.length > 0;
4273
+
4274
+ if (!hasChildren) {
4275
+ return concat([tagName, closingTagName]);
4276
+ }
4277
+
4278
+ // Print JSXElement children - they remain as JSX
4279
+ // Filter out whitespace-only JSXText nodes
4280
+ const finalChildren = [];
4281
+
4282
+ for (let i = 0; i < node.children.length; i++) {
4283
+ const child = node.children[i];
4284
+
4285
+ // Skip whitespace-only JSXText nodes
4286
+ if (child.type === 'JSXText' && !child.value.trim()) {
4287
+ continue;
4288
+ }
4289
+
4290
+ const printedChild = path.call(print, 'children', i);
4291
+ finalChildren.push(printedChild);
4292
+
4293
+ if (i < node.children.length - 1) {
4294
+ // Only add hardline if the next child is not whitespace-only
4295
+ const nextChild = node.children[i + 1];
4296
+ if (nextChild && !(nextChild.type === 'JSXText' && !nextChild.value.trim())) {
4297
+ finalChildren.push(hardline);
4298
+ }
4299
+ }
4300
+ }
4301
+
4302
+ // Format the TsxCompat element
4303
+ const elementOutput = group([
4304
+ tagName,
4305
+ indent(concat([hardline, ...finalChildren])),
4306
+ hardline,
4307
+ closingTagName,
4308
+ ]);
4309
+
4310
+ return elementOutput;
4311
+ }
4312
+
4313
+ function printJSXElement(node, path, options, print) {
4314
+ // Get the tag name from the opening element
4315
+ const openingElement = node.openingElement;
4316
+ const closingElement = node.closingElement;
4317
+
4318
+ let tagName;
4319
+ if (openingElement.name.type === 'JSXIdentifier') {
4320
+ tagName = openingElement.name.name;
4321
+ } else if (openingElement.name.type === 'JSXMemberExpression') {
4322
+ // Handle Member expressions like React.Fragment
4323
+ tagName = printJSXMemberExpression(openingElement.name);
4324
+ } else {
4325
+ tagName = openingElement.name.name || 'Unknown';
4326
+ }
4327
+
4328
+ const isSelfClosing = openingElement.selfClosing;
4329
+ const hasAttributes = openingElement.attributes && openingElement.attributes.length > 0;
4330
+ const hasChildren = node.children && node.children.length > 0;
4331
+
4332
+ // Format attributes
4333
+ let attributesDoc = '';
4334
+ if (hasAttributes) {
4335
+ const attrs = openingElement.attributes.map((attr, i) => {
4336
+ if (attr.type === 'JSXAttribute') {
4337
+ return printJSXAttribute(attr, path, options, print, i);
4338
+ } else if (attr.type === 'JSXSpreadAttribute') {
4339
+ return concat([
4340
+ '{...',
4341
+ path.call(print, 'openingElement', 'attributes', i, 'argument'),
4342
+ '}',
4343
+ ]);
4344
+ }
4345
+ return '';
4346
+ });
4347
+ attributesDoc = concat([' ', join(' ', attrs)]);
4348
+ }
4349
+
4350
+ if (isSelfClosing) {
4351
+ return concat(['<', tagName, attributesDoc, ' />']);
4352
+ }
4353
+
4354
+ if (!hasChildren) {
4355
+ return concat(['<', tagName, attributesDoc, '></', tagName, '>']);
4356
+ }
4357
+
4358
+ // Format children - filter out empty text nodes
4359
+ const childrenDocs = [];
4360
+ for (let i = 0; i < node.children.length; i++) {
4361
+ const child = node.children[i];
4362
+
4363
+ if (child.type === 'JSXText') {
4364
+ // Handle JSX text nodes - only include if not just whitespace
4365
+ const text = child.value;
4366
+ if (text.trim()) {
4367
+ childrenDocs.push(text);
4368
+ }
4369
+ } else if (child.type === 'JSXExpressionContainer') {
4370
+ // Handle JSX expression containers
4371
+ childrenDocs.push(concat(['{', path.call(print, 'children', i, 'expression'), '}']));
4372
+ } else {
4373
+ // Handle nested JSX elements
4374
+ childrenDocs.push(path.call(print, 'children', i));
4375
+ }
4376
+ }
4377
+
4378
+ // Check if content can be inlined (single text node or single expression)
4379
+ if (childrenDocs.length === 1 && typeof childrenDocs[0] === 'string') {
4380
+ return concat(['<', tagName, attributesDoc, '>', childrenDocs[0], '</', tagName, '>']);
4381
+ }
4382
+
4383
+ // Multiple children or complex children - format with line breaks
4384
+ const formattedChildren = [];
4385
+ for (let i = 0; i < childrenDocs.length; i++) {
4386
+ formattedChildren.push(childrenDocs[i]);
4387
+ if (i < childrenDocs.length - 1) {
4388
+ formattedChildren.push(hardline);
4389
+ }
4390
+ }
4391
+
4392
+ // Build the final element
4393
+ return group([
4394
+ '<',
4395
+ tagName,
4396
+ attributesDoc,
4397
+ '>',
4398
+ indent(concat([hardline, ...formattedChildren])),
4399
+ hardline,
4400
+ '</',
4401
+ tagName,
4402
+ '>',
4403
+ ]);
4404
+ }
4405
+
4406
+ function printJSXFragment(node, path, options, print) {
4407
+ const hasChildren = node.children && node.children.length > 0;
4408
+
4409
+ if (!hasChildren) {
4410
+ return '<></>';
4411
+ }
4412
+
4413
+ // Format children - filter out empty text nodes
4414
+ const childrenDocs = [];
4415
+ for (let i = 0; i < node.children.length; i++) {
4416
+ const child = node.children[i];
4417
+
4418
+ if (child.type === 'JSXText') {
4419
+ // Handle JSX text nodes - trim whitespace and only include if not empty
4420
+ const text = child.value.trim();
4421
+ if (text) {
4422
+ childrenDocs.push(text);
4423
+ }
4424
+ } else if (child.type === 'JSXExpressionContainer') {
4425
+ // Handle JSX expression containers
4426
+ childrenDocs.push(concat(['{', path.call(print, 'children', i, 'expression'), '}']));
4427
+ } else {
4428
+ // Handle nested JSX elements and fragments
4429
+ childrenDocs.push(path.call(print, 'children', i));
4430
+ }
4431
+ }
4432
+
4433
+ // Check if content can be inlined (single text node or single expression)
4434
+ if (childrenDocs.length === 1 && typeof childrenDocs[0] === 'string') {
4435
+ return concat(['<>', childrenDocs[0], '</>']);
4436
+ }
4437
+
4438
+ // Multiple children or complex children - format with line breaks
4439
+ const formattedChildren = [];
4440
+ for (let i = 0; i < childrenDocs.length; i++) {
4441
+ formattedChildren.push(childrenDocs[i]);
4442
+ if (i < childrenDocs.length - 1) {
4443
+ formattedChildren.push(hardline);
4444
+ }
4445
+ }
4446
+
4447
+ // Build the final fragment
4448
+ return group(['<>', indent(concat([hardline, ...formattedChildren])), hardline, '</>']);
4449
+ }
4450
+
4451
+ function printJSXAttribute(attr, path, options, print, index) {
4452
+ const name = attr.name.name;
4453
+
4454
+ if (!attr.value) {
4455
+ return name;
4456
+ }
4457
+
4458
+ if (attr.value.type === 'Literal' || attr.value.type === 'StringLiteral') {
4459
+ const quote = options.jsxSingleQuote ? "'" : '"';
4460
+ return concat([name, '=', quote, attr.value.value, quote]);
4461
+ }
4462
+
4463
+ if (attr.value.type === 'JSXExpressionContainer') {
4464
+ // For JSXExpressionContainer, we need to access the expression inside
4465
+ // Use a simple approach since we don't have direct path access here
4466
+ const exprValue = attr.value.expression;
4467
+ let exprStr;
4468
+
4469
+ if (exprValue.type === 'Literal' || exprValue.type === 'StringLiteral') {
4470
+ exprStr = JSON.stringify(exprValue.value);
4471
+ } else if (exprValue.type === 'Identifier') {
4472
+ exprStr = (exprValue.tracked ? '@' : '') + exprValue.name;
4473
+ } else if (exprValue.type === 'MemberExpression') {
4474
+ exprStr = printMemberExpressionSimple(exprValue, options);
4475
+ } else {
4476
+ // For complex expressions, try to stringify
4477
+ exprStr = '...';
4478
+ }
4479
+
4480
+ return concat([name, '={', exprStr, '}']);
4481
+ }
4482
+
4483
+ return name;
4484
+ }
4485
+
4486
+ function printJSXMemberExpression(node) {
4487
+ if (node.type === 'JSXIdentifier') {
4488
+ return node.name;
4489
+ }
4490
+ if (node.type === 'JSXMemberExpression') {
4491
+ return printJSXMemberExpression(node.object) + '.' + printJSXMemberExpression(node.property);
4492
+ }
4493
+ return 'Unknown';
4494
+ }
4495
+
4496
+ function printMemberExpressionSimple(node, options, computed = false) {
4497
+ if (node.type === 'Identifier') {
4498
+ return node.name;
4499
+ }
4500
+
4501
+ if (node.type === 'MemberExpression') {
4502
+ const obj = printMemberExpressionSimple(node.object, options);
4503
+ const prop = node.computed
4504
+ ? (node.property.tracked ? '.@[' : '[') +
4505
+ printMemberExpressionSimple(node.property, options, node.computed) +
4506
+ ']'
4507
+ : (node.property.tracked ? '.@' : '.') +
4508
+ printMemberExpressionSimple(node.property, options, node.computed);
4509
+ return obj + prop;
4510
+ }
4511
+
4512
+ if (node.type === 'Literal') {
4513
+ return computed ? formatStringLiteral(node.value, options) : JSON.stringify(node.value);
4514
+ }
4515
+ return '';
4516
+ }
4517
+
4518
+ function printElement(node, path, options, print) {
4519
+ const tagName = (node.id.tracked ? '@' : '') + printMemberExpressionSimple(node.id, options);
4520
+
4521
+ const elementLeadingComments = getElementLeadingComments(node);
4522
+ const metadataCommentParts =
4523
+ elementLeadingComments.length > 0 ? createElementLevelCommentParts(elementLeadingComments) : [];
4524
+ const fallbackElementComments = [];
4525
+ const shouldLiftTextLevelComments = elementLeadingComments.length === 0;
4526
+
4527
+ const hasChildren = Array.isArray(node.children) && node.children.length > 0;
4528
+ const hasInnerComments = Array.isArray(node.innerComments) && node.innerComments.length > 0;
4529
+ const isSelfClosing = !!node.selfClosing;
4530
+ const hasAttributes = Array.isArray(node.attributes) && node.attributes.length > 0;
4531
+
4532
+ if (isSelfClosing && !hasInnerComments && !hasAttributes) {
4533
+ const elementDoc = group(['<', tagName, ' />']);
4534
+ return metadataCommentParts.length > 0
4535
+ ? concat([...metadataCommentParts, elementDoc])
4536
+ : elementDoc;
4537
+ }
4538
+
4539
+ // Determine the line break type for attributes
4540
+ // When singleAttributePerLine is true, force each attribute on its own line with hardline
4541
+ // Otherwise, use line to allow collapsing when it fits
4542
+ const attrLineBreak = options.singleAttributePerLine ? hardline : line;
4543
+
4544
+ const shouldUseSelfClosingSyntax = isSelfClosing || (!hasChildren && !hasInnerComments);
4545
+
4546
+ const openingTag = group([
4547
+ '<',
4548
+ tagName,
4549
+ hasAttributes
4550
+ ? indent(
4551
+ concat([
4552
+ ...path.map((attrPath) => {
4553
+ return concat([attrLineBreak, print(attrPath)]);
4554
+ }, 'attributes'),
4555
+ ]),
4556
+ )
4557
+ : '',
4558
+ // Add line break opportunity before > or />
4559
+ // Use line for self-closing (keeps space), softline for non-self-closing when attributes present
4560
+ // When bracketSameLine is true, don't add line break for non-self-closing elements
4561
+ shouldUseSelfClosingSyntax
4562
+ ? hasAttributes
4563
+ ? line
4564
+ : ''
4565
+ : hasAttributes && !options.bracketSameLine
4566
+ ? softline
4567
+ : '',
4568
+ shouldUseSelfClosingSyntax ? (hasAttributes ? '/>' : ' />') : '>',
4569
+ ]);
4570
+
4571
+ if (!hasChildren) {
4572
+ if (!hasInnerComments) {
4573
+ return metadataCommentParts.length > 0
4574
+ ? concat([...metadataCommentParts, openingTag])
4575
+ : openingTag;
4576
+ }
4577
+
4578
+ const innerParts = [];
4579
+ for (const comment of node.innerComments) {
4580
+ if (comment.type === 'Line') {
4581
+ innerParts.push('//' + comment.value);
4582
+ innerParts.push(hardline);
4583
+ } else if (comment.type === 'Block') {
4584
+ innerParts.push('/*' + comment.value + '*/');
4585
+ innerParts.push(hardline);
4586
+ }
4587
+ }
4588
+
4589
+ if (innerParts.length > 0 && innerParts[innerParts.length - 1] === hardline) {
4590
+ innerParts.pop();
4591
+ }
4592
+
4593
+ const closingTag = concat(['</', tagName, '>']);
4594
+ const elementOutput = group([
4595
+ openingTag,
4596
+ indent(concat([hardline, ...innerParts])),
4597
+ hardline,
4598
+ closingTag,
4599
+ ]);
4600
+ return metadataCommentParts.length > 0
4601
+ ? concat([...metadataCommentParts, elementOutput])
4602
+ : elementOutput;
4603
+ }
4604
+
4605
+ // Has children - use unified children processing
4606
+ // Build children with whitespace preservation
4607
+ const finalChildren = [];
4608
+
4609
+ for (let i = 0; i < node.children.length; i++) {
4610
+ const currentChild = node.children[i];
4611
+ const nextChild = node.children[i + 1];
4612
+ const isTextLikeChild = currentChild.type === 'Text' || currentChild.type === 'Html';
4613
+ const hasTextLeadingComments =
4614
+ shouldLiftTextLevelComments &&
4615
+ isTextLikeChild &&
4616
+ Array.isArray(currentChild.leadingComments) &&
4617
+ currentChild.leadingComments.length > 0;
4618
+ const rawExpressionLeadingComments =
4619
+ isTextLikeChild && Array.isArray(currentChild.expression?.leadingComments)
4620
+ ? currentChild.expression.leadingComments
4621
+ : null;
4622
+
4623
+ if (hasTextLeadingComments) {
4624
+ for (let j = 0; j < currentChild.leadingComments.length; j++) {
4625
+ fallbackElementComments.push(currentChild.leadingComments[j]);
4626
+ }
4627
+ }
4628
+
4629
+ const childPrintArgs = {};
4630
+ if (hasTextLeadingComments) {
4631
+ childPrintArgs.suppressLeadingComments = true;
4632
+ }
4633
+ if (rawExpressionLeadingComments && rawExpressionLeadingComments.length > 0) {
4634
+ childPrintArgs.suppressExpressionLeadingComments = true;
4635
+ }
4636
+
4637
+ const printedChild =
4638
+ Object.keys(childPrintArgs).length > 0
4639
+ ? path.call((childPath) => print(childPath, childPrintArgs), 'children', i)
4640
+ : path.call(print, 'children', i);
4641
+
4642
+ const childDoc =
4643
+ rawExpressionLeadingComments && rawExpressionLeadingComments.length > 0
4644
+ ? concat([...createElementLevelCommentParts(rawExpressionLeadingComments), printedChild])
4645
+ : printedChild;
4646
+ finalChildren.push(childDoc);
4647
+
4648
+ if (nextChild) {
4649
+ const whitespaceLinesCount = getBlankLinesBetweenNodes(currentChild, nextChild);
4650
+ const isTextOrHtmlChild =
4651
+ currentChild.type === 'Text' ||
4652
+ currentChild.type === 'Html' ||
4653
+ nextChild.type === 'Text' ||
4654
+ nextChild.type === 'Html';
4655
+
4656
+ if (whitespaceLinesCount > 0) {
4657
+ finalChildren.push(hardline);
4658
+ finalChildren.push(hardline);
4659
+ } else if (!isTextOrHtmlChild && shouldAddBlankLine(currentChild, nextChild)) {
4660
+ finalChildren.push(hardline);
4661
+ finalChildren.push(hardline);
4662
+ } else {
4663
+ finalChildren.push(hardline);
4664
+ }
4665
+ }
4666
+ }
4667
+
4668
+ const fallbackCommentParts =
4669
+ fallbackElementComments.length > 0
4670
+ ? createElementLevelCommentParts(fallbackElementComments)
4671
+ : [];
4672
+ const leadingCommentParts =
4673
+ metadataCommentParts.length > 0
4674
+ ? [...metadataCommentParts, ...fallbackCommentParts]
4675
+ : fallbackCommentParts;
4676
+
4677
+ const closingTag = concat(['</', tagName, '>']);
4678
+ let elementOutput;
4679
+
4680
+ const hasComponentChild =
4681
+ node.children &&
4682
+ node.children.some((child) => child.type === 'Component' && !child.selfClosing);
4683
+
4684
+ if (finalChildren.length === 1 && !hasComponentChild) {
4685
+ const child = finalChildren[0];
4686
+ const firstChild = node.children[0];
4687
+ const isNonSelfClosingElement =
4688
+ firstChild &&
4689
+ (firstChild.type === 'Element' || firstChild.type === 'JSXElement') &&
4690
+ !firstChild.selfClosing;
4691
+ const isElementChild =
4692
+ firstChild && (firstChild.type === 'Element' || firstChild.type === 'JSXElement');
4693
+
4694
+ if (typeof child === 'string' && child.length < 20) {
4695
+ elementOutput = group([openingTag, child, closingTag]);
4696
+ } else if (
4697
+ child &&
4698
+ typeof child === 'object' &&
4699
+ !isNonSelfClosingElement &&
4700
+ shouldInlineSingleChild(node, firstChild, child)
4701
+ ) {
4702
+ if (isElementChild && hasAttributes) {
4703
+ elementOutput = concat([
4704
+ openingTag,
4705
+ indent(concat([hardline, child])),
4706
+ hardline,
4707
+ closingTag,
4708
+ ]);
4709
+ } else {
4710
+ elementOutput = group([
4711
+ openingTag,
4712
+ indent(concat([softline, child])),
4713
+ softline,
4714
+ closingTag,
4715
+ ]);
4716
+ }
4717
+ } else {
4718
+ elementOutput = concat([
4719
+ openingTag,
4720
+ indent(concat([hardline, ...finalChildren])),
4721
+ hardline,
4722
+ closingTag,
4723
+ ]);
4724
+ }
4725
+ } else {
4726
+ elementOutput = group([
4727
+ openingTag,
4728
+ indent(concat([hardline, ...finalChildren])),
4729
+ hardline,
4730
+ closingTag,
4731
+ ]);
4732
+ }
4733
+
4734
+ return leadingCommentParts.length > 0
4735
+ ? concat([...leadingCommentParts, elementOutput])
4736
+ : elementOutput;
4737
+ }
4738
+
4739
+ function printAttribute(node, path, options, print) {
4740
+ const parts = [];
4741
+
4742
+ // Handle shorthand syntax: {id} instead of id={id}
4743
+ // Check if either node.shorthand is true, OR if the value is an Identifier with the same name
4744
+ const isShorthand =
4745
+ node.shorthand ||
4746
+ (node.value && node.value.type === 'Identifier' && node.value.name === node.name.name);
4747
+
4748
+ if (isShorthand) {
4749
+ parts.push('{');
4750
+ // Check if the value has tracked property for @count syntax
4751
+ const trackedPrefix = node.value && node.value.tracked ? '@' : '';
4752
+ parts.push(trackedPrefix + node.name.name);
4753
+ parts.push('}');
4754
+ return parts;
4755
+ }
4756
+
4757
+ parts.push(node.name.name);
4758
+
4759
+ if (node.value) {
4760
+ if (node.value.type === 'Literal' && typeof node.value.value === 'string') {
4761
+ // String literals don't need curly braces
4762
+ // Use jsxSingleQuote option if available, otherwise use double quotes
4763
+ parts.push('=');
4764
+ const useJsxSingleQuote = options.jsxSingleQuote === true;
4765
+ parts.push(
4766
+ formatStringLiteral(node.value.value, { ...options, singleQuote: useJsxSingleQuote }),
4767
+ );
4768
+ } else {
4769
+ // All other values need curly braces: numbers, booleans, null, expressions, etc.
4770
+ parts.push('={');
4771
+ // Pass inline context for attribute values (keep objects compact)
4772
+ parts.push(path.call((attrPath) => print(attrPath, { isInAttribute: true }), 'value'));
4773
+ parts.push('}');
4774
+ }
4775
+ }
4776
+
4777
+ return parts;
4778
+ }