@tsrx/prettier-plugin 0.3.71 → 0.3.74
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/package.json +2 -2
- package/src/index.js +800 -409
- package/src/index.test.js +5786 -11
package/src/index.js
CHANGED
|
@@ -129,26 +129,6 @@ export const printers = {
|
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
if (node.type === 'ScriptContent' && node.content) {
|
|
133
|
-
return async (textToDoc) => {
|
|
134
|
-
try {
|
|
135
|
-
// Format JS/TS using Prettier's textToDoc
|
|
136
|
-
const body = await textToDoc(node.content, {
|
|
137
|
-
parser: 'babel-ts',
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
// Return complete element with tags
|
|
141
|
-
// return ['<script>', indent([hardline, formattedContent]), hardline, '</script>'];
|
|
142
|
-
return body;
|
|
143
|
-
} catch (error) {
|
|
144
|
-
// If JS/TS has syntax errors, return original unformatted content
|
|
145
|
-
console.error('Error formatting JS/TS inside <script>:', error);
|
|
146
|
-
return node.content;
|
|
147
|
-
// return ['<script>', indent([hardline, node.content]), hardline, '</script>'];
|
|
148
|
-
}
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
132
|
return null;
|
|
153
133
|
},
|
|
154
134
|
/**
|
|
@@ -761,16 +741,9 @@ function printRippleNode(node, path, options, print, args) {
|
|
|
761
741
|
|
|
762
742
|
const isInlineContext = args && args.isInlineContext;
|
|
763
743
|
const suppressLeadingComments = args && args.suppressLeadingComments;
|
|
764
|
-
const suppressExpressionLeadingComments = args && args.suppressExpressionLeadingComments;
|
|
765
|
-
const parentNode = /** @type {AST.Node | null} */ (path.getParentNode());
|
|
766
|
-
|
|
767
|
-
// For TSRXExpression and Text nodes, don't add leading comments here - they should be handled
|
|
768
|
-
// as separate children within elements, not as part of the expression.
|
|
769
|
-
const shouldSkipLeadingComments =
|
|
770
|
-
parentNode?.type === 'Element' && (node.type === 'TSRXExpression' || node.type === 'Text');
|
|
771
744
|
|
|
772
745
|
// Handle leading comments
|
|
773
|
-
if (node.leadingComments && !
|
|
746
|
+
if (node.leadingComments && !suppressLeadingComments) {
|
|
774
747
|
for (let i = 0; i < node.leadingComments.length; i++) {
|
|
775
748
|
const comment = node.leadingComments[i];
|
|
776
749
|
const nextComment = node.leadingComments[i + 1];
|
|
@@ -786,7 +759,7 @@ function printRippleNode(node, path, options, print, args) {
|
|
|
786
759
|
if (blankLinesBetween > 0) {
|
|
787
760
|
parts.push(hardline);
|
|
788
761
|
}
|
|
789
|
-
} else if (isLastComment) {
|
|
762
|
+
} else if (isLastComment && node.type !== 'JSXText') {
|
|
790
763
|
// Preserve a blank line between the last comment and the node if it existed
|
|
791
764
|
const blankLinesBetween = getBlankLinesBetweenNodes(comment, node);
|
|
792
765
|
if (blankLinesBetween > 0) {
|
|
@@ -902,10 +875,56 @@ function printRippleNode(node, path, options, print, args) {
|
|
|
902
875
|
case 'IfStatement':
|
|
903
876
|
nodeContent = printIfStatement(node, path, options, print);
|
|
904
877
|
break;
|
|
878
|
+
case 'JSXIfExpression':
|
|
879
|
+
nodeContent = [
|
|
880
|
+
'@',
|
|
881
|
+
printIfStatement(
|
|
882
|
+
/** @type {AST.IfStatement} */ (/** @type {unknown} */ (node)),
|
|
883
|
+
path,
|
|
884
|
+
options,
|
|
885
|
+
print,
|
|
886
|
+
true,
|
|
887
|
+
),
|
|
888
|
+
];
|
|
889
|
+
break;
|
|
905
890
|
|
|
906
891
|
case 'ForOfStatement':
|
|
907
892
|
nodeContent = printForOfStatement(node, path, options, print);
|
|
908
893
|
break;
|
|
894
|
+
case 'JSXForExpression':
|
|
895
|
+
if (node.statementType === 'ForInStatement') {
|
|
896
|
+
nodeContent = [
|
|
897
|
+
'@',
|
|
898
|
+
printForInStatement(
|
|
899
|
+
/** @type {AST.ForInStatement} */ (/** @type {unknown} */ (node)),
|
|
900
|
+
path,
|
|
901
|
+
options,
|
|
902
|
+
print,
|
|
903
|
+
),
|
|
904
|
+
];
|
|
905
|
+
} else if (node.statementType === 'ForStatement') {
|
|
906
|
+
nodeContent = [
|
|
907
|
+
'@',
|
|
908
|
+
printForStatement(
|
|
909
|
+
/** @type {AST.ForStatement} */ (/** @type {unknown} */ (node)),
|
|
910
|
+
path,
|
|
911
|
+
options,
|
|
912
|
+
print,
|
|
913
|
+
),
|
|
914
|
+
];
|
|
915
|
+
} else {
|
|
916
|
+
nodeContent = [
|
|
917
|
+
'@',
|
|
918
|
+
printForOfStatement(
|
|
919
|
+
/** @type {AST.ForOfStatement} */ (/** @type {unknown} */ (node)),
|
|
920
|
+
path,
|
|
921
|
+
options,
|
|
922
|
+
print,
|
|
923
|
+
true,
|
|
924
|
+
),
|
|
925
|
+
];
|
|
926
|
+
}
|
|
927
|
+
break;
|
|
909
928
|
|
|
910
929
|
case 'ForStatement':
|
|
911
930
|
nodeContent = printForStatement(node, path, options, print);
|
|
@@ -931,6 +950,18 @@ function printRippleNode(node, path, options, print, args) {
|
|
|
931
950
|
case 'TryStatement':
|
|
932
951
|
nodeContent = printTryStatement(node, path, options, print);
|
|
933
952
|
break;
|
|
953
|
+
case 'JSXTryExpression':
|
|
954
|
+
nodeContent = [
|
|
955
|
+
'@',
|
|
956
|
+
printTryStatement(
|
|
957
|
+
/** @type {AST.TryStatement} */ (/** @type {unknown} */ (node)),
|
|
958
|
+
path,
|
|
959
|
+
options,
|
|
960
|
+
print,
|
|
961
|
+
true,
|
|
962
|
+
),
|
|
963
|
+
];
|
|
964
|
+
break;
|
|
934
965
|
|
|
935
966
|
case 'ArrayExpression': {
|
|
936
967
|
if (!node.elements || node.elements.length === 0) {
|
|
@@ -1497,9 +1528,10 @@ function printRippleNode(node, path, options, print, args) {
|
|
|
1497
1528
|
(typePath) => print(typePath, { preferInlineSimpleUnionType: true }),
|
|
1498
1529
|
'typeAnnotation',
|
|
1499
1530
|
);
|
|
1500
|
-
nodeContent =
|
|
1501
|
-
|
|
1502
|
-
|
|
1531
|
+
nodeContent =
|
|
1532
|
+
node.typeAnnotation.type !== 'TSTypeLiteral' && willBreak(typeAnnotation)
|
|
1533
|
+
? [path.call(print, 'expression'), ' as', indent([line, typeAnnotation])]
|
|
1534
|
+
: [path.call(print, 'expression'), ' as ', typeAnnotation];
|
|
1503
1535
|
break;
|
|
1504
1536
|
}
|
|
1505
1537
|
|
|
@@ -1508,14 +1540,19 @@ function printRippleNode(node, path, options, print, args) {
|
|
|
1508
1540
|
(typePath) => print(typePath, { preferInlineSimpleUnionType: true }),
|
|
1509
1541
|
'typeAnnotation',
|
|
1510
1542
|
);
|
|
1511
|
-
nodeContent =
|
|
1512
|
-
|
|
1513
|
-
|
|
1543
|
+
nodeContent =
|
|
1544
|
+
node.typeAnnotation.type !== 'TSTypeLiteral' && willBreak(typeAnnotation)
|
|
1545
|
+
? [path.call(print, 'expression'), ' satisfies', indent([line, typeAnnotation])]
|
|
1546
|
+
: [path.call(print, 'expression'), ' satisfies ', typeAnnotation];
|
|
1514
1547
|
break;
|
|
1515
1548
|
}
|
|
1516
1549
|
|
|
1517
1550
|
case 'TSNonNullExpression': {
|
|
1518
|
-
|
|
1551
|
+
const expression = path.call(print, 'expression');
|
|
1552
|
+
const needsParens =
|
|
1553
|
+
node.expression.type === 'TSAsExpression' ||
|
|
1554
|
+
node.expression.type === 'TSSatisfiesExpression';
|
|
1555
|
+
nodeContent = needsParens ? ['(', expression, ')!'] : [expression, '!'];
|
|
1519
1556
|
break;
|
|
1520
1557
|
}
|
|
1521
1558
|
|
|
@@ -1628,6 +1665,14 @@ function printRippleNode(node, path, options, print, args) {
|
|
|
1628
1665
|
case 'SwitchStatement':
|
|
1629
1666
|
nodeContent = printSwitchStatement(node, path, options, print);
|
|
1630
1667
|
break;
|
|
1668
|
+
case 'JSXSwitchExpression':
|
|
1669
|
+
nodeContent = printJSXSwitchExpression(
|
|
1670
|
+
/** @type {AST.SwitchStatement} */ (/** @type {unknown} */ (node)),
|
|
1671
|
+
path,
|
|
1672
|
+
options,
|
|
1673
|
+
print,
|
|
1674
|
+
);
|
|
1675
|
+
break;
|
|
1631
1676
|
|
|
1632
1677
|
case 'SwitchCase':
|
|
1633
1678
|
nodeContent = printSwitchCase(node, path, options, print);
|
|
@@ -1685,13 +1730,6 @@ function printRippleNode(node, path, options, print, args) {
|
|
|
1685
1730
|
}
|
|
1686
1731
|
break;
|
|
1687
1732
|
}
|
|
1688
|
-
case 'SpreadAttribute': {
|
|
1689
|
-
/** @type {Doc[]} */
|
|
1690
|
-
const parts = ['{...', path.call(print, 'argument'), '}'];
|
|
1691
|
-
nodeContent = parts;
|
|
1692
|
-
break;
|
|
1693
|
-
}
|
|
1694
|
-
|
|
1695
1733
|
case 'Identifier': {
|
|
1696
1734
|
// Simple case - just return the name directly like Prettier core
|
|
1697
1735
|
const trackedPrefix = node.tracked ? '@' : '';
|
|
@@ -2120,6 +2158,10 @@ function printRippleNode(node, path, options, print, args) {
|
|
|
2120
2158
|
nodeContent = printTSCallSignatureDeclaration(node, path, options, print);
|
|
2121
2159
|
break;
|
|
2122
2160
|
|
|
2161
|
+
case 'TSConstructSignatureDeclaration':
|
|
2162
|
+
nodeContent = printTSConstructSignatureDeclaration(node, path, options, print);
|
|
2163
|
+
break;
|
|
2164
|
+
|
|
2123
2165
|
case 'TSEnumMember':
|
|
2124
2166
|
nodeContent = printTSEnumMember(node, path, options, print);
|
|
2125
2167
|
break;
|
|
@@ -2245,20 +2287,17 @@ function printRippleNode(node, path, options, print, args) {
|
|
|
2245
2287
|
break;
|
|
2246
2288
|
}
|
|
2247
2289
|
|
|
2248
|
-
case '
|
|
2249
|
-
nodeContent =
|
|
2250
|
-
break;
|
|
2251
|
-
|
|
2252
|
-
case 'TsxCompat':
|
|
2253
|
-
nodeContent = printTsxCompat(node, path, options, print);
|
|
2290
|
+
case 'JSXCodeBlock':
|
|
2291
|
+
nodeContent = printJSXCodeBlock(node, path, options, print);
|
|
2254
2292
|
break;
|
|
2255
2293
|
|
|
2256
|
-
case '
|
|
2257
|
-
nodeContent =
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2294
|
+
case 'JSXStyleElement':
|
|
2295
|
+
nodeContent = printJSXElement(
|
|
2296
|
+
/** @type {ESTreeJSX.JSXElement} */ (/** @type {unknown} */ (node)),
|
|
2297
|
+
path,
|
|
2298
|
+
options,
|
|
2299
|
+
print,
|
|
2300
|
+
);
|
|
2262
2301
|
break;
|
|
2263
2302
|
|
|
2264
2303
|
case 'JSXElement':
|
|
@@ -2270,7 +2309,7 @@ function printRippleNode(node, path, options, print, args) {
|
|
|
2270
2309
|
break;
|
|
2271
2310
|
|
|
2272
2311
|
case 'JSXText':
|
|
2273
|
-
nodeContent = node.value;
|
|
2312
|
+
nodeContent = printRawText(node.value);
|
|
2274
2313
|
break;
|
|
2275
2314
|
|
|
2276
2315
|
case 'JSXEmptyExpression':
|
|
@@ -2283,28 +2322,12 @@ function printRippleNode(node, path, options, print, args) {
|
|
|
2283
2322
|
}
|
|
2284
2323
|
break;
|
|
2285
2324
|
|
|
2286
|
-
case '
|
|
2287
|
-
nodeContent =
|
|
2325
|
+
case 'JSXAttribute':
|
|
2326
|
+
nodeContent = printJSXAttribute(node, path, options, print);
|
|
2288
2327
|
break;
|
|
2289
2328
|
|
|
2290
|
-
case '
|
|
2291
|
-
|
|
2292
|
-
? path.call((exprPath) => print(exprPath, { suppressLeadingComments: true }), 'expression')
|
|
2293
|
-
: path.call(print, 'expression');
|
|
2294
|
-
nodeContent = ['{', expressionDoc, '}'];
|
|
2295
|
-
break;
|
|
2296
|
-
}
|
|
2297
|
-
|
|
2298
|
-
case 'Text': {
|
|
2299
|
-
if (typeof node.raw === 'string') {
|
|
2300
|
-
nodeContent = printRawText(node.raw);
|
|
2301
|
-
break;
|
|
2302
|
-
}
|
|
2303
|
-
|
|
2304
|
-
const expressionDoc = suppressExpressionLeadingComments
|
|
2305
|
-
? path.call((exprPath) => print(exprPath, { suppressLeadingComments: true }), 'expression')
|
|
2306
|
-
: path.call(print, 'expression');
|
|
2307
|
-
nodeContent = ['{', expressionDoc, '}'];
|
|
2329
|
+
case 'JSXSpreadAttribute': {
|
|
2330
|
+
nodeContent = ['{...', path.call(print, 'argument'), '}'];
|
|
2308
2331
|
break;
|
|
2309
2332
|
}
|
|
2310
2333
|
|
|
@@ -2531,7 +2554,10 @@ function printVariableDeclaration(node, path, options, print) {
|
|
|
2531
2554
|
const isForLoopInit =
|
|
2532
2555
|
(parentNode && parentNode.type === 'ForStatement' && parentNode.init === node) ||
|
|
2533
2556
|
(parentNode && parentNode.type === 'ForOfStatement' && parentNode.left === node) ||
|
|
2534
|
-
(parentNode && parentNode.type === 'ForInStatement' && parentNode.left === node)
|
|
2557
|
+
(parentNode && parentNode.type === 'ForInStatement' && parentNode.left === node) ||
|
|
2558
|
+
(parentNode &&
|
|
2559
|
+
parentNode.type === 'JSXForExpression' &&
|
|
2560
|
+
(parentNode.left === node || parentNode.init === node));
|
|
2535
2561
|
|
|
2536
2562
|
const declarations = path.map(print, 'declarations');
|
|
2537
2563
|
const declarationParts = join(', ', declarations);
|
|
@@ -2676,6 +2702,12 @@ function printArrowFunction(node, path, options, print, args) {
|
|
|
2676
2702
|
if (shouldBreakBody) {
|
|
2677
2703
|
parts.push(' =>', indent([hardline, bodyContent]));
|
|
2678
2704
|
} else {
|
|
2705
|
+
if (isTemplateExpression(node.body)) {
|
|
2706
|
+
return conditionalGroup([
|
|
2707
|
+
group([...parts, ' => ', bodyContent]),
|
|
2708
|
+
group([...parts, ' =>', indent([hardline, bodyContent])]),
|
|
2709
|
+
]);
|
|
2710
|
+
}
|
|
2679
2711
|
parts.push(
|
|
2680
2712
|
' =>',
|
|
2681
2713
|
group(indent(line), { id: groupId }),
|
|
@@ -2693,22 +2725,26 @@ function printArrowFunction(node, path, options, print, args) {
|
|
|
2693
2725
|
* @returns {boolean}
|
|
2694
2726
|
*/
|
|
2695
2727
|
function isTemplateExpression(node) {
|
|
2696
|
-
return
|
|
2697
|
-
node.type === 'Tsx' ||
|
|
2698
|
-
node.type === 'TsxCompat' ||
|
|
2699
|
-
node.type === 'Tsrx' ||
|
|
2700
|
-
node.type === 'JSXElement' ||
|
|
2701
|
-
node.type === 'JSXFragment'
|
|
2702
|
-
);
|
|
2728
|
+
return node.type === 'JSXElement' || node.type === 'JSXFragment';
|
|
2703
2729
|
}
|
|
2704
2730
|
|
|
2705
2731
|
/**
|
|
2706
2732
|
* Check whether a braced attribute expression should close on its own line.
|
|
2707
2733
|
* @param {AST.Node} node - The expression inside the attribute braces
|
|
2734
|
+
* @param {RippleFormatOptions} options
|
|
2735
|
+
* @param {AST.Node} [attributeNode]
|
|
2708
2736
|
* @returns {boolean}
|
|
2709
2737
|
*/
|
|
2710
|
-
function shouldBreakAttributeExpressionClosingBrace(node) {
|
|
2711
|
-
return
|
|
2738
|
+
function shouldBreakAttributeExpressionClosingBrace(node, options, attributeNode = node) {
|
|
2739
|
+
return (
|
|
2740
|
+
node.type === 'ArrowFunctionExpression' &&
|
|
2741
|
+
node.body &&
|
|
2742
|
+
isTemplateExpression(node.body) &&
|
|
2743
|
+
sourceSpanExceedsPrintWidth(
|
|
2744
|
+
/** @type {AST.NodeWithLocation} */ (/** @type {unknown} */ (attributeNode ?? node)),
|
|
2745
|
+
options,
|
|
2746
|
+
)
|
|
2747
|
+
);
|
|
2712
2748
|
}
|
|
2713
2749
|
|
|
2714
2750
|
/**
|
|
@@ -2925,9 +2961,6 @@ function sourceSpanExceedsPrintWidth(node, options) {
|
|
|
2925
2961
|
* @returns {boolean}
|
|
2926
2962
|
*/
|
|
2927
2963
|
function shouldBreakArrowExpressionBody(node, options, args) {
|
|
2928
|
-
if (args?.isInAttribute && isTemplateExpression(node)) {
|
|
2929
|
-
return true;
|
|
2930
|
-
}
|
|
2931
2964
|
return (
|
|
2932
2965
|
(node.type === 'BinaryExpression' || node.type === 'LogicalExpression') &&
|
|
2933
2966
|
sourceSpanExceedsPrintWidth(/** @type {AST.NodeWithLocation} */ (node), options)
|
|
@@ -3300,9 +3333,10 @@ function extractAndPrintLeadingComments(node) {
|
|
|
3300
3333
|
* @param {AstPath<AST.IfStatement>} path - The AST path
|
|
3301
3334
|
* @param {RippleFormatOptions} options - Prettier options
|
|
3302
3335
|
* @param {PrintFn} print - Print callback
|
|
3336
|
+
* @param {boolean} [directive]
|
|
3303
3337
|
* @returns {Doc[]}
|
|
3304
3338
|
*/
|
|
3305
|
-
function printIfStatement(node, path, options, print) {
|
|
3339
|
+
function printIfStatement(node, path, options, print, directive = false) {
|
|
3306
3340
|
// Extract leading comments from test node to print them before 'if' keyword
|
|
3307
3341
|
const testNode = node.test;
|
|
3308
3342
|
|
|
@@ -3346,8 +3380,24 @@ function printIfStatement(node, path, options, print) {
|
|
|
3346
3380
|
parts.push(' ');
|
|
3347
3381
|
}
|
|
3348
3382
|
|
|
3349
|
-
parts.push('else ');
|
|
3350
|
-
|
|
3383
|
+
parts.push(directive ? '@else ' : 'else ');
|
|
3384
|
+
if (directive && node.alternate.type === 'IfStatement') {
|
|
3385
|
+
parts.push(
|
|
3386
|
+
path.call(
|
|
3387
|
+
(alternatePath) =>
|
|
3388
|
+
printIfStatement(
|
|
3389
|
+
/** @type {AST.IfStatement} */ (alternatePath.node),
|
|
3390
|
+
/** @type {AstPath<AST.IfStatement>} */ (alternatePath),
|
|
3391
|
+
options,
|
|
3392
|
+
print,
|
|
3393
|
+
true,
|
|
3394
|
+
),
|
|
3395
|
+
'alternate',
|
|
3396
|
+
),
|
|
3397
|
+
);
|
|
3398
|
+
} else {
|
|
3399
|
+
parts.push(path.call(print, 'alternate'));
|
|
3400
|
+
}
|
|
3351
3401
|
}
|
|
3352
3402
|
|
|
3353
3403
|
return parts;
|
|
@@ -3381,9 +3431,10 @@ function printForInStatement(node, path, options, print) {
|
|
|
3381
3431
|
* @param {AstPath<AST.ForOfStatement>} path - The AST path
|
|
3382
3432
|
* @param {RippleFormatOptions} options - Prettier options
|
|
3383
3433
|
* @param {PrintFn} print - Print callback
|
|
3434
|
+
* @param {boolean} [directive]
|
|
3384
3435
|
* @returns {Doc[]}
|
|
3385
3436
|
*/
|
|
3386
|
-
function printForOfStatement(node, path, options, print) {
|
|
3437
|
+
function printForOfStatement(node, path, options, print, directive = false) {
|
|
3387
3438
|
/** @type {Doc[]} */
|
|
3388
3439
|
const parts = [];
|
|
3389
3440
|
parts.push('for (');
|
|
@@ -3404,6 +3455,10 @@ function printForOfStatement(node, path, options, print) {
|
|
|
3404
3455
|
|
|
3405
3456
|
parts.push(') ');
|
|
3406
3457
|
parts.push(path.call(print, 'body'));
|
|
3458
|
+
if (node.empty) {
|
|
3459
|
+
parts.push(directive ? ' @empty ' : ' empty ');
|
|
3460
|
+
parts.push(path.call(print, 'empty'));
|
|
3461
|
+
}
|
|
3407
3462
|
|
|
3408
3463
|
return parts;
|
|
3409
3464
|
}
|
|
@@ -3720,9 +3775,10 @@ function printClassDeclaration(node, path, options, print) {
|
|
|
3720
3775
|
* @param {AstPath<AST.TryStatement>} path - The AST path
|
|
3721
3776
|
* @param {RippleFormatOptions} options - Prettier options
|
|
3722
3777
|
* @param {PrintFn} print - Print callback
|
|
3778
|
+
* @param {boolean} [directive=false] - Whether this is a JSX @try expression.
|
|
3723
3779
|
* @returns {Doc[]}
|
|
3724
3780
|
*/
|
|
3725
|
-
function printTryStatement(node, path, options, print) {
|
|
3781
|
+
function printTryStatement(node, path, options, print, directive = false) {
|
|
3726
3782
|
// Extract leading comments from block node to print them before 'try' keyword
|
|
3727
3783
|
const blockNode = node.block;
|
|
3728
3784
|
|
|
@@ -3742,12 +3798,12 @@ function printTryStatement(node, path, options, print) {
|
|
|
3742
3798
|
parts.push(block);
|
|
3743
3799
|
|
|
3744
3800
|
if (node.pending) {
|
|
3745
|
-
parts.push(' pending ');
|
|
3801
|
+
parts.push(directive ? ' @pending ' : ' pending ');
|
|
3746
3802
|
parts.push(path.call(print, 'pending'));
|
|
3747
3803
|
}
|
|
3748
3804
|
|
|
3749
3805
|
if (node.handler) {
|
|
3750
|
-
parts.push(' catch');
|
|
3806
|
+
parts.push(directive ? ' @catch' : ' catch');
|
|
3751
3807
|
if (node.handler.param) {
|
|
3752
3808
|
parts.push(' (');
|
|
3753
3809
|
parts.push(path.call(print, 'handler', 'param'));
|
|
@@ -4488,6 +4544,76 @@ function printSwitchStatement(node, path, options, print) {
|
|
|
4488
4544
|
return parts;
|
|
4489
4545
|
}
|
|
4490
4546
|
|
|
4547
|
+
/**
|
|
4548
|
+
* Print a JSX switch expression. JSX switch cases use explicit template blocks:
|
|
4549
|
+
* `case value: { ... }`, unlike ordinary JavaScript switch cases.
|
|
4550
|
+
* @param {AST.SwitchStatement} node - The switch expression node
|
|
4551
|
+
* @param {AstPath<AST.SwitchStatement>} path - The AST path
|
|
4552
|
+
* @param {RippleFormatOptions} options - Prettier options
|
|
4553
|
+
* @param {PrintFn} print - Print callback
|
|
4554
|
+
* @returns {Doc[]}
|
|
4555
|
+
*/
|
|
4556
|
+
function printJSXSwitchExpression(node, path, options, print) {
|
|
4557
|
+
const discriminant = path.call(
|
|
4558
|
+
(discriminantPath) => print(discriminantPath, { suppressLeadingComments: true }),
|
|
4559
|
+
'discriminant',
|
|
4560
|
+
);
|
|
4561
|
+
|
|
4562
|
+
/** @type {Doc[]} */
|
|
4563
|
+
const cases = [];
|
|
4564
|
+
for (let i = 0; i < node.cases.length; i++) {
|
|
4565
|
+
const caseDoc = [printJSXSwitchCase(node.cases[i], path, options, print, i)];
|
|
4566
|
+
if (i < node.cases.length - 1 && isNextLineEmpty(node.cases[i], options)) {
|
|
4567
|
+
caseDoc.push(hardline);
|
|
4568
|
+
}
|
|
4569
|
+
cases.push(caseDoc);
|
|
4570
|
+
}
|
|
4571
|
+
|
|
4572
|
+
const bodyDoc =
|
|
4573
|
+
cases.length > 0 ? [indent([hardline, join(hardline, cases)]), hardline] : hardline;
|
|
4574
|
+
|
|
4575
|
+
const discriminantDoc = group(['@switch (', indent([softline, discriminant]), softline, ')']);
|
|
4576
|
+
|
|
4577
|
+
return [
|
|
4578
|
+
...extractAndPrintLeadingComments(node.discriminant),
|
|
4579
|
+
discriminantDoc,
|
|
4580
|
+
' {',
|
|
4581
|
+
bodyDoc,
|
|
4582
|
+
'}',
|
|
4583
|
+
];
|
|
4584
|
+
}
|
|
4585
|
+
|
|
4586
|
+
/**
|
|
4587
|
+
* @param {AST.SwitchCase} node
|
|
4588
|
+
* @param {AstPath<AST.SwitchStatement>} path
|
|
4589
|
+
* @param {RippleFormatOptions} options
|
|
4590
|
+
* @param {PrintFn} print
|
|
4591
|
+
* @param {number} index
|
|
4592
|
+
* @returns {Doc[]}
|
|
4593
|
+
*/
|
|
4594
|
+
function printJSXSwitchCase(node, path, options, print, index) {
|
|
4595
|
+
const header = node.test
|
|
4596
|
+
? ['@case ', path.call(print, 'cases', index, 'test'), ':']
|
|
4597
|
+
: '@default:';
|
|
4598
|
+
const consequents = node.consequent || [];
|
|
4599
|
+
const printedConsequents = [];
|
|
4600
|
+
|
|
4601
|
+
for (let i = 0; i < consequents.length; i++) {
|
|
4602
|
+
const child = consequents[i];
|
|
4603
|
+
if (!child || child.type === 'EmptyStatement') {
|
|
4604
|
+
continue;
|
|
4605
|
+
}
|
|
4606
|
+
printedConsequents.push(path.call(print, 'cases', index, 'consequent', i));
|
|
4607
|
+
}
|
|
4608
|
+
|
|
4609
|
+
const bodyDoc =
|
|
4610
|
+
printedConsequents.length > 0
|
|
4611
|
+
? [indent([hardline, join(hardline, printedConsequents)]), hardline]
|
|
4612
|
+
: hardline;
|
|
4613
|
+
|
|
4614
|
+
return [header, ' {', bodyDoc, '}'];
|
|
4615
|
+
}
|
|
4616
|
+
|
|
4491
4617
|
/**
|
|
4492
4618
|
* Print a switch case
|
|
4493
4619
|
* @param {AST.SwitchCase} node - The switch case node
|
|
@@ -4655,6 +4781,26 @@ function getBlankLinesBetweenPositions(current_pos, next_pos) {
|
|
|
4655
4781
|
* @param {AST.Node | AST.CSS.StyleSheet | AST.Comment} nextNode - Next node
|
|
4656
4782
|
* @returns {number}
|
|
4657
4783
|
*/
|
|
4784
|
+
/**
|
|
4785
|
+
* The position to measure a leading blank line against: the first leading
|
|
4786
|
+
* comment if any (so the comment lines aren't miscounted as blank), else the
|
|
4787
|
+
* node itself.
|
|
4788
|
+
* @param {any} node
|
|
4789
|
+
* @returns {any}
|
|
4790
|
+
*/
|
|
4791
|
+
function leadingAnchor(node) {
|
|
4792
|
+
const lead = node?.leadingComments;
|
|
4793
|
+
if (Array.isArray(lead) && lead.length > 0 && lead[0].loc) {
|
|
4794
|
+
return lead[0];
|
|
4795
|
+
}
|
|
4796
|
+
return node;
|
|
4797
|
+
}
|
|
4798
|
+
|
|
4799
|
+
/**
|
|
4800
|
+
* @param {any} currentNode
|
|
4801
|
+
* @param {any} nextNode
|
|
4802
|
+
* @returns {number}
|
|
4803
|
+
*/
|
|
4658
4804
|
function getBlankLinesBetweenNodes(currentNode, nextNode) {
|
|
4659
4805
|
// Return the number of blank lines between two nodes based on their location
|
|
4660
4806
|
if (
|
|
@@ -5027,6 +5173,16 @@ function printVariableDeclarator(node, path, options, print) {
|
|
|
5027
5173
|
}
|
|
5028
5174
|
}
|
|
5029
5175
|
|
|
5176
|
+
if (isTemplateExpression(node.init)) {
|
|
5177
|
+
const groupId = Symbol('declaration');
|
|
5178
|
+
return group([
|
|
5179
|
+
group(id),
|
|
5180
|
+
' =',
|
|
5181
|
+
group(indent(line), { id: groupId }),
|
|
5182
|
+
indentIfBreak(init, { groupId }),
|
|
5183
|
+
]);
|
|
5184
|
+
}
|
|
5185
|
+
|
|
5030
5186
|
// Default: simple inline format with space
|
|
5031
5187
|
// Use group to allow breaking if needed - but keep inline when it fits
|
|
5032
5188
|
return group([id, ' = ', init]);
|
|
@@ -5196,6 +5352,45 @@ function printTSCallSignatureDeclaration(node, path, options, print) {
|
|
|
5196
5352
|
return parts;
|
|
5197
5353
|
}
|
|
5198
5354
|
|
|
5355
|
+
/**
|
|
5356
|
+
* Print a TypeScript construct signature in an interface or type literal
|
|
5357
|
+
* @param {AST.TSConstructSignatureDeclaration} node - The construct signature node
|
|
5358
|
+
* @param {AstPath<AST.TSConstructSignatureDeclaration>} path - The AST path
|
|
5359
|
+
* @param {RippleFormatOptions} options - Prettier options
|
|
5360
|
+
* @param {PrintFn} print - Print callback
|
|
5361
|
+
* @returns {Doc[]}
|
|
5362
|
+
*/
|
|
5363
|
+
function printTSConstructSignatureDeclaration(node, path, options, print) {
|
|
5364
|
+
/** @type {Doc[]} */
|
|
5365
|
+
const parts = ['new '];
|
|
5366
|
+
|
|
5367
|
+
if (node.typeParameters) {
|
|
5368
|
+
const type_params = path.call(print, 'typeParameters');
|
|
5369
|
+
if (Array.isArray(type_params)) {
|
|
5370
|
+
parts.push(...type_params);
|
|
5371
|
+
} else {
|
|
5372
|
+
parts.push(type_params);
|
|
5373
|
+
}
|
|
5374
|
+
}
|
|
5375
|
+
|
|
5376
|
+
parts.push('(');
|
|
5377
|
+
if (node.parameters && node.parameters.length > 0) {
|
|
5378
|
+
const params = path.map(print, 'parameters');
|
|
5379
|
+
for (let i = 0; i < params.length; i++) {
|
|
5380
|
+
if (i > 0) parts.push(', ');
|
|
5381
|
+
parts.push(params[i]);
|
|
5382
|
+
}
|
|
5383
|
+
}
|
|
5384
|
+
parts.push(')');
|
|
5385
|
+
|
|
5386
|
+
if (node.typeAnnotation) {
|
|
5387
|
+
parts.push(': ');
|
|
5388
|
+
parts.push(path.call(print, 'typeAnnotation'));
|
|
5389
|
+
}
|
|
5390
|
+
|
|
5391
|
+
return parts;
|
|
5392
|
+
}
|
|
5393
|
+
|
|
5199
5394
|
/**
|
|
5200
5395
|
* Print a TypeScript type reference (e.g., Array<string>)
|
|
5201
5396
|
* @param {AST.TSTypeReference} node - The type reference node
|
|
@@ -5470,6 +5665,36 @@ function printRawText(raw) {
|
|
|
5470
5665
|
);
|
|
5471
5666
|
}
|
|
5472
5667
|
|
|
5668
|
+
/**
|
|
5669
|
+
* @param {string} raw
|
|
5670
|
+
* @returns {Doc | Doc[] | string}
|
|
5671
|
+
*/
|
|
5672
|
+
function printJSXTextChild(raw) {
|
|
5673
|
+
const text = raw.trim();
|
|
5674
|
+
if (!text) {
|
|
5675
|
+
return '';
|
|
5676
|
+
}
|
|
5677
|
+
|
|
5678
|
+
const lines = text
|
|
5679
|
+
.split(/\r\n|\r|\n/u)
|
|
5680
|
+
.map((line) => line.trim())
|
|
5681
|
+
.filter(Boolean);
|
|
5682
|
+
if (lines.length <= 1) {
|
|
5683
|
+
return lines[0] ?? '';
|
|
5684
|
+
}
|
|
5685
|
+
|
|
5686
|
+
return join(hardline, lines);
|
|
5687
|
+
}
|
|
5688
|
+
|
|
5689
|
+
/**
|
|
5690
|
+
* @param {string} raw
|
|
5691
|
+
* @returns {string}
|
|
5692
|
+
*/
|
|
5693
|
+
function normalizeInlineJSXText(raw) {
|
|
5694
|
+
const text = raw.replace(/[^\S\r\n]+/gu, ' ');
|
|
5695
|
+
return text.trim() || !/[\r\n]/u.test(text) ? text : '';
|
|
5696
|
+
}
|
|
5697
|
+
|
|
5473
5698
|
/**
|
|
5474
5699
|
* @param {AST.Node} parentNode
|
|
5475
5700
|
* @param {AST.Node} firstChild
|
|
@@ -5481,8 +5706,7 @@ function shouldInlineSingleChild(parentNode, firstChild, childDoc) {
|
|
|
5481
5706
|
return false;
|
|
5482
5707
|
}
|
|
5483
5708
|
|
|
5484
|
-
|
|
5485
|
-
if (firstChild.type === 'Text') {
|
|
5709
|
+
if (firstChild.type === 'JSXText') {
|
|
5486
5710
|
return true;
|
|
5487
5711
|
}
|
|
5488
5712
|
|
|
@@ -5492,7 +5716,7 @@ function shouldInlineSingleChild(parentNode, firstChild, childDoc) {
|
|
|
5492
5716
|
|
|
5493
5717
|
// Inline JSX expressions if they fit, but respect original multi-line formatting
|
|
5494
5718
|
// for non-literal expressions (e.g. {children} should stay multi-line if written that way)
|
|
5495
|
-
if (firstChild.type === '
|
|
5719
|
+
if (firstChild.type === 'JSXExpressionContainer') {
|
|
5496
5720
|
if (wasOriginallySingleLine(parentNode)) {
|
|
5497
5721
|
return true;
|
|
5498
5722
|
}
|
|
@@ -5510,19 +5734,71 @@ function shouldInlineSingleChild(parentNode, firstChild, childDoc) {
|
|
|
5510
5734
|
return false;
|
|
5511
5735
|
}
|
|
5512
5736
|
|
|
5513
|
-
if (firstChild.type === '
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
/** @type {AST.Element} */ (parentNode).attributes.length === 0
|
|
5517
|
-
);
|
|
5737
|
+
if (firstChild.type === 'JSXElement' && firstChild.openingElement?.selfClosing) {
|
|
5738
|
+
const parent = /** @type {any} */ (parentNode);
|
|
5739
|
+
return !parent.openingElement?.attributes?.length;
|
|
5518
5740
|
}
|
|
5519
5741
|
|
|
5520
5742
|
return false;
|
|
5521
5743
|
}
|
|
5522
5744
|
|
|
5745
|
+
/**
|
|
5746
|
+
* Check whether a child can participate in compact inline TSRX content.
|
|
5747
|
+
* @param {any} child
|
|
5748
|
+
* @returns {boolean}
|
|
5749
|
+
*/
|
|
5750
|
+
function isInlineableTextOrExpressionChild(child) {
|
|
5751
|
+
if (!child || (child.type !== 'JSXText' && child.type !== 'JSXExpressionContainer')) {
|
|
5752
|
+
return false;
|
|
5753
|
+
}
|
|
5754
|
+
|
|
5755
|
+
if (hasComment(/** @type {AST.Node & AST.NodeWithMaybeComments} */ (child))) {
|
|
5756
|
+
return false;
|
|
5757
|
+
}
|
|
5758
|
+
|
|
5759
|
+
const expression = /** @type {{ expression?: AST.Node & AST.NodeWithMaybeComments }} */ (child)
|
|
5760
|
+
.expression;
|
|
5761
|
+
return !expression || !hasComment(expression);
|
|
5762
|
+
}
|
|
5763
|
+
|
|
5764
|
+
/**
|
|
5765
|
+
* @param {any} node
|
|
5766
|
+
* @returns {boolean}
|
|
5767
|
+
*/
|
|
5768
|
+
function shouldTryInlineMultipleTextChildren(node) {
|
|
5769
|
+
return (
|
|
5770
|
+
wasOriginallySingleLine(node) &&
|
|
5771
|
+
Array.isArray(node.children) &&
|
|
5772
|
+
node.children.length > 1 &&
|
|
5773
|
+
node.children.some((/** @type {any} */ child) => child.type === 'JSXText') &&
|
|
5774
|
+
node.children.every(isInlineableTextOrExpressionChild)
|
|
5775
|
+
);
|
|
5776
|
+
}
|
|
5777
|
+
|
|
5778
|
+
/**
|
|
5779
|
+
* @param {AST.Node} child
|
|
5780
|
+
* @returns {boolean}
|
|
5781
|
+
*/
|
|
5782
|
+
function isSimpleJSXExpressionChild(child) {
|
|
5783
|
+
if (child?.type !== 'JSXExpressionContainer') {
|
|
5784
|
+
return false;
|
|
5785
|
+
}
|
|
5786
|
+
|
|
5787
|
+
const expression = child.expression;
|
|
5788
|
+
return (
|
|
5789
|
+
expression?.type === 'Identifier' ||
|
|
5790
|
+
expression?.type === 'Literal' ||
|
|
5791
|
+
expression?.type === 'TemplateLiteral' ||
|
|
5792
|
+
// Stock Prettier keeps a single `{expr}` child inline regardless of the
|
|
5793
|
+
// expression kind (member access, calls, etc.); only multiple children break.
|
|
5794
|
+
expression?.type === 'MemberExpression' ||
|
|
5795
|
+
expression?.type === 'CallExpression'
|
|
5796
|
+
);
|
|
5797
|
+
}
|
|
5798
|
+
|
|
5523
5799
|
/**
|
|
5524
5800
|
* Get leading comments from element metadata
|
|
5525
|
-
* @param {
|
|
5801
|
+
* @param {ESTreeJSX.JSXElement} node - The element node
|
|
5526
5802
|
* @returns {AST.Comment[]}
|
|
5527
5803
|
*/
|
|
5528
5804
|
function getElementLeadingComments(node) {
|
|
@@ -5582,200 +5858,10 @@ function createElementLevelCommentPartsTrimmed(comments) {
|
|
|
5582
5858
|
return parts;
|
|
5583
5859
|
}
|
|
5584
5860
|
|
|
5585
|
-
/**
|
|
5586
|
-
* Print a Tsx node - renders Ripple template children inside <tsx>...</tsx>
|
|
5587
|
-
* or fragment shorthand <>...</> when the original source used a fragment.
|
|
5588
|
-
* @param {AST.Tsx} node - The Tsx node
|
|
5589
|
-
* @param {AstPath<AST.Tsx>} path - The AST path
|
|
5590
|
-
* @param {RippleFormatOptions} options - Prettier options
|
|
5591
|
-
* @param {PrintFn} print - Print callback
|
|
5592
|
-
* @returns {Doc}
|
|
5593
|
-
*/
|
|
5594
|
-
function printTsx(node, path, options, print) {
|
|
5595
|
-
const is_fragment = !node.openingElement?.name;
|
|
5596
|
-
const tagName = is_fragment ? '<>' : '<tsx>';
|
|
5597
|
-
const closingTagName = is_fragment ? '</>' : '</tsx>';
|
|
5598
|
-
|
|
5599
|
-
const hasChildren = Array.isArray(node.children) && node.children.length > 0;
|
|
5600
|
-
|
|
5601
|
-
if (!hasChildren) {
|
|
5602
|
-
return [tagName, closingTagName];
|
|
5603
|
-
}
|
|
5604
|
-
|
|
5605
|
-
// Print children - these are Ripple template children (Element, Text, etc.)
|
|
5606
|
-
const printedChildren = [];
|
|
5607
|
-
|
|
5608
|
-
for (let i = 0; i < node.children.length; i++) {
|
|
5609
|
-
const child = node.children[i];
|
|
5610
|
-
|
|
5611
|
-
if (child.type === 'JSXText') {
|
|
5612
|
-
const text = child.value.trim();
|
|
5613
|
-
if (!text) continue;
|
|
5614
|
-
printedChildren.push(text);
|
|
5615
|
-
} else {
|
|
5616
|
-
const printedChild = path.call(print, 'children', i);
|
|
5617
|
-
printedChildren.push(printedChild);
|
|
5618
|
-
}
|
|
5619
|
-
}
|
|
5620
|
-
|
|
5621
|
-
if (printedChildren.length === 0) {
|
|
5622
|
-
return [tagName, closingTagName];
|
|
5623
|
-
}
|
|
5624
|
-
|
|
5625
|
-
if (!is_fragment || printedChildren.length > 1) {
|
|
5626
|
-
return group([
|
|
5627
|
-
tagName,
|
|
5628
|
-
indent([hardline, join(hardline, printedChildren)]),
|
|
5629
|
-
hardline,
|
|
5630
|
-
closingTagName,
|
|
5631
|
-
]);
|
|
5632
|
-
}
|
|
5633
|
-
|
|
5634
|
-
// Use softline to allow single-line when content fits
|
|
5635
|
-
return group([
|
|
5636
|
-
tagName,
|
|
5637
|
-
indent([softline, join(softline, printedChildren)]),
|
|
5638
|
-
softline,
|
|
5639
|
-
closingTagName,
|
|
5640
|
-
]);
|
|
5641
|
-
}
|
|
5642
|
-
|
|
5643
|
-
/**
|
|
5644
|
-
* Print a Tsrx node - renders native TSRX template children inside a fragment.
|
|
5645
|
-
* @param {AST.Tsrx} node - The Tsrx node
|
|
5646
|
-
* @param {AstPath<AST.Tsrx>} path - The AST path
|
|
5647
|
-
* @param {RippleFormatOptions} options - Prettier options
|
|
5648
|
-
* @param {PrintFn} print - Print callback
|
|
5649
|
-
* @returns {Doc}
|
|
5650
|
-
*/
|
|
5651
|
-
function printTsrx(node, path, options, print) {
|
|
5652
|
-
const tagName = '<>';
|
|
5653
|
-
const closingTagName = '</>';
|
|
5654
|
-
const hasChildren = Array.isArray(node.children) && node.children.length > 0;
|
|
5655
|
-
|
|
5656
|
-
if (!hasChildren) {
|
|
5657
|
-
return [tagName, closingTagName];
|
|
5658
|
-
}
|
|
5659
|
-
|
|
5660
|
-
const printedChildren = [];
|
|
5661
|
-
|
|
5662
|
-
for (let i = 0; i < node.children.length; i++) {
|
|
5663
|
-
const child = node.children[i];
|
|
5664
|
-
|
|
5665
|
-
if (child.type === 'JSXText') {
|
|
5666
|
-
const text = child.value.trim();
|
|
5667
|
-
if (!text) continue;
|
|
5668
|
-
printedChildren.push(text);
|
|
5669
|
-
} else {
|
|
5670
|
-
const printedChild = path.call(print, 'children', i);
|
|
5671
|
-
printedChildren.push(printedChild);
|
|
5672
|
-
}
|
|
5673
|
-
}
|
|
5674
|
-
|
|
5675
|
-
if (printedChildren.length === 0) {
|
|
5676
|
-
return [tagName, closingTagName];
|
|
5677
|
-
}
|
|
5678
|
-
|
|
5679
|
-
if (
|
|
5680
|
-
printedChildren.length === 1 &&
|
|
5681
|
-
['Element', 'Text', 'TSRXExpression'].includes(node.children[0]?.type)
|
|
5682
|
-
) {
|
|
5683
|
-
return group([tagName, indent([softline, printedChildren[0]]), softline, closingTagName]);
|
|
5684
|
-
}
|
|
5685
|
-
|
|
5686
|
-
return group([
|
|
5687
|
-
tagName,
|
|
5688
|
-
indent([hardline, join(hardline, printedChildren)]),
|
|
5689
|
-
hardline,
|
|
5690
|
-
closingTagName,
|
|
5691
|
-
]);
|
|
5692
|
-
}
|
|
5693
|
-
|
|
5694
|
-
/**
|
|
5695
|
-
* Print a TSX compatibility node
|
|
5696
|
-
* @param {AST.TsxCompat} node - The TSX compat node
|
|
5697
|
-
* @param {AstPath<AST.TsxCompat>} path - The AST path
|
|
5698
|
-
* @param {RippleFormatOptions} options - Prettier options
|
|
5699
|
-
* @param {PrintFn} print - Print callback
|
|
5700
|
-
* @returns {Doc}
|
|
5701
|
-
*/
|
|
5702
|
-
function printTsxCompat(node, path, options, print) {
|
|
5703
|
-
const tagName = `<tsx:${node.kind}>`;
|
|
5704
|
-
const closingTagName = `</tsx:${node.kind}>`;
|
|
5705
|
-
|
|
5706
|
-
const hasChildren = Array.isArray(node.children) && node.children.length > 0;
|
|
5707
|
-
|
|
5708
|
-
if (!hasChildren) {
|
|
5709
|
-
return [tagName, closingTagName];
|
|
5710
|
-
}
|
|
5711
|
-
|
|
5712
|
-
// Print JSXElement children - they remain as JSX
|
|
5713
|
-
// Filter out whitespace-only JSXText nodes and merge adjacent text-like nodes
|
|
5714
|
-
const finalChildren = [];
|
|
5715
|
-
let accumulatedText = '';
|
|
5716
|
-
|
|
5717
|
-
for (let i = 0; i < node.children.length; i++) {
|
|
5718
|
-
const child = node.children[i];
|
|
5719
|
-
|
|
5720
|
-
// Check if this is a text-like node (JSXText or Identifier in JSX context)
|
|
5721
|
-
const isTextLike = child.type === 'JSXText';
|
|
5722
|
-
|
|
5723
|
-
if (isTextLike) {
|
|
5724
|
-
// Get the text content
|
|
5725
|
-
let text;
|
|
5726
|
-
if (child.type === 'JSXText') {
|
|
5727
|
-
text = child.value.trim();
|
|
5728
|
-
}
|
|
5729
|
-
|
|
5730
|
-
if (text) {
|
|
5731
|
-
if (accumulatedText) {
|
|
5732
|
-
accumulatedText += ' ' + text;
|
|
5733
|
-
} else {
|
|
5734
|
-
accumulatedText = text;
|
|
5735
|
-
}
|
|
5736
|
-
}
|
|
5737
|
-
} else {
|
|
5738
|
-
// Before adding non-text node, flush accumulated text
|
|
5739
|
-
if (accumulatedText) {
|
|
5740
|
-
if (finalChildren.length > 0) {
|
|
5741
|
-
finalChildren.push(hardline);
|
|
5742
|
-
}
|
|
5743
|
-
finalChildren.push(accumulatedText);
|
|
5744
|
-
accumulatedText = '';
|
|
5745
|
-
}
|
|
5746
|
-
|
|
5747
|
-
if (finalChildren.length > 0) {
|
|
5748
|
-
finalChildren.push(hardline);
|
|
5749
|
-
}
|
|
5750
|
-
|
|
5751
|
-
const printedChild = path.call(print, 'children', i);
|
|
5752
|
-
finalChildren.push(printedChild);
|
|
5753
|
-
}
|
|
5754
|
-
}
|
|
5755
|
-
|
|
5756
|
-
// Don't forget any remaining accumulated text
|
|
5757
|
-
if (accumulatedText) {
|
|
5758
|
-
if (finalChildren.length > 0) {
|
|
5759
|
-
finalChildren.push(hardline);
|
|
5760
|
-
}
|
|
5761
|
-
finalChildren.push(accumulatedText);
|
|
5762
|
-
}
|
|
5763
|
-
|
|
5764
|
-
// Format the TsxCompat element
|
|
5765
|
-
const elementOutput = group([
|
|
5766
|
-
tagName,
|
|
5767
|
-
indent([hardline, ...finalChildren]),
|
|
5768
|
-
hardline,
|
|
5769
|
-
closingTagName,
|
|
5770
|
-
]);
|
|
5771
|
-
|
|
5772
|
-
return elementOutput;
|
|
5773
|
-
}
|
|
5774
|
-
|
|
5775
5861
|
/**
|
|
5776
5862
|
* Print a JSX element
|
|
5777
|
-
* @param {
|
|
5778
|
-
* @param {AstPath<
|
|
5863
|
+
* @param {AST.TSRXJSXElement} node - The JSX element node
|
|
5864
|
+
* @param {AstPath<any>} path - The AST path
|
|
5779
5865
|
* @param {RippleFormatOptions} options - Prettier options
|
|
5780
5866
|
* @param {PrintFn} print - Print callback
|
|
5781
5867
|
* @returns {Doc | Doc[]}
|
|
@@ -5785,20 +5871,7 @@ function printJSXElement(node, path, options, print) {
|
|
|
5785
5871
|
const openingElement = node.openingElement;
|
|
5786
5872
|
const closingElement = node.closingElement;
|
|
5787
5873
|
|
|
5788
|
-
|
|
5789
|
-
let tagName;
|
|
5790
|
-
if (openingElement.name.type === 'JSXIdentifier') {
|
|
5791
|
-
tagName = openingElement.name.name;
|
|
5792
|
-
} else if (openingElement.name.type === 'JSXMemberExpression') {
|
|
5793
|
-
// Handle Member expressions like React.Fragment
|
|
5794
|
-
tagName = printJSXMemberExpression(openingElement.name);
|
|
5795
|
-
} else if (openingElement.name.type === 'JSXNamespacedName') {
|
|
5796
|
-
const namespace_name = openingElement.name.namespace.name;
|
|
5797
|
-
const local_name = openingElement.name.name.name;
|
|
5798
|
-
tagName = namespace_name + ':' + local_name;
|
|
5799
|
-
} else {
|
|
5800
|
-
tagName = 'Unknown';
|
|
5801
|
-
}
|
|
5874
|
+
const tagName = printJSXElementName(openingElement.name);
|
|
5802
5875
|
|
|
5803
5876
|
const isSelfClosing = openingElement.selfClosing;
|
|
5804
5877
|
const hasAttributes = openingElement.attributes && openingElement.attributes.length > 0;
|
|
@@ -5810,6 +5883,12 @@ function printJSXElement(node, path, options, print) {
|
|
|
5810
5883
|
typeArgsDoc = path.call(print, 'openingElement', 'typeArguments');
|
|
5811
5884
|
}
|
|
5812
5885
|
|
|
5886
|
+
// Comments that sit inside the opening tag (before an attribute) are attached
|
|
5887
|
+
// by the parser to a body child; pull them out and key them by the attribute
|
|
5888
|
+
// they precede so they print in the opening tag, not jammed into the body.
|
|
5889
|
+
const openingTagCommentsByAttr = collectOpeningTagComments(node);
|
|
5890
|
+
const hasOpeningTagComments = openingTagCommentsByAttr.size > 0;
|
|
5891
|
+
|
|
5813
5892
|
// Format attributes
|
|
5814
5893
|
/** @type {Doc} */
|
|
5815
5894
|
let attributesDoc = '';
|
|
@@ -5833,19 +5912,31 @@ function printJSXElement(node, path, options, print) {
|
|
|
5833
5912
|
'attributes',
|
|
5834
5913
|
i,
|
|
5835
5914
|
);
|
|
5836
|
-
} else if (attr.type === 'JSXSpreadAttribute'
|
|
5915
|
+
} else if (attr.type === 'JSXSpreadAttribute') {
|
|
5837
5916
|
attrDoc = ['{...', path.call(print, 'openingElement', 'attributes', i, 'argument'), '}'];
|
|
5838
5917
|
}
|
|
5839
5918
|
if (!hasBreakingAttribute && attrDoc && willBreak(attrDoc)) {
|
|
5840
5919
|
hasBreakingAttribute = true;
|
|
5841
5920
|
}
|
|
5921
|
+
const lead = openingTagCommentsByAttr.get(i);
|
|
5922
|
+
if (lead) {
|
|
5923
|
+
/** @type {Doc[]} */
|
|
5924
|
+
const parts = [];
|
|
5925
|
+
for (const comment of lead) {
|
|
5926
|
+
parts.push(
|
|
5927
|
+
comment.type === 'Line' ? '//' + comment.value : '/*' + comment.value + '*/',
|
|
5928
|
+
);
|
|
5929
|
+
parts.push(hardline);
|
|
5930
|
+
}
|
|
5931
|
+
return [...parts, attrDoc];
|
|
5932
|
+
}
|
|
5842
5933
|
return attrDoc;
|
|
5843
5934
|
},
|
|
5844
5935
|
);
|
|
5845
5936
|
const attrLineBreak = options.singleAttributePerLine ? hardline : line;
|
|
5846
5937
|
attributesDoc = indent([attrLineBreak, join(attrLineBreak, attrs)]);
|
|
5847
5938
|
}
|
|
5848
|
-
const shouldForceBreak = hasBreakingAttribute;
|
|
5939
|
+
const shouldForceBreak = hasBreakingAttribute || hasOpeningTagComments;
|
|
5849
5940
|
|
|
5850
5941
|
if (isSelfClosing) {
|
|
5851
5942
|
return group(['<', tagName, typeArgsDoc, attributesDoc, hasAttributes ? line : ' ', '/>'], {
|
|
@@ -5865,58 +5956,119 @@ function printJSXElement(node, path, options, print) {
|
|
|
5865
5956
|
{ shouldBreak: shouldForceBreak },
|
|
5866
5957
|
);
|
|
5867
5958
|
|
|
5959
|
+
// Trailing comments after the last child are attached by the parser either to
|
|
5960
|
+
// the closing tag (`closingElement.leadingComments`) or, when the last child is
|
|
5961
|
+
// an `{expr}` container, to `metadata.elementLeadingComments` positioned inside
|
|
5962
|
+
// the body (start >= opening tag end). Emit both before `</tag>`.
|
|
5963
|
+
const openingTagEnd = /** @type {AST.NodeWithLocation} */ (openingElement).end;
|
|
5964
|
+
const bodyMetaComments = (node.metadata?.elementLeadingComments ?? []).filter(
|
|
5965
|
+
(/** @type {AST.Comment} */ comment) =>
|
|
5966
|
+
typeof comment.start === 'number' && comment.start >= openingTagEnd,
|
|
5967
|
+
);
|
|
5968
|
+
const trailingComments = [
|
|
5969
|
+
...(node.closingElement?.leadingComments ?? []),
|
|
5970
|
+
...bodyMetaComments,
|
|
5971
|
+
].sort((a, b) => /** @type {number} */ (a.start) - /** @type {number} */ (b.start));
|
|
5972
|
+
const lastMeaningfulChild = [...(node.children ?? [])]
|
|
5973
|
+
.reverse()
|
|
5974
|
+
.find((child) => child.type !== 'JSXText' || child.value.trim());
|
|
5975
|
+
const closingCommentDocs = printElementBodyLineComments(trailingComments, lastMeaningfulChild);
|
|
5976
|
+
const hasClosingComments = closingCommentDocs.length > 0;
|
|
5977
|
+
// A comment-only element has no children; its comments live in `innerComments`.
|
|
5978
|
+
const innerCommentDocs = printElementBodyLineComments(node.innerComments);
|
|
5979
|
+
|
|
5868
5980
|
if (!hasChildren) {
|
|
5981
|
+
const bodyComments = [...innerCommentDocs, ...closingCommentDocs];
|
|
5982
|
+
if (bodyComments.length > 0) {
|
|
5983
|
+
return group([openingTag, indent(bodyComments), hardline, '</', tagName, '>']);
|
|
5984
|
+
}
|
|
5869
5985
|
return [openingTag, '</', tagName, '>'];
|
|
5870
5986
|
}
|
|
5871
5987
|
|
|
5872
|
-
//
|
|
5988
|
+
// A `@{ … }` code block is the whole body and hugs the tags: `<div>@{ … }</div>`.
|
|
5989
|
+
if (node.children.length === 1 && node.children[0].type === 'JSXCodeBlock') {
|
|
5990
|
+
return group([openingTag, path.call(print, 'children', 0), '</', tagName, '>']);
|
|
5991
|
+
}
|
|
5992
|
+
|
|
5993
|
+
// Format children - filter out empty text nodes and merge adjacent text nodes.
|
|
5994
|
+
// childNodes tracks the source node behind each doc (a text run is a single
|
|
5995
|
+
// JSXText) so the join can preserve authored blank lines.
|
|
5873
5996
|
const childrenDocs = [];
|
|
5997
|
+
const childNodes = [];
|
|
5874
5998
|
let currentText = '';
|
|
5999
|
+
let currentTextNode = null;
|
|
5875
6000
|
|
|
5876
6001
|
for (let i = 0; i < node.children.length; i++) {
|
|
5877
6002
|
const child = node.children[i];
|
|
5878
6003
|
|
|
5879
6004
|
if (child.type === 'JSXText') {
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
6005
|
+
if (hasComment(/** @type {AST.Node & AST.NodeWithMaybeComments} */ (child))) {
|
|
6006
|
+
if (currentText) {
|
|
6007
|
+
childrenDocs.push(currentText);
|
|
6008
|
+
childNodes.push(currentTextNode);
|
|
6009
|
+
currentText = '';
|
|
6010
|
+
currentTextNode = null;
|
|
6011
|
+
}
|
|
6012
|
+
const printedChild = path.call(print, 'children', i);
|
|
6013
|
+
if (printedChild !== '') {
|
|
6014
|
+
childrenDocs.push(printedChild);
|
|
6015
|
+
childNodes.push(child);
|
|
6016
|
+
}
|
|
6017
|
+
continue;
|
|
6018
|
+
}
|
|
6019
|
+
// Accumulate text content, preserving meaningful boundary spaces.
|
|
6020
|
+
const text = normalizeInlineJSXText(child.value);
|
|
6021
|
+
if (text) {
|
|
5883
6022
|
const nextChild = node.children[i + 1];
|
|
5884
6023
|
const afterNextChild = node.children[i + 2];
|
|
5885
6024
|
const nextText = afterNextChild?.type === 'JSXText' ? afterNextChild.value.trim() : '';
|
|
5886
6025
|
if (
|
|
5887
6026
|
tagName === 'tsrx' &&
|
|
5888
|
-
|
|
6027
|
+
text.trimEnd().endsWith('=') &&
|
|
5889
6028
|
nextChild?.type === 'JSXElement' &&
|
|
5890
6029
|
nextText === ';'
|
|
5891
6030
|
) {
|
|
5892
6031
|
if (currentText) {
|
|
5893
6032
|
childrenDocs.push(currentText);
|
|
6033
|
+
childNodes.push(currentTextNode);
|
|
5894
6034
|
currentText = '';
|
|
6035
|
+
currentTextNode = null;
|
|
5895
6036
|
}
|
|
5896
|
-
childrenDocs.push([
|
|
6037
|
+
childrenDocs.push([text.trim(), ' ', path.call(print, 'children', i + 1), ';']);
|
|
6038
|
+
childNodes.push(child);
|
|
5897
6039
|
i += 2;
|
|
5898
6040
|
continue;
|
|
5899
6041
|
}
|
|
5900
6042
|
|
|
5901
6043
|
if (currentText) {
|
|
5902
|
-
currentText += ' ' +
|
|
6044
|
+
currentText += currentText.endsWith(' ') || text.startsWith(' ') ? text : ' ' + text;
|
|
5903
6045
|
} else {
|
|
5904
|
-
currentText =
|
|
6046
|
+
currentText = text;
|
|
6047
|
+
currentTextNode = child;
|
|
5905
6048
|
}
|
|
5906
6049
|
}
|
|
5907
6050
|
} else {
|
|
5908
6051
|
// If we have accumulated text, push it before the non-text node
|
|
5909
6052
|
if (currentText) {
|
|
5910
6053
|
childrenDocs.push(currentText);
|
|
6054
|
+
childNodes.push(currentTextNode);
|
|
5911
6055
|
currentText = '';
|
|
6056
|
+
currentTextNode = null;
|
|
5912
6057
|
}
|
|
5913
6058
|
|
|
5914
6059
|
if (child.type === 'JSXExpressionContainer') {
|
|
5915
6060
|
// Handle JSX expression containers
|
|
5916
|
-
childrenDocs.push([
|
|
6061
|
+
childrenDocs.push([
|
|
6062
|
+
...printTemplateChildLeadingComments(child),
|
|
6063
|
+
'{',
|
|
6064
|
+
path.call(print, 'children', i, 'expression'),
|
|
6065
|
+
'}',
|
|
6066
|
+
]);
|
|
6067
|
+
childNodes.push(child);
|
|
5917
6068
|
} else {
|
|
5918
6069
|
// Handle nested JSX elements
|
|
5919
6070
|
childrenDocs.push(path.call(print, 'children', i));
|
|
6071
|
+
childNodes.push(child);
|
|
5920
6072
|
}
|
|
5921
6073
|
}
|
|
5922
6074
|
}
|
|
@@ -5924,37 +6076,71 @@ function printJSXElement(node, path, options, print) {
|
|
|
5924
6076
|
// Don't forget any remaining text
|
|
5925
6077
|
if (currentText) {
|
|
5926
6078
|
childrenDocs.push(currentText);
|
|
6079
|
+
childNodes.push(currentTextNode);
|
|
5927
6080
|
}
|
|
5928
6081
|
|
|
5929
|
-
//
|
|
5930
|
-
|
|
5931
|
-
|
|
6082
|
+
// A child with leading comments must break onto its own line, so the comment
|
|
6083
|
+
// reads above the child rather than being jammed onto the opening tag.
|
|
6084
|
+
const hasChildLeadingComments = node.children.some((child) => {
|
|
6085
|
+
const leadingComments = /** @type {AST.NodeWithMaybeComments} */ (child).leadingComments;
|
|
6086
|
+
return Array.isArray(leadingComments) && leadingComments.length > 0;
|
|
6087
|
+
});
|
|
6088
|
+
const forceMultiline = hasClosingComments || hasChildLeadingComments;
|
|
6089
|
+
|
|
6090
|
+
// Check if content can be inlined (single text node or single expression).
|
|
6091
|
+
// Trailing or child-leading comments force the multi-line layout. A single
|
|
6092
|
+
// text child stays inline when it fits and otherwise fills/wraps to printWidth.
|
|
6093
|
+
if (!forceMultiline && childrenDocs.length === 1 && typeof childrenDocs[0] === 'string') {
|
|
6094
|
+
// The open tag breaks for attributes independently; the text+closing get
|
|
6095
|
+
// their own group so the text only drops to its own (filled) lines when it
|
|
6096
|
+
// itself overflows — otherwise it hugs `>text</tag>`.
|
|
6097
|
+
return [
|
|
6098
|
+
openingTag,
|
|
6099
|
+
group([indent([softline, printRawText(childrenDocs[0])]), softline, '</', tagName, '>']),
|
|
6100
|
+
];
|
|
5932
6101
|
}
|
|
5933
6102
|
const meaningfulChildren = node.children.filter(
|
|
5934
|
-
(child) => child.type !== 'JSXText' || child.value.trim(),
|
|
6103
|
+
(/** @type {any} */ child) => child.type !== 'JSXText' || child.value.trim(),
|
|
5935
6104
|
);
|
|
5936
6105
|
const singleMeaningfulChild = meaningfulChildren.length === 1 ? meaningfulChildren[0] : null;
|
|
5937
6106
|
if (
|
|
6107
|
+
!forceMultiline &&
|
|
5938
6108
|
childrenDocs.length === 1 &&
|
|
5939
6109
|
singleMeaningfulChild?.type === 'JSXExpressionContainer' &&
|
|
5940
|
-
|
|
6110
|
+
isSimpleJSXExpressionChild(/** @type {AST.Node} */ (singleMeaningfulChild))
|
|
5941
6111
|
) {
|
|
5942
6112
|
return group([openingTag, childrenDocs[0], '</', tagName, '>']);
|
|
5943
6113
|
}
|
|
6114
|
+
if (
|
|
6115
|
+
!forceMultiline &&
|
|
6116
|
+
childrenDocs.length > 1 &&
|
|
6117
|
+
wasOriginallySingleLine(node) &&
|
|
6118
|
+
node.children.some((/** @type {any} */ child) => child.type === 'JSXText') &&
|
|
6119
|
+
node.children.every(
|
|
6120
|
+
(/** @type {any} */ child) =>
|
|
6121
|
+
child.type === 'JSXText' || isSimpleJSXExpressionChild(/** @type {AST.Node} */ (child)),
|
|
6122
|
+
)
|
|
6123
|
+
) {
|
|
6124
|
+
return group([openingTag, ...childrenDocs, '</', tagName, '>']);
|
|
6125
|
+
}
|
|
5944
6126
|
|
|
5945
|
-
// Multiple children or complex children - format with line breaks
|
|
6127
|
+
// Multiple children or complex children - format with line breaks. Text runs
|
|
6128
|
+
// fill/wrap to printWidth.
|
|
5946
6129
|
const formattedChildren = [];
|
|
5947
6130
|
for (let i = 0; i < childrenDocs.length; i++) {
|
|
5948
|
-
|
|
6131
|
+
const childDoc = childrenDocs[i];
|
|
6132
|
+
formattedChildren.push(typeof childDoc === 'string' ? printRawText(childDoc) : childDoc);
|
|
5949
6133
|
if (i < childrenDocs.length - 1) {
|
|
5950
|
-
|
|
6134
|
+
// Preserve a single authored blank line between children (2+ collapse to 1).
|
|
6135
|
+
const blank = getBlankLinesBetweenNodes(childNodes[i], leadingAnchor(childNodes[i + 1])) > 0;
|
|
6136
|
+
formattedChildren.push(blank ? [hardline, hardline] : hardline);
|
|
5951
6137
|
}
|
|
5952
6138
|
}
|
|
5953
6139
|
|
|
5954
6140
|
// Build the final element
|
|
5955
6141
|
return group([
|
|
5956
6142
|
openingTag,
|
|
5957
|
-
indent([hardline, ...formattedChildren]),
|
|
6143
|
+
indent([hardline, ...formattedChildren, ...closingCommentDocs]),
|
|
5958
6144
|
hardline,
|
|
5959
6145
|
'</',
|
|
5960
6146
|
tagName,
|
|
@@ -5977,23 +6163,46 @@ function printJSXFragment(node, path, options, print) {
|
|
|
5977
6163
|
return '<></>';
|
|
5978
6164
|
}
|
|
5979
6165
|
|
|
5980
|
-
//
|
|
6166
|
+
// A `@{ … }` code block is the whole body and hugs the tags: `<>@{ … }</>`.
|
|
6167
|
+
if (node.children.length === 1 && /** @type {any} */ (node.children[0]).type === 'JSXCodeBlock') {
|
|
6168
|
+
return group(['<>', path.call(print, 'children', 0), '</>']);
|
|
6169
|
+
}
|
|
6170
|
+
|
|
6171
|
+
// Format children - filter out empty text nodes. childNodes tracks the source
|
|
6172
|
+
// node behind each doc so the join can preserve authored blank lines.
|
|
5981
6173
|
const childrenDocs = [];
|
|
6174
|
+
const childNodes = [];
|
|
5982
6175
|
for (let i = 0; i < node.children.length; i++) {
|
|
5983
6176
|
const child = node.children[i];
|
|
5984
6177
|
|
|
5985
6178
|
if (child.type === 'JSXText') {
|
|
6179
|
+
if (hasComment(/** @type {AST.Node & AST.NodeWithMaybeComments} */ (child))) {
|
|
6180
|
+
const printedChild = path.call(print, 'children', i);
|
|
6181
|
+
if (printedChild !== '') {
|
|
6182
|
+
childrenDocs.push(printedChild);
|
|
6183
|
+
childNodes.push(child);
|
|
6184
|
+
}
|
|
6185
|
+
continue;
|
|
6186
|
+
}
|
|
5986
6187
|
// Handle JSX text nodes - trim whitespace and only include if not empty
|
|
5987
|
-
const text = child.value
|
|
6188
|
+
const text = printJSXTextChild(child.value);
|
|
5988
6189
|
if (text) {
|
|
5989
6190
|
childrenDocs.push(text);
|
|
6191
|
+
childNodes.push(child);
|
|
5990
6192
|
}
|
|
5991
6193
|
} else if (child.type === 'JSXExpressionContainer') {
|
|
5992
6194
|
// Handle JSX expression containers
|
|
5993
|
-
childrenDocs.push([
|
|
6195
|
+
childrenDocs.push([
|
|
6196
|
+
...printTemplateChildLeadingComments(child),
|
|
6197
|
+
'{',
|
|
6198
|
+
path.call(print, 'children', i, 'expression'),
|
|
6199
|
+
'}',
|
|
6200
|
+
]);
|
|
6201
|
+
childNodes.push(child);
|
|
5994
6202
|
} else {
|
|
5995
6203
|
// Handle nested JSX elements and fragments
|
|
5996
6204
|
childrenDocs.push(path.call(print, 'children', i));
|
|
6205
|
+
childNodes.push(child);
|
|
5997
6206
|
}
|
|
5998
6207
|
}
|
|
5999
6208
|
|
|
@@ -6001,13 +6210,33 @@ function printJSXFragment(node, path, options, print) {
|
|
|
6001
6210
|
if (childrenDocs.length === 1 && typeof childrenDocs[0] === 'string') {
|
|
6002
6211
|
return ['<>', childrenDocs[0], '</>'];
|
|
6003
6212
|
}
|
|
6213
|
+
const meaningfulChildren = node.children.filter(
|
|
6214
|
+
(child) => child.type !== 'JSXText' || child.value.trim(),
|
|
6215
|
+
);
|
|
6216
|
+
if (
|
|
6217
|
+
childrenDocs.length === 1 &&
|
|
6218
|
+
meaningfulChildren.length === 1 &&
|
|
6219
|
+
meaningfulChildren[0].type === 'JSXElement' &&
|
|
6220
|
+
wasOriginallySingleLine(node) &&
|
|
6221
|
+
!willBreak(childrenDocs[0])
|
|
6222
|
+
) {
|
|
6223
|
+
// Keep the fragment inline when it fits; otherwise expand `<>` onto its own
|
|
6224
|
+
// lines so a breaking single child reads as `<>\n <Child …/>\n</>` rather than
|
|
6225
|
+
// `<><Child` with only the child's attributes broken.
|
|
6226
|
+
return conditionalGroup([
|
|
6227
|
+
['<>', childrenDocs[0], '</>'],
|
|
6228
|
+
group(['<>', indent([hardline, childrenDocs[0]]), hardline, '</>']),
|
|
6229
|
+
]);
|
|
6230
|
+
}
|
|
6004
6231
|
|
|
6005
6232
|
// Multiple children or complex children - format with line breaks
|
|
6006
6233
|
const formattedChildren = [];
|
|
6007
6234
|
for (let i = 0; i < childrenDocs.length; i++) {
|
|
6008
6235
|
formattedChildren.push(childrenDocs[i]);
|
|
6009
6236
|
if (i < childrenDocs.length - 1) {
|
|
6010
|
-
|
|
6237
|
+
// Preserve a single authored blank line between children (2+ collapse to 1).
|
|
6238
|
+
const blank = getBlankLinesBetweenNodes(childNodes[i], leadingAnchor(childNodes[i + 1])) > 0;
|
|
6239
|
+
formattedChildren.push(blank ? [hardline, hardline] : hardline);
|
|
6011
6240
|
}
|
|
6012
6241
|
}
|
|
6013
6242
|
|
|
@@ -6015,6 +6244,169 @@ function printJSXFragment(node, path, options, print) {
|
|
|
6015
6244
|
return group(['<>', indent([hardline, ...formattedChildren]), hardline, '</>']);
|
|
6016
6245
|
}
|
|
6017
6246
|
|
|
6247
|
+
/**
|
|
6248
|
+
* Comments written inside an opening tag, before an attribute, are attached by
|
|
6249
|
+
* the parser to the next visited body child (positionally they sort before the
|
|
6250
|
+
* opening tag's end, but the child is visited first). Pull those out of the
|
|
6251
|
+
* children and return a map from attribute index to the comments that precede it,
|
|
6252
|
+
* so the element printer can render them in the opening tag instead of the body.
|
|
6253
|
+
* @param {AST.TSRXJSXElement} node
|
|
6254
|
+
* @returns {Map<number, AST.Comment[]>}
|
|
6255
|
+
*/
|
|
6256
|
+
function collectOpeningTagComments(node) {
|
|
6257
|
+
/** @type {Map<number, AST.Comment[]>} */
|
|
6258
|
+
const byAttr = new Map();
|
|
6259
|
+
const openingElement = /** @type {AST.NodeWithLocation} */ (node.openingElement);
|
|
6260
|
+
const attributes = /** @type {any[]} */ (node.openingElement?.attributes) ?? [];
|
|
6261
|
+
if (!openingElement || attributes.length === 0 || !Array.isArray(node.children)) {
|
|
6262
|
+
return byAttr;
|
|
6263
|
+
}
|
|
6264
|
+
const openingEnd = openingElement.end;
|
|
6265
|
+
/** @type {AST.Comment[]} */
|
|
6266
|
+
const collected = [];
|
|
6267
|
+
for (const child of node.children) {
|
|
6268
|
+
const lead = /** @type {AST.NodeWithMaybeComments} */ (child).leadingComments;
|
|
6269
|
+
if (!Array.isArray(lead) || lead.length === 0) continue;
|
|
6270
|
+
const keep = [];
|
|
6271
|
+
for (const comment of lead) {
|
|
6272
|
+
if (typeof comment.start === 'number' && comment.start < openingEnd) {
|
|
6273
|
+
collected.push(comment);
|
|
6274
|
+
} else {
|
|
6275
|
+
keep.push(comment);
|
|
6276
|
+
}
|
|
6277
|
+
}
|
|
6278
|
+
if (keep.length !== lead.length) {
|
|
6279
|
+
/** @type {any} */ (child).leadingComments = keep;
|
|
6280
|
+
}
|
|
6281
|
+
}
|
|
6282
|
+
if (collected.length === 0) return byAttr;
|
|
6283
|
+
collected.sort((a, b) => /** @type {number} */ (a.start) - /** @type {number} */ (b.start));
|
|
6284
|
+
let ci = 0;
|
|
6285
|
+
for (let ai = 0; ai < attributes.length; ai++) {
|
|
6286
|
+
const attrStart = /** @type {AST.NodeWithLocation} */ (attributes[ai]).start;
|
|
6287
|
+
/** @type {AST.Comment[]} */
|
|
6288
|
+
const forAttr = [];
|
|
6289
|
+
while (ci < collected.length && /** @type {number} */ (collected[ci].start) < attrStart) {
|
|
6290
|
+
forAttr.push(collected[ci]);
|
|
6291
|
+
ci++;
|
|
6292
|
+
}
|
|
6293
|
+
if (forAttr.length > 0) byAttr.set(ai, forAttr);
|
|
6294
|
+
}
|
|
6295
|
+
return byAttr;
|
|
6296
|
+
}
|
|
6297
|
+
|
|
6298
|
+
/**
|
|
6299
|
+
* Build doc parts for a template child's leading comments (each on its own line).
|
|
6300
|
+
* Used for `{expr}` children, whose `{ … }` form is printed inline by the JSX
|
|
6301
|
+
* printers and so would otherwise skip the node's attached leading comments.
|
|
6302
|
+
* @param {AST.Node & AST.NodeWithMaybeComments} child
|
|
6303
|
+
* @returns {Doc[]}
|
|
6304
|
+
*/
|
|
6305
|
+
function printTemplateChildLeadingComments(child) {
|
|
6306
|
+
const comments = child.leadingComments;
|
|
6307
|
+
if (!comments || comments.length === 0) {
|
|
6308
|
+
return [];
|
|
6309
|
+
}
|
|
6310
|
+
/** @type {Doc[]} */
|
|
6311
|
+
const parts = [];
|
|
6312
|
+
for (let i = 0; i < comments.length; i++) {
|
|
6313
|
+
const comment = comments[i];
|
|
6314
|
+
if (comment.type === 'Line') {
|
|
6315
|
+
parts.push('//' + comment.value);
|
|
6316
|
+
} else if (comment.type === 'Block') {
|
|
6317
|
+
parts.push('/*' + comment.value + '*/');
|
|
6318
|
+
}
|
|
6319
|
+
parts.push(hardline);
|
|
6320
|
+
const next = comments[i + 1];
|
|
6321
|
+
if (next && getBlankLinesBetweenNodes(comment, next) > 0) {
|
|
6322
|
+
parts.push(hardline);
|
|
6323
|
+
}
|
|
6324
|
+
}
|
|
6325
|
+
return parts;
|
|
6326
|
+
}
|
|
6327
|
+
|
|
6328
|
+
/**
|
|
6329
|
+
* Build doc parts for `//` line comments attached to an element body — trailing
|
|
6330
|
+
* comments before `</tag>` (`closingElement.leadingComments`) or the comments of a
|
|
6331
|
+
* comment-only element (`innerComments`). Block comments are intentionally skipped:
|
|
6332
|
+
* they survive in the adjacent JSXText value and are already rendered as text, so
|
|
6333
|
+
* emitting them here would duplicate them. Each comment is emitted on its own line
|
|
6334
|
+
* at the children indent.
|
|
6335
|
+
* @param {AST.Comment[] | null | undefined} commentList
|
|
6336
|
+
* @param {any} [previousNode]
|
|
6337
|
+
* @returns {Doc[]}
|
|
6338
|
+
*/
|
|
6339
|
+
function printElementBodyLineComments(commentList, previousNode = null) {
|
|
6340
|
+
const comments = (commentList ?? []).filter((comment) => comment.type === 'Line');
|
|
6341
|
+
if (comments.length === 0) {
|
|
6342
|
+
return [];
|
|
6343
|
+
}
|
|
6344
|
+
/** @type {Doc[]} */
|
|
6345
|
+
const parts = [];
|
|
6346
|
+
/** @type {AST.Node | AST.Comment | null | undefined} */
|
|
6347
|
+
let prev = previousNode;
|
|
6348
|
+
for (let i = 0; i < comments.length; i++) {
|
|
6349
|
+
parts.push(hardline);
|
|
6350
|
+
// Preserve a blank line before this comment if one existed in source.
|
|
6351
|
+
if (prev && getBlankLinesBetweenNodes(prev, comments[i]) > 0) {
|
|
6352
|
+
parts.push(hardline);
|
|
6353
|
+
}
|
|
6354
|
+
parts.push('//' + comments[i].value);
|
|
6355
|
+
prev = comments[i];
|
|
6356
|
+
}
|
|
6357
|
+
return parts;
|
|
6358
|
+
}
|
|
6359
|
+
|
|
6360
|
+
/**
|
|
6361
|
+
* Print a TSRX code block: setup statements then the single render output.
|
|
6362
|
+
* Callers in element/fragment body position hug it to the surrounding tags;
|
|
6363
|
+
* on its own as an arrow body it stands alone.
|
|
6364
|
+
* @param {AST.JSXCodeBlock} node
|
|
6365
|
+
* @param {AstPath<AST.JSXCodeBlock>} path
|
|
6366
|
+
* @param {RippleFormatOptions} options
|
|
6367
|
+
* @param {PrintFn} print
|
|
6368
|
+
* @returns {Doc}
|
|
6369
|
+
*/
|
|
6370
|
+
function printJSXCodeBlock(node, path, options, print) {
|
|
6371
|
+
/** @type {Doc[]} */
|
|
6372
|
+
const parts = [];
|
|
6373
|
+
for (let i = 0; i < node.body.length; i++) {
|
|
6374
|
+
parts.push(path.call(print, 'body', i));
|
|
6375
|
+
if (i < node.body.length - 1) {
|
|
6376
|
+
parts.push(
|
|
6377
|
+
shouldAddBlankLine(node.body[i], node.body[i + 1]) ? [hardline, hardline] : hardline,
|
|
6378
|
+
);
|
|
6379
|
+
}
|
|
6380
|
+
}
|
|
6381
|
+
if (node.render) {
|
|
6382
|
+
if (node.body.length > 0) {
|
|
6383
|
+
// Preserve a blank line between the last setup statement and the render
|
|
6384
|
+
// output (measured to the render's leading comment, if any).
|
|
6385
|
+
const last = node.body[node.body.length - 1];
|
|
6386
|
+
const renderStart =
|
|
6387
|
+
/** @type {AST.NodeWithMaybeComments} */ (node.render).leadingComments?.[0] ?? node.render;
|
|
6388
|
+
parts.push(
|
|
6389
|
+
getBlankLinesBetweenNodes(last, renderStart) > 0 ? [hardline, hardline] : hardline,
|
|
6390
|
+
);
|
|
6391
|
+
}
|
|
6392
|
+
parts.push(path.call(print, 'render'));
|
|
6393
|
+
}
|
|
6394
|
+
// Trailing comments after the last statement/render inside the block.
|
|
6395
|
+
const innerCommentDocs = printElementBodyLineComments(node.innerComments);
|
|
6396
|
+
if (innerCommentDocs.length > 0) {
|
|
6397
|
+
const lastNode = node.render ?? node.body[node.body.length - 1];
|
|
6398
|
+
const firstComment = (node.innerComments ?? []).find((c) => c.type === 'Line');
|
|
6399
|
+
if (lastNode && firstComment && getBlankLinesBetweenNodes(lastNode, firstComment) > 0) {
|
|
6400
|
+
parts.push(hardline);
|
|
6401
|
+
}
|
|
6402
|
+
parts.push(...innerCommentDocs);
|
|
6403
|
+
}
|
|
6404
|
+
if (parts.length === 0) {
|
|
6405
|
+
return '@{}';
|
|
6406
|
+
}
|
|
6407
|
+
return group(['@{', indent([hardline, ...parts]), hardline, '}']);
|
|
6408
|
+
}
|
|
6409
|
+
|
|
6018
6410
|
/**
|
|
6019
6411
|
* Print a JSX attribute
|
|
6020
6412
|
* @param {ESTreeJSX.JSXAttribute} attr - The JSX attribute node
|
|
@@ -6026,6 +6418,10 @@ function printJSXFragment(node, path, options, print) {
|
|
|
6026
6418
|
function printJSXAttribute(attr, path, options, print) {
|
|
6027
6419
|
const name = /** @type {ESTreeJSX.JSXIdentifier} */ (attr.name).name;
|
|
6028
6420
|
|
|
6421
|
+
if (attr.shorthand) {
|
|
6422
|
+
return ['{', name, '}'];
|
|
6423
|
+
}
|
|
6424
|
+
|
|
6029
6425
|
if (!attr.value) {
|
|
6030
6426
|
return name;
|
|
6031
6427
|
}
|
|
@@ -6043,12 +6439,16 @@ function printJSXAttribute(attr, path, options, print) {
|
|
|
6043
6439
|
|
|
6044
6440
|
if (attr.value.type === 'JSXExpressionContainer') {
|
|
6045
6441
|
const expression = attr.value.expression;
|
|
6442
|
+
if (expression.type === 'Literal' && typeof expression.value === 'string') {
|
|
6443
|
+
const quote = options.jsxSingleQuote ? "'" : '"';
|
|
6444
|
+
return [name, '=', quote, /** @type {string} */ (expression.value), quote];
|
|
6445
|
+
}
|
|
6046
6446
|
const exprDoc = path.call(
|
|
6047
6447
|
(valuePath) => print(valuePath, { isInAttribute: true }),
|
|
6048
6448
|
'value',
|
|
6049
6449
|
'expression',
|
|
6050
6450
|
);
|
|
6051
|
-
if (shouldBreakAttributeExpressionClosingBrace(expression)) {
|
|
6451
|
+
if (shouldBreakAttributeExpressionClosingBrace(expression, options, attr)) {
|
|
6052
6452
|
return [name, '={', exprDoc, hardline, '}'];
|
|
6053
6453
|
}
|
|
6054
6454
|
return [name, '={', exprDoc, '}'];
|
|
@@ -6058,20 +6458,33 @@ function printJSXAttribute(attr, path, options, print) {
|
|
|
6058
6458
|
}
|
|
6059
6459
|
|
|
6060
6460
|
/**
|
|
6061
|
-
* Print a JSX
|
|
6062
|
-
* @param {AST.Node} node - The JSX
|
|
6461
|
+
* Print a JSX element name.
|
|
6462
|
+
* @param {AST.Node} node - The JSX element name node
|
|
6063
6463
|
* @returns {string}
|
|
6064
6464
|
*/
|
|
6065
|
-
function
|
|
6465
|
+
function printJSXElementName(node) {
|
|
6066
6466
|
if (node.type === 'JSXIdentifier') {
|
|
6067
|
-
return node.name;
|
|
6467
|
+
return (isDynamicJSXIdentifier(node) ? '@' : '') + node.name;
|
|
6068
6468
|
}
|
|
6069
6469
|
if (node.type === 'JSXMemberExpression') {
|
|
6070
|
-
return
|
|
6470
|
+
return printJSXElementName(node.object) + '.' + printJSXElementName(node.property);
|
|
6471
|
+
}
|
|
6472
|
+
if (node.type === 'JSXNamespacedName') {
|
|
6473
|
+
const namespace_name = node.namespace.name;
|
|
6474
|
+
const local_name = node.name.name;
|
|
6475
|
+
return namespace_name + ':' + local_name;
|
|
6071
6476
|
}
|
|
6072
6477
|
return 'Unknown';
|
|
6073
6478
|
}
|
|
6074
6479
|
|
|
6480
|
+
/**
|
|
6481
|
+
* @param {ESTreeJSX.JSXIdentifier} node
|
|
6482
|
+
* @returns {boolean}
|
|
6483
|
+
*/
|
|
6484
|
+
function isDynamicJSXIdentifier(node) {
|
|
6485
|
+
return /** @type {{ dynamic?: boolean }} */ (node).dynamic === true;
|
|
6486
|
+
}
|
|
6487
|
+
|
|
6075
6488
|
/**
|
|
6076
6489
|
* Print a member expression as simple string (for element tag names)
|
|
6077
6490
|
* @param {AST.Node} node - The node to print
|
|
@@ -6080,6 +6493,22 @@ function printJSXMemberExpression(node) {
|
|
|
6080
6493
|
* @returns {string}
|
|
6081
6494
|
*/
|
|
6082
6495
|
function printMemberExpressionSimple(node, options, computed = false) {
|
|
6496
|
+
if (node.type === 'JSXIdentifier') {
|
|
6497
|
+
return (isDynamicJSXIdentifier(node) ? '@' : '') + node.name;
|
|
6498
|
+
}
|
|
6499
|
+
|
|
6500
|
+
if (node.type === 'JSXMemberExpression') {
|
|
6501
|
+
return (
|
|
6502
|
+
printMemberExpressionSimple(node.object, options) +
|
|
6503
|
+
'.' +
|
|
6504
|
+
printMemberExpressionSimple(node.property, options, true)
|
|
6505
|
+
);
|
|
6506
|
+
}
|
|
6507
|
+
|
|
6508
|
+
if (node.type === 'JSXNamespacedName') {
|
|
6509
|
+
return node.namespace.name + ':' + node.name.name;
|
|
6510
|
+
}
|
|
6511
|
+
|
|
6083
6512
|
if (node.type === 'Identifier') {
|
|
6084
6513
|
return (computed ? '' : node.tracked ? '@' : '') + node.name;
|
|
6085
6514
|
}
|
|
@@ -6127,7 +6556,7 @@ function is_attribute_value_breakable(value, is_nested_in_object = false) {
|
|
|
6127
6556
|
}
|
|
6128
6557
|
|
|
6129
6558
|
/**
|
|
6130
|
-
* Print a
|
|
6559
|
+
* Print a JSX element node
|
|
6131
6560
|
* @param {AST.Element} element - The element node
|
|
6132
6561
|
* @param {AstPath<AST.Element>} path - The AST path
|
|
6133
6562
|
* @param {RippleFormatOptions} options - Prettier options
|
|
@@ -6135,7 +6564,7 @@ function is_attribute_value_breakable(value, is_nested_in_object = false) {
|
|
|
6135
6564
|
* @returns {Doc}
|
|
6136
6565
|
*/
|
|
6137
6566
|
function printElement(element, path, options, print) {
|
|
6138
|
-
const node = /** @type {
|
|
6567
|
+
const node = /** @type {any} */ (element);
|
|
6139
6568
|
const tagName = printMemberExpressionSimple(node.id, options);
|
|
6140
6569
|
const openingElement = /** @type {any} */ (node.openingElement);
|
|
6141
6570
|
/** @type {Doc} */
|
|
@@ -6143,7 +6572,7 @@ function printElement(element, path, options, print) {
|
|
|
6143
6572
|
if (openingElement?.typeArguments) {
|
|
6144
6573
|
typeArgsDoc = path.call(print, 'openingElement', 'typeArguments');
|
|
6145
6574
|
}
|
|
6146
|
-
const elementLeadingComments = getElementLeadingComments(node);
|
|
6575
|
+
const elementLeadingComments = getElementLeadingComments(/** @type {any} */ (node));
|
|
6147
6576
|
|
|
6148
6577
|
// `metadata.elementLeadingComments` may include comments that actually appear *inside* the element
|
|
6149
6578
|
// body (after the opening tag). Those must not be hoisted before the element.
|
|
@@ -6178,7 +6607,7 @@ function printElement(element, path, options, print) {
|
|
|
6178
6607
|
const openingEnd = /** @type {AST.NodeWithLocation} */ (node.openingElement).end;
|
|
6179
6608
|
for (const child of node.children) {
|
|
6180
6609
|
if (
|
|
6181
|
-
(child.type === '
|
|
6610
|
+
(child.type === 'JSXExpressionContainer' || child.type === 'JSXText') &&
|
|
6182
6611
|
Array.isArray(child.leadingComments)
|
|
6183
6612
|
) {
|
|
6184
6613
|
for (const comment of child.leadingComments) {
|
|
@@ -6255,11 +6684,14 @@ function printElement(element, path, options, print) {
|
|
|
6255
6684
|
parts.push(attrLineBreak);
|
|
6256
6685
|
const attrDoc = print(attrPath);
|
|
6257
6686
|
parts.push(attrDoc);
|
|
6258
|
-
const attr_node = /** @type {
|
|
6687
|
+
const attr_node = /** @type {ESTreeJSX.JSXAttribute | ESTreeJSX.JSXSpreadAttribute} */ (
|
|
6688
|
+
/** @type {unknown} */ (attrPath.node)
|
|
6689
|
+
);
|
|
6259
6690
|
if (
|
|
6260
6691
|
!hasBreakingAttribute &&
|
|
6261
6692
|
(willBreak(attrDoc) ||
|
|
6262
|
-
(attr_node.type === '
|
|
6693
|
+
(attr_node.type === 'JSXAttribute' &&
|
|
6694
|
+
is_attribute_value_breakable(/** @type {any} */ (attr_node.value))))
|
|
6263
6695
|
) {
|
|
6264
6696
|
hasBreakingAttribute = true;
|
|
6265
6697
|
}
|
|
@@ -6367,15 +6799,17 @@ function printElement(element, path, options, print) {
|
|
|
6367
6799
|
}
|
|
6368
6800
|
}
|
|
6369
6801
|
|
|
6370
|
-
const isTextLikeChild =
|
|
6802
|
+
const isTextLikeChild =
|
|
6803
|
+
currentChild.type === 'JSXExpressionContainer' || currentChild.type === 'JSXText';
|
|
6371
6804
|
const hasTextLeadingComments =
|
|
6372
6805
|
shouldLiftTextLevelComments &&
|
|
6373
6806
|
isTextLikeChild &&
|
|
6374
6807
|
Array.isArray(currentChild.leadingComments) &&
|
|
6375
6808
|
currentChild.leadingComments.length > 0;
|
|
6809
|
+
const currentChildAny = /** @type {any} */ (currentChild);
|
|
6376
6810
|
const rawExpressionLeadingComments =
|
|
6377
|
-
isTextLikeChild && Array.isArray(
|
|
6378
|
-
?
|
|
6811
|
+
isTextLikeChild && Array.isArray(currentChildAny.expression?.leadingComments)
|
|
6812
|
+
? currentChildAny.expression.leadingComments
|
|
6379
6813
|
: null;
|
|
6380
6814
|
const elementBodyLeadingComments =
|
|
6381
6815
|
hasTextLeadingComments && node.openingElement
|
|
@@ -6481,10 +6915,10 @@ function printElement(element, path, options, print) {
|
|
|
6481
6915
|
: nextChild;
|
|
6482
6916
|
const whitespaceLinesCount = getBlankLinesBetweenNodes(currentChild, whitespaceTarget);
|
|
6483
6917
|
const isTextOrExpressionChild =
|
|
6484
|
-
currentChild.type === '
|
|
6485
|
-
currentChild.type === '
|
|
6486
|
-
nextChild.type === '
|
|
6487
|
-
nextChild.type === '
|
|
6918
|
+
currentChild.type === 'JSXExpressionContainer' ||
|
|
6919
|
+
currentChild.type === 'JSXText' ||
|
|
6920
|
+
nextChild.type === 'JSXExpressionContainer' ||
|
|
6921
|
+
nextChild.type === 'JSXText';
|
|
6488
6922
|
|
|
6489
6923
|
if (whitespaceLinesCount > 0) {
|
|
6490
6924
|
finalChildren.push(hardline);
|
|
@@ -6535,10 +6969,15 @@ function printElement(element, path, options, print) {
|
|
|
6535
6969
|
|
|
6536
6970
|
const closingTag = ['</', tagName, '>'];
|
|
6537
6971
|
let elementOutput;
|
|
6972
|
+
const shouldTryInlineMultipleChildren =
|
|
6973
|
+
!openingTagAlwaysBreaks &&
|
|
6974
|
+
fallbackCommentParts.length === 0 &&
|
|
6975
|
+
closingElementComments.length === 0 &&
|
|
6976
|
+
shouldTryInlineMultipleTextChildren(node);
|
|
6538
6977
|
|
|
6539
6978
|
if (finalChildren.length === 1) {
|
|
6540
6979
|
const child = finalChildren[0];
|
|
6541
|
-
const firstChild = node.children[0];
|
|
6980
|
+
const firstChild = /** @type {any} */ (node.children[0]);
|
|
6542
6981
|
const isNonSelfClosingElement =
|
|
6543
6982
|
firstChild && firstChild.type === 'Element' && !firstChild.selfClosing;
|
|
6544
6983
|
const isElementChild = firstChild && firstChild.type === 'Element';
|
|
@@ -6569,63 +7008,15 @@ function printElement(element, path, options, print) {
|
|
|
6569
7008
|
} else {
|
|
6570
7009
|
elementOutput = [openingTag, indent([hardline, ...finalChildren]), hardline, closingTag];
|
|
6571
7010
|
}
|
|
7011
|
+
} else if (shouldTryInlineMultipleChildren) {
|
|
7012
|
+
const inlineChildren = path.map(print, 'children');
|
|
7013
|
+
elementOutput = conditionalGroup([
|
|
7014
|
+
group([openingTag, ...inlineChildren, closingTag]),
|
|
7015
|
+
[openingTag, indent([hardline, ...finalChildren]), hardline, closingTag],
|
|
7016
|
+
]);
|
|
6572
7017
|
} else {
|
|
6573
7018
|
elementOutput = group([openingTag, indent([hardline, ...finalChildren]), hardline, closingTag]);
|
|
6574
7019
|
}
|
|
6575
7020
|
|
|
6576
7021
|
return leadingCommentParts.length > 0 ? [...leadingCommentParts, elementOutput] : elementOutput;
|
|
6577
7022
|
}
|
|
6578
|
-
|
|
6579
|
-
/**
|
|
6580
|
-
* Print a Ripple attribute node
|
|
6581
|
-
* @param {AST.Attribute} node - The attribute node
|
|
6582
|
-
* @param {AstPath<AST.Attribute>} path - The AST path
|
|
6583
|
-
* @param {RippleFormatOptions} options - Prettier options
|
|
6584
|
-
* @param {PrintFn} print - Print callback
|
|
6585
|
-
* @returns {Doc[]}
|
|
6586
|
-
*/
|
|
6587
|
-
function printAttribute(node, path, options, print) {
|
|
6588
|
-
/** @type {Doc[]} */
|
|
6589
|
-
const parts = [];
|
|
6590
|
-
|
|
6591
|
-
// Handle shorthand syntax: {id} instead of id={id}
|
|
6592
|
-
// Check if either node.shorthand is true, OR if the value is an Identifier with the same name
|
|
6593
|
-
const isShorthand =
|
|
6594
|
-
node.shorthand ||
|
|
6595
|
-
(node.value && node.value.type === 'Identifier' && node.value.name === node.name.name);
|
|
6596
|
-
|
|
6597
|
-
if (isShorthand) {
|
|
6598
|
-
parts.push('{');
|
|
6599
|
-
parts.push(node.name.name);
|
|
6600
|
-
parts.push('}');
|
|
6601
|
-
return parts;
|
|
6602
|
-
}
|
|
6603
|
-
|
|
6604
|
-
parts.push(node.name.name);
|
|
6605
|
-
|
|
6606
|
-
if (node.value) {
|
|
6607
|
-
if (node.value.type === 'Literal' && typeof node.value.value === 'string') {
|
|
6608
|
-
// String literals don't need curly braces
|
|
6609
|
-
// Use jsxSingleQuote option if available, otherwise use double quotes
|
|
6610
|
-
parts.push('=');
|
|
6611
|
-
const useJsxSingleQuote = options.jsxSingleQuote === true;
|
|
6612
|
-
parts.push(
|
|
6613
|
-
formatStringLiteral(node.value.value, {
|
|
6614
|
-
...options,
|
|
6615
|
-
singleQuote: useJsxSingleQuote,
|
|
6616
|
-
}),
|
|
6617
|
-
);
|
|
6618
|
-
} else {
|
|
6619
|
-
// All other values need curly braces: numbers, booleans, null, expressions, etc.
|
|
6620
|
-
parts.push('={');
|
|
6621
|
-
// Pass inline context for attribute values (keep objects compact)
|
|
6622
|
-
parts.push(path.call((attrPath) => print(attrPath, { isInAttribute: true }), 'value'));
|
|
6623
|
-
if (shouldBreakAttributeExpressionClosingBrace(node.value)) {
|
|
6624
|
-
parts.push(hardline);
|
|
6625
|
-
}
|
|
6626
|
-
parts.push('}');
|
|
6627
|
-
}
|
|
6628
|
-
}
|
|
6629
|
-
|
|
6630
|
-
return parts;
|
|
6631
|
-
}
|