@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/LICENSE +21 -0
- package/README.md +3 -0
- package/package.json +37 -0
- package/src/index.js +4778 -0
- package/src/index.test.js +3779 -0
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
|
+
}
|