@tsrx/prettier-plugin 0.3.72 → 0.3.76
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 +798 -359
- package/src/index.test.js +5737 -14
package/src/index.js
CHANGED
|
@@ -42,16 +42,16 @@ const { replaceEndOfLine, willBreak } = utils;
|
|
|
42
42
|
/** @type {import('prettier').Plugin['languages']} */
|
|
43
43
|
export const languages = [
|
|
44
44
|
{
|
|
45
|
-
name: '
|
|
46
|
-
parsers: ['
|
|
45
|
+
name: 'tsrx',
|
|
46
|
+
parsers: ['tsrx'],
|
|
47
47
|
extensions: ['.tsrx'],
|
|
48
|
-
vscodeLanguageIds: ['ripple'],
|
|
48
|
+
vscodeLanguageIds: ['tsrx', 'ripple'],
|
|
49
49
|
},
|
|
50
50
|
];
|
|
51
51
|
|
|
52
52
|
/** @type {import('prettier').Plugin['parsers']} */
|
|
53
53
|
export const parsers = {
|
|
54
|
-
|
|
54
|
+
tsrx: {
|
|
55
55
|
astFormat: 'ripple-ast',
|
|
56
56
|
/**
|
|
57
57
|
* @param {string} text
|
|
@@ -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,27 +1730,14 @@ 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
|
-
const trackedPrefix = node.tracked ? '@' : '';
|
|
1698
1735
|
let identifierContent;
|
|
1699
1736
|
if (node.typeAnnotation) {
|
|
1700
1737
|
const optionalMarker = node.optional ? '?' : '';
|
|
1701
|
-
identifierContent = [
|
|
1702
|
-
trackedPrefix + node.name,
|
|
1703
|
-
optionalMarker,
|
|
1704
|
-
': ',
|
|
1705
|
-
path.call(print, 'typeAnnotation'),
|
|
1706
|
-
];
|
|
1738
|
+
identifierContent = [node.name, optionalMarker, ': ', path.call(print, 'typeAnnotation')];
|
|
1707
1739
|
} else {
|
|
1708
|
-
identifierContent =
|
|
1740
|
+
identifierContent = node.name;
|
|
1709
1741
|
}
|
|
1710
1742
|
// Preserve parentheses for type-cast identifiers, but only if:
|
|
1711
1743
|
// 1. The identifier itself is marked as parenthesized
|
|
@@ -2120,6 +2152,10 @@ function printRippleNode(node, path, options, print, args) {
|
|
|
2120
2152
|
nodeContent = printTSCallSignatureDeclaration(node, path, options, print);
|
|
2121
2153
|
break;
|
|
2122
2154
|
|
|
2155
|
+
case 'TSConstructSignatureDeclaration':
|
|
2156
|
+
nodeContent = printTSConstructSignatureDeclaration(node, path, options, print);
|
|
2157
|
+
break;
|
|
2158
|
+
|
|
2123
2159
|
case 'TSEnumMember':
|
|
2124
2160
|
nodeContent = printTSEnumMember(node, path, options, print);
|
|
2125
2161
|
break;
|
|
@@ -2245,16 +2281,17 @@ function printRippleNode(node, path, options, print, args) {
|
|
|
2245
2281
|
break;
|
|
2246
2282
|
}
|
|
2247
2283
|
|
|
2248
|
-
case '
|
|
2249
|
-
nodeContent =
|
|
2250
|
-
break;
|
|
2251
|
-
|
|
2252
|
-
case 'TsxCompat':
|
|
2253
|
-
nodeContent = printTsxCompat(node, path, options, print);
|
|
2284
|
+
case 'JSXCodeBlock':
|
|
2285
|
+
nodeContent = printJSXCodeBlock(node, path, options, print);
|
|
2254
2286
|
break;
|
|
2255
2287
|
|
|
2256
|
-
case '
|
|
2257
|
-
nodeContent =
|
|
2288
|
+
case 'JSXStyleElement':
|
|
2289
|
+
nodeContent = printJSXElement(
|
|
2290
|
+
/** @type {ESTreeJSX.JSXElement} */ (/** @type {unknown} */ (node)),
|
|
2291
|
+
path,
|
|
2292
|
+
options,
|
|
2293
|
+
print,
|
|
2294
|
+
);
|
|
2258
2295
|
break;
|
|
2259
2296
|
|
|
2260
2297
|
case 'JSXElement':
|
|
@@ -2266,7 +2303,7 @@ function printRippleNode(node, path, options, print, args) {
|
|
|
2266
2303
|
break;
|
|
2267
2304
|
|
|
2268
2305
|
case 'JSXText':
|
|
2269
|
-
nodeContent = node.value;
|
|
2306
|
+
nodeContent = printRawText(node.value);
|
|
2270
2307
|
break;
|
|
2271
2308
|
|
|
2272
2309
|
case 'JSXEmptyExpression':
|
|
@@ -2279,28 +2316,12 @@ function printRippleNode(node, path, options, print, args) {
|
|
|
2279
2316
|
}
|
|
2280
2317
|
break;
|
|
2281
2318
|
|
|
2282
|
-
case '
|
|
2283
|
-
nodeContent =
|
|
2284
|
-
break;
|
|
2285
|
-
|
|
2286
|
-
case 'TSRXExpression': {
|
|
2287
|
-
const expressionDoc = suppressExpressionLeadingComments
|
|
2288
|
-
? path.call((exprPath) => print(exprPath, { suppressLeadingComments: true }), 'expression')
|
|
2289
|
-
: path.call(print, 'expression');
|
|
2290
|
-
nodeContent = ['{', expressionDoc, '}'];
|
|
2319
|
+
case 'JSXAttribute':
|
|
2320
|
+
nodeContent = printJSXAttribute(node, path, options, print);
|
|
2291
2321
|
break;
|
|
2292
|
-
}
|
|
2293
|
-
|
|
2294
|
-
case 'Text': {
|
|
2295
|
-
if (typeof node.raw === 'string') {
|
|
2296
|
-
nodeContent = printRawText(node.raw);
|
|
2297
|
-
break;
|
|
2298
|
-
}
|
|
2299
2322
|
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
: path.call(print, 'expression');
|
|
2303
|
-
nodeContent = ['{', expressionDoc, '}'];
|
|
2323
|
+
case 'JSXSpreadAttribute': {
|
|
2324
|
+
nodeContent = ['{...', path.call(print, 'argument'), '}'];
|
|
2304
2325
|
break;
|
|
2305
2326
|
}
|
|
2306
2327
|
|
|
@@ -2527,7 +2548,10 @@ function printVariableDeclaration(node, path, options, print) {
|
|
|
2527
2548
|
const isForLoopInit =
|
|
2528
2549
|
(parentNode && parentNode.type === 'ForStatement' && parentNode.init === node) ||
|
|
2529
2550
|
(parentNode && parentNode.type === 'ForOfStatement' && parentNode.left === node) ||
|
|
2530
|
-
(parentNode && parentNode.type === 'ForInStatement' && parentNode.left === node)
|
|
2551
|
+
(parentNode && parentNode.type === 'ForInStatement' && parentNode.left === node) ||
|
|
2552
|
+
(parentNode &&
|
|
2553
|
+
parentNode.type === 'JSXForExpression' &&
|
|
2554
|
+
(parentNode.left === node || parentNode.init === node));
|
|
2531
2555
|
|
|
2532
2556
|
const declarations = path.map(print, 'declarations');
|
|
2533
2557
|
const declarationParts = join(', ', declarations);
|
|
@@ -2672,6 +2696,12 @@ function printArrowFunction(node, path, options, print, args) {
|
|
|
2672
2696
|
if (shouldBreakBody) {
|
|
2673
2697
|
parts.push(' =>', indent([hardline, bodyContent]));
|
|
2674
2698
|
} else {
|
|
2699
|
+
if (isTemplateExpression(node.body)) {
|
|
2700
|
+
return conditionalGroup([
|
|
2701
|
+
group([...parts, ' => ', bodyContent]),
|
|
2702
|
+
group([...parts, ' =>', indent([hardline, bodyContent])]),
|
|
2703
|
+
]);
|
|
2704
|
+
}
|
|
2675
2705
|
parts.push(
|
|
2676
2706
|
' =>',
|
|
2677
2707
|
group(indent(line), { id: groupId }),
|
|
@@ -2689,21 +2719,26 @@ function printArrowFunction(node, path, options, print, args) {
|
|
|
2689
2719
|
* @returns {boolean}
|
|
2690
2720
|
*/
|
|
2691
2721
|
function isTemplateExpression(node) {
|
|
2692
|
-
return
|
|
2693
|
-
node.type === 'TsxCompat' ||
|
|
2694
|
-
node.type === 'TsrxFragment' ||
|
|
2695
|
-
node.type === 'JSXElement' ||
|
|
2696
|
-
node.type === 'JSXFragment'
|
|
2697
|
-
);
|
|
2722
|
+
return node.type === 'JSXElement' || node.type === 'JSXFragment';
|
|
2698
2723
|
}
|
|
2699
2724
|
|
|
2700
2725
|
/**
|
|
2701
2726
|
* Check whether a braced attribute expression should close on its own line.
|
|
2702
2727
|
* @param {AST.Node} node - The expression inside the attribute braces
|
|
2728
|
+
* @param {RippleFormatOptions} options
|
|
2729
|
+
* @param {AST.Node} [attributeNode]
|
|
2703
2730
|
* @returns {boolean}
|
|
2704
2731
|
*/
|
|
2705
|
-
function shouldBreakAttributeExpressionClosingBrace(node) {
|
|
2706
|
-
return
|
|
2732
|
+
function shouldBreakAttributeExpressionClosingBrace(node, options, attributeNode = node) {
|
|
2733
|
+
return (
|
|
2734
|
+
node.type === 'ArrowFunctionExpression' &&
|
|
2735
|
+
node.body &&
|
|
2736
|
+
isTemplateExpression(node.body) &&
|
|
2737
|
+
sourceSpanExceedsPrintWidth(
|
|
2738
|
+
/** @type {AST.NodeWithLocation} */ (/** @type {unknown} */ (attributeNode ?? node)),
|
|
2739
|
+
options,
|
|
2740
|
+
)
|
|
2741
|
+
);
|
|
2707
2742
|
}
|
|
2708
2743
|
|
|
2709
2744
|
/**
|
|
@@ -2920,9 +2955,6 @@ function sourceSpanExceedsPrintWidth(node, options) {
|
|
|
2920
2955
|
* @returns {boolean}
|
|
2921
2956
|
*/
|
|
2922
2957
|
function shouldBreakArrowExpressionBody(node, options, args) {
|
|
2923
|
-
if (args?.isInAttribute && isTemplateExpression(node)) {
|
|
2924
|
-
return true;
|
|
2925
|
-
}
|
|
2926
2958
|
return (
|
|
2927
2959
|
(node.type === 'BinaryExpression' || node.type === 'LogicalExpression') &&
|
|
2928
2960
|
sourceSpanExceedsPrintWidth(/** @type {AST.NodeWithLocation} */ (node), options)
|
|
@@ -3295,9 +3327,10 @@ function extractAndPrintLeadingComments(node) {
|
|
|
3295
3327
|
* @param {AstPath<AST.IfStatement>} path - The AST path
|
|
3296
3328
|
* @param {RippleFormatOptions} options - Prettier options
|
|
3297
3329
|
* @param {PrintFn} print - Print callback
|
|
3330
|
+
* @param {boolean} [directive]
|
|
3298
3331
|
* @returns {Doc[]}
|
|
3299
3332
|
*/
|
|
3300
|
-
function printIfStatement(node, path, options, print) {
|
|
3333
|
+
function printIfStatement(node, path, options, print, directive = false) {
|
|
3301
3334
|
// Extract leading comments from test node to print them before 'if' keyword
|
|
3302
3335
|
const testNode = node.test;
|
|
3303
3336
|
|
|
@@ -3341,8 +3374,24 @@ function printIfStatement(node, path, options, print) {
|
|
|
3341
3374
|
parts.push(' ');
|
|
3342
3375
|
}
|
|
3343
3376
|
|
|
3344
|
-
parts.push('else ');
|
|
3345
|
-
|
|
3377
|
+
parts.push(directive ? '@else ' : 'else ');
|
|
3378
|
+
if (directive && node.alternate.type === 'IfStatement') {
|
|
3379
|
+
parts.push(
|
|
3380
|
+
path.call(
|
|
3381
|
+
(alternatePath) =>
|
|
3382
|
+
printIfStatement(
|
|
3383
|
+
/** @type {AST.IfStatement} */ (alternatePath.node),
|
|
3384
|
+
/** @type {AstPath<AST.IfStatement>} */ (alternatePath),
|
|
3385
|
+
options,
|
|
3386
|
+
print,
|
|
3387
|
+
true,
|
|
3388
|
+
),
|
|
3389
|
+
'alternate',
|
|
3390
|
+
),
|
|
3391
|
+
);
|
|
3392
|
+
} else {
|
|
3393
|
+
parts.push(path.call(print, 'alternate'));
|
|
3394
|
+
}
|
|
3346
3395
|
}
|
|
3347
3396
|
|
|
3348
3397
|
return parts;
|
|
@@ -3376,9 +3425,10 @@ function printForInStatement(node, path, options, print) {
|
|
|
3376
3425
|
* @param {AstPath<AST.ForOfStatement>} path - The AST path
|
|
3377
3426
|
* @param {RippleFormatOptions} options - Prettier options
|
|
3378
3427
|
* @param {PrintFn} print - Print callback
|
|
3428
|
+
* @param {boolean} [directive]
|
|
3379
3429
|
* @returns {Doc[]}
|
|
3380
3430
|
*/
|
|
3381
|
-
function printForOfStatement(node, path, options, print) {
|
|
3431
|
+
function printForOfStatement(node, path, options, print, directive = false) {
|
|
3382
3432
|
/** @type {Doc[]} */
|
|
3383
3433
|
const parts = [];
|
|
3384
3434
|
parts.push('for (');
|
|
@@ -3399,6 +3449,10 @@ function printForOfStatement(node, path, options, print) {
|
|
|
3399
3449
|
|
|
3400
3450
|
parts.push(') ');
|
|
3401
3451
|
parts.push(path.call(print, 'body'));
|
|
3452
|
+
if (node.empty) {
|
|
3453
|
+
parts.push(directive ? ' @empty ' : ' empty ');
|
|
3454
|
+
parts.push(path.call(print, 'empty'));
|
|
3455
|
+
}
|
|
3402
3456
|
|
|
3403
3457
|
return parts;
|
|
3404
3458
|
}
|
|
@@ -3715,9 +3769,10 @@ function printClassDeclaration(node, path, options, print) {
|
|
|
3715
3769
|
* @param {AstPath<AST.TryStatement>} path - The AST path
|
|
3716
3770
|
* @param {RippleFormatOptions} options - Prettier options
|
|
3717
3771
|
* @param {PrintFn} print - Print callback
|
|
3772
|
+
* @param {boolean} [directive=false] - Whether this is a JSX @try expression.
|
|
3718
3773
|
* @returns {Doc[]}
|
|
3719
3774
|
*/
|
|
3720
|
-
function printTryStatement(node, path, options, print) {
|
|
3775
|
+
function printTryStatement(node, path, options, print, directive = false) {
|
|
3721
3776
|
// Extract leading comments from block node to print them before 'try' keyword
|
|
3722
3777
|
const blockNode = node.block;
|
|
3723
3778
|
|
|
@@ -3737,12 +3792,12 @@ function printTryStatement(node, path, options, print) {
|
|
|
3737
3792
|
parts.push(block);
|
|
3738
3793
|
|
|
3739
3794
|
if (node.pending) {
|
|
3740
|
-
parts.push(' pending ');
|
|
3795
|
+
parts.push(directive ? ' @pending ' : ' pending ');
|
|
3741
3796
|
parts.push(path.call(print, 'pending'));
|
|
3742
3797
|
}
|
|
3743
3798
|
|
|
3744
3799
|
if (node.handler) {
|
|
3745
|
-
parts.push(' catch');
|
|
3800
|
+
parts.push(directive ? ' @catch' : ' catch');
|
|
3746
3801
|
if (node.handler.param) {
|
|
3747
3802
|
parts.push(' (');
|
|
3748
3803
|
parts.push(path.call(print, 'handler', 'param'));
|
|
@@ -3949,7 +4004,6 @@ function printMemberExpression(node, path, options, print) {
|
|
|
3949
4004
|
|
|
3950
4005
|
let result;
|
|
3951
4006
|
if (node.computed) {
|
|
3952
|
-
// Check if the MemberExpression itself is tracked to add @ symbol
|
|
3953
4007
|
const openBracket = node.optional ? '?.[' : '[';
|
|
3954
4008
|
result = [objectPart, openBracket, propertyPart, ']'];
|
|
3955
4009
|
} else {
|
|
@@ -4483,6 +4537,76 @@ function printSwitchStatement(node, path, options, print) {
|
|
|
4483
4537
|
return parts;
|
|
4484
4538
|
}
|
|
4485
4539
|
|
|
4540
|
+
/**
|
|
4541
|
+
* Print a JSX switch expression. JSX switch cases use explicit template blocks:
|
|
4542
|
+
* `case value: { ... }`, unlike ordinary JavaScript switch cases.
|
|
4543
|
+
* @param {AST.SwitchStatement} node - The switch expression node
|
|
4544
|
+
* @param {AstPath<AST.SwitchStatement>} path - The AST path
|
|
4545
|
+
* @param {RippleFormatOptions} options - Prettier options
|
|
4546
|
+
* @param {PrintFn} print - Print callback
|
|
4547
|
+
* @returns {Doc[]}
|
|
4548
|
+
*/
|
|
4549
|
+
function printJSXSwitchExpression(node, path, options, print) {
|
|
4550
|
+
const discriminant = path.call(
|
|
4551
|
+
(discriminantPath) => print(discriminantPath, { suppressLeadingComments: true }),
|
|
4552
|
+
'discriminant',
|
|
4553
|
+
);
|
|
4554
|
+
|
|
4555
|
+
/** @type {Doc[]} */
|
|
4556
|
+
const cases = [];
|
|
4557
|
+
for (let i = 0; i < node.cases.length; i++) {
|
|
4558
|
+
const caseDoc = [printJSXSwitchCase(node.cases[i], path, options, print, i)];
|
|
4559
|
+
if (i < node.cases.length - 1 && isNextLineEmpty(node.cases[i], options)) {
|
|
4560
|
+
caseDoc.push(hardline);
|
|
4561
|
+
}
|
|
4562
|
+
cases.push(caseDoc);
|
|
4563
|
+
}
|
|
4564
|
+
|
|
4565
|
+
const bodyDoc =
|
|
4566
|
+
cases.length > 0 ? [indent([hardline, join(hardline, cases)]), hardline] : hardline;
|
|
4567
|
+
|
|
4568
|
+
const discriminantDoc = group(['@switch (', indent([softline, discriminant]), softline, ')']);
|
|
4569
|
+
|
|
4570
|
+
return [
|
|
4571
|
+
...extractAndPrintLeadingComments(node.discriminant),
|
|
4572
|
+
discriminantDoc,
|
|
4573
|
+
' {',
|
|
4574
|
+
bodyDoc,
|
|
4575
|
+
'}',
|
|
4576
|
+
];
|
|
4577
|
+
}
|
|
4578
|
+
|
|
4579
|
+
/**
|
|
4580
|
+
* @param {AST.SwitchCase} node
|
|
4581
|
+
* @param {AstPath<AST.SwitchStatement>} path
|
|
4582
|
+
* @param {RippleFormatOptions} options
|
|
4583
|
+
* @param {PrintFn} print
|
|
4584
|
+
* @param {number} index
|
|
4585
|
+
* @returns {Doc[]}
|
|
4586
|
+
*/
|
|
4587
|
+
function printJSXSwitchCase(node, path, options, print, index) {
|
|
4588
|
+
const header = node.test
|
|
4589
|
+
? ['@case ', path.call(print, 'cases', index, 'test'), ':']
|
|
4590
|
+
: '@default:';
|
|
4591
|
+
const consequents = node.consequent || [];
|
|
4592
|
+
const printedConsequents = [];
|
|
4593
|
+
|
|
4594
|
+
for (let i = 0; i < consequents.length; i++) {
|
|
4595
|
+
const child = consequents[i];
|
|
4596
|
+
if (!child || child.type === 'EmptyStatement') {
|
|
4597
|
+
continue;
|
|
4598
|
+
}
|
|
4599
|
+
printedConsequents.push(path.call(print, 'cases', index, 'consequent', i));
|
|
4600
|
+
}
|
|
4601
|
+
|
|
4602
|
+
const bodyDoc =
|
|
4603
|
+
printedConsequents.length > 0
|
|
4604
|
+
? [indent([hardline, join(hardline, printedConsequents)]), hardline]
|
|
4605
|
+
: hardline;
|
|
4606
|
+
|
|
4607
|
+
return [header, ' {', bodyDoc, '}'];
|
|
4608
|
+
}
|
|
4609
|
+
|
|
4486
4610
|
/**
|
|
4487
4611
|
* Print a switch case
|
|
4488
4612
|
* @param {AST.SwitchCase} node - The switch case node
|
|
@@ -4650,6 +4774,26 @@ function getBlankLinesBetweenPositions(current_pos, next_pos) {
|
|
|
4650
4774
|
* @param {AST.Node | AST.CSS.StyleSheet | AST.Comment} nextNode - Next node
|
|
4651
4775
|
* @returns {number}
|
|
4652
4776
|
*/
|
|
4777
|
+
/**
|
|
4778
|
+
* The position to measure a leading blank line against: the first leading
|
|
4779
|
+
* comment if any (so the comment lines aren't miscounted as blank), else the
|
|
4780
|
+
* node itself.
|
|
4781
|
+
* @param {any} node
|
|
4782
|
+
* @returns {any}
|
|
4783
|
+
*/
|
|
4784
|
+
function leadingAnchor(node) {
|
|
4785
|
+
const lead = node?.leadingComments;
|
|
4786
|
+
if (Array.isArray(lead) && lead.length > 0 && lead[0].loc) {
|
|
4787
|
+
return lead[0];
|
|
4788
|
+
}
|
|
4789
|
+
return node;
|
|
4790
|
+
}
|
|
4791
|
+
|
|
4792
|
+
/**
|
|
4793
|
+
* @param {any} currentNode
|
|
4794
|
+
* @param {any} nextNode
|
|
4795
|
+
* @returns {number}
|
|
4796
|
+
*/
|
|
4653
4797
|
function getBlankLinesBetweenNodes(currentNode, nextNode) {
|
|
4654
4798
|
// Return the number of blank lines between two nodes based on their location
|
|
4655
4799
|
if (
|
|
@@ -5022,6 +5166,16 @@ function printVariableDeclarator(node, path, options, print) {
|
|
|
5022
5166
|
}
|
|
5023
5167
|
}
|
|
5024
5168
|
|
|
5169
|
+
if (isTemplateExpression(node.init)) {
|
|
5170
|
+
const groupId = Symbol('declaration');
|
|
5171
|
+
return group([
|
|
5172
|
+
group(id),
|
|
5173
|
+
' =',
|
|
5174
|
+
group(indent(line), { id: groupId }),
|
|
5175
|
+
indentIfBreak(init, { groupId }),
|
|
5176
|
+
]);
|
|
5177
|
+
}
|
|
5178
|
+
|
|
5025
5179
|
// Default: simple inline format with space
|
|
5026
5180
|
// Use group to allow breaking if needed - but keep inline when it fits
|
|
5027
5181
|
return group([id, ' = ', init]);
|
|
@@ -5191,6 +5345,45 @@ function printTSCallSignatureDeclaration(node, path, options, print) {
|
|
|
5191
5345
|
return parts;
|
|
5192
5346
|
}
|
|
5193
5347
|
|
|
5348
|
+
/**
|
|
5349
|
+
* Print a TypeScript construct signature in an interface or type literal
|
|
5350
|
+
* @param {AST.TSConstructSignatureDeclaration} node - The construct signature node
|
|
5351
|
+
* @param {AstPath<AST.TSConstructSignatureDeclaration>} path - The AST path
|
|
5352
|
+
* @param {RippleFormatOptions} options - Prettier options
|
|
5353
|
+
* @param {PrintFn} print - Print callback
|
|
5354
|
+
* @returns {Doc[]}
|
|
5355
|
+
*/
|
|
5356
|
+
function printTSConstructSignatureDeclaration(node, path, options, print) {
|
|
5357
|
+
/** @type {Doc[]} */
|
|
5358
|
+
const parts = ['new '];
|
|
5359
|
+
|
|
5360
|
+
if (node.typeParameters) {
|
|
5361
|
+
const type_params = path.call(print, 'typeParameters');
|
|
5362
|
+
if (Array.isArray(type_params)) {
|
|
5363
|
+
parts.push(...type_params);
|
|
5364
|
+
} else {
|
|
5365
|
+
parts.push(type_params);
|
|
5366
|
+
}
|
|
5367
|
+
}
|
|
5368
|
+
|
|
5369
|
+
parts.push('(');
|
|
5370
|
+
if (node.parameters && node.parameters.length > 0) {
|
|
5371
|
+
const params = path.map(print, 'parameters');
|
|
5372
|
+
for (let i = 0; i < params.length; i++) {
|
|
5373
|
+
if (i > 0) parts.push(', ');
|
|
5374
|
+
parts.push(params[i]);
|
|
5375
|
+
}
|
|
5376
|
+
}
|
|
5377
|
+
parts.push(')');
|
|
5378
|
+
|
|
5379
|
+
if (node.typeAnnotation) {
|
|
5380
|
+
parts.push(': ');
|
|
5381
|
+
parts.push(path.call(print, 'typeAnnotation'));
|
|
5382
|
+
}
|
|
5383
|
+
|
|
5384
|
+
return parts;
|
|
5385
|
+
}
|
|
5386
|
+
|
|
5194
5387
|
/**
|
|
5195
5388
|
* Print a TypeScript type reference (e.g., Array<string>)
|
|
5196
5389
|
* @param {AST.TSTypeReference} node - The type reference node
|
|
@@ -5465,6 +5658,36 @@ function printRawText(raw) {
|
|
|
5465
5658
|
);
|
|
5466
5659
|
}
|
|
5467
5660
|
|
|
5661
|
+
/**
|
|
5662
|
+
* @param {string} raw
|
|
5663
|
+
* @returns {Doc | Doc[] | string}
|
|
5664
|
+
*/
|
|
5665
|
+
function printJSXTextChild(raw) {
|
|
5666
|
+
const text = raw.trim();
|
|
5667
|
+
if (!text) {
|
|
5668
|
+
return '';
|
|
5669
|
+
}
|
|
5670
|
+
|
|
5671
|
+
const lines = text
|
|
5672
|
+
.split(/\r\n|\r|\n/u)
|
|
5673
|
+
.map((line) => line.trim())
|
|
5674
|
+
.filter(Boolean);
|
|
5675
|
+
if (lines.length <= 1) {
|
|
5676
|
+
return lines[0] ?? '';
|
|
5677
|
+
}
|
|
5678
|
+
|
|
5679
|
+
return join(hardline, lines);
|
|
5680
|
+
}
|
|
5681
|
+
|
|
5682
|
+
/**
|
|
5683
|
+
* @param {string} raw
|
|
5684
|
+
* @returns {string}
|
|
5685
|
+
*/
|
|
5686
|
+
function normalizeInlineJSXText(raw) {
|
|
5687
|
+
const text = raw.replace(/[^\S\r\n]+/gu, ' ');
|
|
5688
|
+
return text.trim() || !/[\r\n]/u.test(text) ? text : '';
|
|
5689
|
+
}
|
|
5690
|
+
|
|
5468
5691
|
/**
|
|
5469
5692
|
* @param {AST.Node} parentNode
|
|
5470
5693
|
* @param {AST.Node} firstChild
|
|
@@ -5476,8 +5699,7 @@ function shouldInlineSingleChild(parentNode, firstChild, childDoc) {
|
|
|
5476
5699
|
return false;
|
|
5477
5700
|
}
|
|
5478
5701
|
|
|
5479
|
-
|
|
5480
|
-
if (firstChild.type === 'Text') {
|
|
5702
|
+
if (firstChild.type === 'JSXText') {
|
|
5481
5703
|
return true;
|
|
5482
5704
|
}
|
|
5483
5705
|
|
|
@@ -5487,7 +5709,7 @@ function shouldInlineSingleChild(parentNode, firstChild, childDoc) {
|
|
|
5487
5709
|
|
|
5488
5710
|
// Inline JSX expressions if they fit, but respect original multi-line formatting
|
|
5489
5711
|
// for non-literal expressions (e.g. {children} should stay multi-line if written that way)
|
|
5490
|
-
if (firstChild.type === '
|
|
5712
|
+
if (firstChild.type === 'JSXExpressionContainer') {
|
|
5491
5713
|
if (wasOriginallySingleLine(parentNode)) {
|
|
5492
5714
|
return true;
|
|
5493
5715
|
}
|
|
@@ -5505,19 +5727,71 @@ function shouldInlineSingleChild(parentNode, firstChild, childDoc) {
|
|
|
5505
5727
|
return false;
|
|
5506
5728
|
}
|
|
5507
5729
|
|
|
5508
|
-
if (firstChild.type === '
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
/** @type {AST.Element} */ (parentNode).attributes.length === 0
|
|
5512
|
-
);
|
|
5730
|
+
if (firstChild.type === 'JSXElement' && firstChild.openingElement?.selfClosing) {
|
|
5731
|
+
const parent = /** @type {any} */ (parentNode);
|
|
5732
|
+
return !parent.openingElement?.attributes?.length;
|
|
5513
5733
|
}
|
|
5514
5734
|
|
|
5515
5735
|
return false;
|
|
5516
5736
|
}
|
|
5517
5737
|
|
|
5738
|
+
/**
|
|
5739
|
+
* Check whether a child can participate in compact inline TSRX content.
|
|
5740
|
+
* @param {any} child
|
|
5741
|
+
* @returns {boolean}
|
|
5742
|
+
*/
|
|
5743
|
+
function isInlineableTextOrExpressionChild(child) {
|
|
5744
|
+
if (!child || (child.type !== 'JSXText' && child.type !== 'JSXExpressionContainer')) {
|
|
5745
|
+
return false;
|
|
5746
|
+
}
|
|
5747
|
+
|
|
5748
|
+
if (hasComment(/** @type {AST.Node & AST.NodeWithMaybeComments} */ (child))) {
|
|
5749
|
+
return false;
|
|
5750
|
+
}
|
|
5751
|
+
|
|
5752
|
+
const expression = /** @type {{ expression?: AST.Node & AST.NodeWithMaybeComments }} */ (child)
|
|
5753
|
+
.expression;
|
|
5754
|
+
return !expression || !hasComment(expression);
|
|
5755
|
+
}
|
|
5756
|
+
|
|
5757
|
+
/**
|
|
5758
|
+
* @param {any} node
|
|
5759
|
+
* @returns {boolean}
|
|
5760
|
+
*/
|
|
5761
|
+
function shouldTryInlineMultipleTextChildren(node) {
|
|
5762
|
+
return (
|
|
5763
|
+
wasOriginallySingleLine(node) &&
|
|
5764
|
+
Array.isArray(node.children) &&
|
|
5765
|
+
node.children.length > 1 &&
|
|
5766
|
+
node.children.some((/** @type {any} */ child) => child.type === 'JSXText') &&
|
|
5767
|
+
node.children.every(isInlineableTextOrExpressionChild)
|
|
5768
|
+
);
|
|
5769
|
+
}
|
|
5770
|
+
|
|
5771
|
+
/**
|
|
5772
|
+
* @param {AST.Node} child
|
|
5773
|
+
* @returns {boolean}
|
|
5774
|
+
*/
|
|
5775
|
+
function isSimpleJSXExpressionChild(child) {
|
|
5776
|
+
if (child?.type !== 'JSXExpressionContainer') {
|
|
5777
|
+
return false;
|
|
5778
|
+
}
|
|
5779
|
+
|
|
5780
|
+
const expression = child.expression;
|
|
5781
|
+
return (
|
|
5782
|
+
expression?.type === 'Identifier' ||
|
|
5783
|
+
expression?.type === 'Literal' ||
|
|
5784
|
+
expression?.type === 'TemplateLiteral' ||
|
|
5785
|
+
// Stock Prettier keeps a single `{expr}` child inline regardless of the
|
|
5786
|
+
// expression kind (member access, calls, etc.); only multiple children break.
|
|
5787
|
+
expression?.type === 'MemberExpression' ||
|
|
5788
|
+
expression?.type === 'CallExpression'
|
|
5789
|
+
);
|
|
5790
|
+
}
|
|
5791
|
+
|
|
5518
5792
|
/**
|
|
5519
5793
|
* Get leading comments from element metadata
|
|
5520
|
-
* @param {
|
|
5794
|
+
* @param {ESTreeJSX.JSXElement} node - The element node
|
|
5521
5795
|
* @returns {AST.Comment[]}
|
|
5522
5796
|
*/
|
|
5523
5797
|
function getElementLeadingComments(node) {
|
|
@@ -5577,142 +5851,10 @@ function createElementLevelCommentPartsTrimmed(comments) {
|
|
|
5577
5851
|
return parts;
|
|
5578
5852
|
}
|
|
5579
5853
|
|
|
5580
|
-
/**
|
|
5581
|
-
* Print a TsrxFragment node - renders native TSRX template children inside a fragment.
|
|
5582
|
-
* @param {AST.TsrxFragment} node - The TsrxFragment node
|
|
5583
|
-
* @param {AstPath<AST.TsrxFragment>} path - The AST path
|
|
5584
|
-
* @param {RippleFormatOptions} options - Prettier options
|
|
5585
|
-
* @param {PrintFn} print - Print callback
|
|
5586
|
-
* @returns {Doc}
|
|
5587
|
-
*/
|
|
5588
|
-
function printTsrx(node, path, options, print) {
|
|
5589
|
-
const tagName = '<>';
|
|
5590
|
-
const closingTagName = '</>';
|
|
5591
|
-
const hasChildren = Array.isArray(node.children) && node.children.length > 0;
|
|
5592
|
-
|
|
5593
|
-
if (!hasChildren) {
|
|
5594
|
-
return [tagName, closingTagName];
|
|
5595
|
-
}
|
|
5596
|
-
|
|
5597
|
-
const printedChildren = [];
|
|
5598
|
-
|
|
5599
|
-
for (let i = 0; i < node.children.length; i++) {
|
|
5600
|
-
const child = node.children[i];
|
|
5601
|
-
|
|
5602
|
-
if (child.type === 'JSXText') {
|
|
5603
|
-
const text = child.value.trim();
|
|
5604
|
-
if (!text) continue;
|
|
5605
|
-
printedChildren.push(text);
|
|
5606
|
-
} else {
|
|
5607
|
-
const printedChild = path.call(print, 'children', i);
|
|
5608
|
-
printedChildren.push(printedChild);
|
|
5609
|
-
}
|
|
5610
|
-
}
|
|
5611
|
-
|
|
5612
|
-
if (printedChildren.length === 0) {
|
|
5613
|
-
return [tagName, closingTagName];
|
|
5614
|
-
}
|
|
5615
|
-
|
|
5616
|
-
if (
|
|
5617
|
-
printedChildren.length === 1 &&
|
|
5618
|
-
['Element', 'Text', 'TSRXExpression'].includes(node.children[0]?.type)
|
|
5619
|
-
) {
|
|
5620
|
-
return group([tagName, indent([softline, printedChildren[0]]), softline, closingTagName]);
|
|
5621
|
-
}
|
|
5622
|
-
|
|
5623
|
-
return group([
|
|
5624
|
-
tagName,
|
|
5625
|
-
indent([hardline, join(hardline, printedChildren)]),
|
|
5626
|
-
hardline,
|
|
5627
|
-
closingTagName,
|
|
5628
|
-
]);
|
|
5629
|
-
}
|
|
5630
|
-
|
|
5631
|
-
/**
|
|
5632
|
-
* Print a TSX compatibility node
|
|
5633
|
-
* @param {AST.TsxCompat} node - The TSX compat node
|
|
5634
|
-
* @param {AstPath<AST.TsxCompat>} path - The AST path
|
|
5635
|
-
* @param {RippleFormatOptions} options - Prettier options
|
|
5636
|
-
* @param {PrintFn} print - Print callback
|
|
5637
|
-
* @returns {Doc}
|
|
5638
|
-
*/
|
|
5639
|
-
function printTsxCompat(node, path, options, print) {
|
|
5640
|
-
const tagName = `<tsx:${node.kind}>`;
|
|
5641
|
-
const closingTagName = `</tsx:${node.kind}>`;
|
|
5642
|
-
|
|
5643
|
-
const hasChildren = Array.isArray(node.children) && node.children.length > 0;
|
|
5644
|
-
|
|
5645
|
-
if (!hasChildren) {
|
|
5646
|
-
return [tagName, closingTagName];
|
|
5647
|
-
}
|
|
5648
|
-
|
|
5649
|
-
// Print JSXElement children - they remain as JSX
|
|
5650
|
-
// Filter out whitespace-only JSXText nodes and merge adjacent text-like nodes
|
|
5651
|
-
const finalChildren = [];
|
|
5652
|
-
let accumulatedText = '';
|
|
5653
|
-
|
|
5654
|
-
for (let i = 0; i < node.children.length; i++) {
|
|
5655
|
-
const child = node.children[i];
|
|
5656
|
-
|
|
5657
|
-
// Check if this is a text-like node (JSXText or Identifier in JSX context)
|
|
5658
|
-
const isTextLike = child.type === 'JSXText';
|
|
5659
|
-
|
|
5660
|
-
if (isTextLike) {
|
|
5661
|
-
// Get the text content
|
|
5662
|
-
let text;
|
|
5663
|
-
if (child.type === 'JSXText') {
|
|
5664
|
-
text = child.value.trim();
|
|
5665
|
-
}
|
|
5666
|
-
|
|
5667
|
-
if (text) {
|
|
5668
|
-
if (accumulatedText) {
|
|
5669
|
-
accumulatedText += ' ' + text;
|
|
5670
|
-
} else {
|
|
5671
|
-
accumulatedText = text;
|
|
5672
|
-
}
|
|
5673
|
-
}
|
|
5674
|
-
} else {
|
|
5675
|
-
// Before adding non-text node, flush accumulated text
|
|
5676
|
-
if (accumulatedText) {
|
|
5677
|
-
if (finalChildren.length > 0) {
|
|
5678
|
-
finalChildren.push(hardline);
|
|
5679
|
-
}
|
|
5680
|
-
finalChildren.push(accumulatedText);
|
|
5681
|
-
accumulatedText = '';
|
|
5682
|
-
}
|
|
5683
|
-
|
|
5684
|
-
if (finalChildren.length > 0) {
|
|
5685
|
-
finalChildren.push(hardline);
|
|
5686
|
-
}
|
|
5687
|
-
|
|
5688
|
-
const printedChild = path.call(print, 'children', i);
|
|
5689
|
-
finalChildren.push(printedChild);
|
|
5690
|
-
}
|
|
5691
|
-
}
|
|
5692
|
-
|
|
5693
|
-
// Don't forget any remaining accumulated text
|
|
5694
|
-
if (accumulatedText) {
|
|
5695
|
-
if (finalChildren.length > 0) {
|
|
5696
|
-
finalChildren.push(hardline);
|
|
5697
|
-
}
|
|
5698
|
-
finalChildren.push(accumulatedText);
|
|
5699
|
-
}
|
|
5700
|
-
|
|
5701
|
-
// Format the TsxCompat element
|
|
5702
|
-
const elementOutput = group([
|
|
5703
|
-
tagName,
|
|
5704
|
-
indent([hardline, ...finalChildren]),
|
|
5705
|
-
hardline,
|
|
5706
|
-
closingTagName,
|
|
5707
|
-
]);
|
|
5708
|
-
|
|
5709
|
-
return elementOutput;
|
|
5710
|
-
}
|
|
5711
|
-
|
|
5712
5854
|
/**
|
|
5713
5855
|
* Print a JSX element
|
|
5714
|
-
* @param {
|
|
5715
|
-
* @param {AstPath<
|
|
5856
|
+
* @param {AST.TSRXJSXElement} node - The JSX element node
|
|
5857
|
+
* @param {AstPath<any>} path - The AST path
|
|
5716
5858
|
* @param {RippleFormatOptions} options - Prettier options
|
|
5717
5859
|
* @param {PrintFn} print - Print callback
|
|
5718
5860
|
* @returns {Doc | Doc[]}
|
|
@@ -5722,20 +5864,7 @@ function printJSXElement(node, path, options, print) {
|
|
|
5722
5864
|
const openingElement = node.openingElement;
|
|
5723
5865
|
const closingElement = node.closingElement;
|
|
5724
5866
|
|
|
5725
|
-
|
|
5726
|
-
let tagName;
|
|
5727
|
-
if (openingElement.name.type === 'JSXIdentifier') {
|
|
5728
|
-
tagName = openingElement.name.name;
|
|
5729
|
-
} else if (openingElement.name.type === 'JSXMemberExpression') {
|
|
5730
|
-
// Handle Member expressions like React.Fragment
|
|
5731
|
-
tagName = printJSXMemberExpression(openingElement.name);
|
|
5732
|
-
} else if (openingElement.name.type === 'JSXNamespacedName') {
|
|
5733
|
-
const namespace_name = openingElement.name.namespace.name;
|
|
5734
|
-
const local_name = openingElement.name.name.name;
|
|
5735
|
-
tagName = namespace_name + ':' + local_name;
|
|
5736
|
-
} else {
|
|
5737
|
-
tagName = 'Unknown';
|
|
5738
|
-
}
|
|
5867
|
+
const tagName = printJSXElementName(openingElement.name);
|
|
5739
5868
|
|
|
5740
5869
|
const isSelfClosing = openingElement.selfClosing;
|
|
5741
5870
|
const hasAttributes = openingElement.attributes && openingElement.attributes.length > 0;
|
|
@@ -5747,6 +5876,12 @@ function printJSXElement(node, path, options, print) {
|
|
|
5747
5876
|
typeArgsDoc = path.call(print, 'openingElement', 'typeArguments');
|
|
5748
5877
|
}
|
|
5749
5878
|
|
|
5879
|
+
// Comments that sit inside the opening tag (before an attribute) are attached
|
|
5880
|
+
// by the parser to a body child; pull them out and key them by the attribute
|
|
5881
|
+
// they precede so they print in the opening tag, not jammed into the body.
|
|
5882
|
+
const openingTagCommentsByAttr = collectOpeningTagComments(node);
|
|
5883
|
+
const hasOpeningTagComments = openingTagCommentsByAttr.size > 0;
|
|
5884
|
+
|
|
5750
5885
|
// Format attributes
|
|
5751
5886
|
/** @type {Doc} */
|
|
5752
5887
|
let attributesDoc = '';
|
|
@@ -5770,19 +5905,31 @@ function printJSXElement(node, path, options, print) {
|
|
|
5770
5905
|
'attributes',
|
|
5771
5906
|
i,
|
|
5772
5907
|
);
|
|
5773
|
-
} else if (attr.type === 'JSXSpreadAttribute'
|
|
5908
|
+
} else if (attr.type === 'JSXSpreadAttribute') {
|
|
5774
5909
|
attrDoc = ['{...', path.call(print, 'openingElement', 'attributes', i, 'argument'), '}'];
|
|
5775
5910
|
}
|
|
5776
5911
|
if (!hasBreakingAttribute && attrDoc && willBreak(attrDoc)) {
|
|
5777
5912
|
hasBreakingAttribute = true;
|
|
5778
5913
|
}
|
|
5914
|
+
const lead = openingTagCommentsByAttr.get(i);
|
|
5915
|
+
if (lead) {
|
|
5916
|
+
/** @type {Doc[]} */
|
|
5917
|
+
const parts = [];
|
|
5918
|
+
for (const comment of lead) {
|
|
5919
|
+
parts.push(
|
|
5920
|
+
comment.type === 'Line' ? '//' + comment.value : '/*' + comment.value + '*/',
|
|
5921
|
+
);
|
|
5922
|
+
parts.push(hardline);
|
|
5923
|
+
}
|
|
5924
|
+
return [...parts, attrDoc];
|
|
5925
|
+
}
|
|
5779
5926
|
return attrDoc;
|
|
5780
5927
|
},
|
|
5781
5928
|
);
|
|
5782
5929
|
const attrLineBreak = options.singleAttributePerLine ? hardline : line;
|
|
5783
5930
|
attributesDoc = indent([attrLineBreak, join(attrLineBreak, attrs)]);
|
|
5784
5931
|
}
|
|
5785
|
-
const shouldForceBreak = hasBreakingAttribute;
|
|
5932
|
+
const shouldForceBreak = hasBreakingAttribute || hasOpeningTagComments;
|
|
5786
5933
|
|
|
5787
5934
|
if (isSelfClosing) {
|
|
5788
5935
|
return group(['<', tagName, typeArgsDoc, attributesDoc, hasAttributes ? line : ' ', '/>'], {
|
|
@@ -5802,58 +5949,119 @@ function printJSXElement(node, path, options, print) {
|
|
|
5802
5949
|
{ shouldBreak: shouldForceBreak },
|
|
5803
5950
|
);
|
|
5804
5951
|
|
|
5952
|
+
// Trailing comments after the last child are attached by the parser either to
|
|
5953
|
+
// the closing tag (`closingElement.leadingComments`) or, when the last child is
|
|
5954
|
+
// an `{expr}` container, to `metadata.elementLeadingComments` positioned inside
|
|
5955
|
+
// the body (start >= opening tag end). Emit both before `</tag>`.
|
|
5956
|
+
const openingTagEnd = /** @type {AST.NodeWithLocation} */ (openingElement).end;
|
|
5957
|
+
const bodyMetaComments = (node.metadata?.elementLeadingComments ?? []).filter(
|
|
5958
|
+
(/** @type {AST.Comment} */ comment) =>
|
|
5959
|
+
typeof comment.start === 'number' && comment.start >= openingTagEnd,
|
|
5960
|
+
);
|
|
5961
|
+
const trailingComments = [
|
|
5962
|
+
...(node.closingElement?.leadingComments ?? []),
|
|
5963
|
+
...bodyMetaComments,
|
|
5964
|
+
].sort((a, b) => /** @type {number} */ (a.start) - /** @type {number} */ (b.start));
|
|
5965
|
+
const lastMeaningfulChild = [...(node.children ?? [])]
|
|
5966
|
+
.reverse()
|
|
5967
|
+
.find((child) => child.type !== 'JSXText' || child.value.trim());
|
|
5968
|
+
const closingCommentDocs = printElementBodyLineComments(trailingComments, lastMeaningfulChild);
|
|
5969
|
+
const hasClosingComments = closingCommentDocs.length > 0;
|
|
5970
|
+
// A comment-only element has no children; its comments live in `innerComments`.
|
|
5971
|
+
const innerCommentDocs = printElementBodyLineComments(node.innerComments);
|
|
5972
|
+
|
|
5805
5973
|
if (!hasChildren) {
|
|
5974
|
+
const bodyComments = [...innerCommentDocs, ...closingCommentDocs];
|
|
5975
|
+
if (bodyComments.length > 0) {
|
|
5976
|
+
return group([openingTag, indent(bodyComments), hardline, '</', tagName, '>']);
|
|
5977
|
+
}
|
|
5806
5978
|
return [openingTag, '</', tagName, '>'];
|
|
5807
5979
|
}
|
|
5808
5980
|
|
|
5809
|
-
//
|
|
5981
|
+
// A `@{ … }` code block is the whole body and hugs the tags: `<div>@{ … }</div>`.
|
|
5982
|
+
if (node.children.length === 1 && node.children[0].type === 'JSXCodeBlock') {
|
|
5983
|
+
return group([openingTag, path.call(print, 'children', 0), '</', tagName, '>']);
|
|
5984
|
+
}
|
|
5985
|
+
|
|
5986
|
+
// Format children - filter out empty text nodes and merge adjacent text nodes.
|
|
5987
|
+
// childNodes tracks the source node behind each doc (a text run is a single
|
|
5988
|
+
// JSXText) so the join can preserve authored blank lines.
|
|
5810
5989
|
const childrenDocs = [];
|
|
5990
|
+
const childNodes = [];
|
|
5811
5991
|
let currentText = '';
|
|
5992
|
+
let currentTextNode = null;
|
|
5812
5993
|
|
|
5813
5994
|
for (let i = 0; i < node.children.length; i++) {
|
|
5814
5995
|
const child = node.children[i];
|
|
5815
5996
|
|
|
5816
5997
|
if (child.type === 'JSXText') {
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5998
|
+
if (hasComment(/** @type {AST.Node & AST.NodeWithMaybeComments} */ (child))) {
|
|
5999
|
+
if (currentText) {
|
|
6000
|
+
childrenDocs.push(currentText);
|
|
6001
|
+
childNodes.push(currentTextNode);
|
|
6002
|
+
currentText = '';
|
|
6003
|
+
currentTextNode = null;
|
|
6004
|
+
}
|
|
6005
|
+
const printedChild = path.call(print, 'children', i);
|
|
6006
|
+
if (printedChild !== '') {
|
|
6007
|
+
childrenDocs.push(printedChild);
|
|
6008
|
+
childNodes.push(child);
|
|
6009
|
+
}
|
|
6010
|
+
continue;
|
|
6011
|
+
}
|
|
6012
|
+
// Accumulate text content, preserving meaningful boundary spaces.
|
|
6013
|
+
const text = normalizeInlineJSXText(child.value);
|
|
6014
|
+
if (text) {
|
|
5820
6015
|
const nextChild = node.children[i + 1];
|
|
5821
6016
|
const afterNextChild = node.children[i + 2];
|
|
5822
6017
|
const nextText = afterNextChild?.type === 'JSXText' ? afterNextChild.value.trim() : '';
|
|
5823
6018
|
if (
|
|
5824
6019
|
tagName === 'tsrx' &&
|
|
5825
|
-
|
|
6020
|
+
text.trimEnd().endsWith('=') &&
|
|
5826
6021
|
nextChild?.type === 'JSXElement' &&
|
|
5827
6022
|
nextText === ';'
|
|
5828
6023
|
) {
|
|
5829
6024
|
if (currentText) {
|
|
5830
6025
|
childrenDocs.push(currentText);
|
|
6026
|
+
childNodes.push(currentTextNode);
|
|
5831
6027
|
currentText = '';
|
|
6028
|
+
currentTextNode = null;
|
|
5832
6029
|
}
|
|
5833
|
-
childrenDocs.push([
|
|
6030
|
+
childrenDocs.push([text.trim(), ' ', path.call(print, 'children', i + 1), ';']);
|
|
6031
|
+
childNodes.push(child);
|
|
5834
6032
|
i += 2;
|
|
5835
6033
|
continue;
|
|
5836
6034
|
}
|
|
5837
6035
|
|
|
5838
6036
|
if (currentText) {
|
|
5839
|
-
currentText += ' ' +
|
|
6037
|
+
currentText += currentText.endsWith(' ') || text.startsWith(' ') ? text : ' ' + text;
|
|
5840
6038
|
} else {
|
|
5841
|
-
currentText =
|
|
6039
|
+
currentText = text;
|
|
6040
|
+
currentTextNode = child;
|
|
5842
6041
|
}
|
|
5843
6042
|
}
|
|
5844
6043
|
} else {
|
|
5845
6044
|
// If we have accumulated text, push it before the non-text node
|
|
5846
6045
|
if (currentText) {
|
|
5847
6046
|
childrenDocs.push(currentText);
|
|
6047
|
+
childNodes.push(currentTextNode);
|
|
5848
6048
|
currentText = '';
|
|
6049
|
+
currentTextNode = null;
|
|
5849
6050
|
}
|
|
5850
6051
|
|
|
5851
6052
|
if (child.type === 'JSXExpressionContainer') {
|
|
5852
6053
|
// Handle JSX expression containers
|
|
5853
|
-
childrenDocs.push([
|
|
6054
|
+
childrenDocs.push([
|
|
6055
|
+
...printTemplateChildLeadingComments(child),
|
|
6056
|
+
'{',
|
|
6057
|
+
path.call(print, 'children', i, 'expression'),
|
|
6058
|
+
'}',
|
|
6059
|
+
]);
|
|
6060
|
+
childNodes.push(child);
|
|
5854
6061
|
} else {
|
|
5855
6062
|
// Handle nested JSX elements
|
|
5856
6063
|
childrenDocs.push(path.call(print, 'children', i));
|
|
6064
|
+
childNodes.push(child);
|
|
5857
6065
|
}
|
|
5858
6066
|
}
|
|
5859
6067
|
}
|
|
@@ -5861,37 +6069,71 @@ function printJSXElement(node, path, options, print) {
|
|
|
5861
6069
|
// Don't forget any remaining text
|
|
5862
6070
|
if (currentText) {
|
|
5863
6071
|
childrenDocs.push(currentText);
|
|
6072
|
+
childNodes.push(currentTextNode);
|
|
5864
6073
|
}
|
|
5865
6074
|
|
|
5866
|
-
//
|
|
5867
|
-
|
|
5868
|
-
|
|
6075
|
+
// A child with leading comments must break onto its own line, so the comment
|
|
6076
|
+
// reads above the child rather than being jammed onto the opening tag.
|
|
6077
|
+
const hasChildLeadingComments = node.children.some((child) => {
|
|
6078
|
+
const leadingComments = /** @type {AST.NodeWithMaybeComments} */ (child).leadingComments;
|
|
6079
|
+
return Array.isArray(leadingComments) && leadingComments.length > 0;
|
|
6080
|
+
});
|
|
6081
|
+
const forceMultiline = hasClosingComments || hasChildLeadingComments;
|
|
6082
|
+
|
|
6083
|
+
// Check if content can be inlined (single text node or single expression).
|
|
6084
|
+
// Trailing or child-leading comments force the multi-line layout. A single
|
|
6085
|
+
// text child stays inline when it fits and otherwise fills/wraps to printWidth.
|
|
6086
|
+
if (!forceMultiline && childrenDocs.length === 1 && typeof childrenDocs[0] === 'string') {
|
|
6087
|
+
// The open tag breaks for attributes independently; the text+closing get
|
|
6088
|
+
// their own group so the text only drops to its own (filled) lines when it
|
|
6089
|
+
// itself overflows — otherwise it hugs `>text</tag>`.
|
|
6090
|
+
return [
|
|
6091
|
+
openingTag,
|
|
6092
|
+
group([indent([softline, printRawText(childrenDocs[0])]), softline, '</', tagName, '>']),
|
|
6093
|
+
];
|
|
5869
6094
|
}
|
|
5870
6095
|
const meaningfulChildren = node.children.filter(
|
|
5871
|
-
(child) => child.type !== 'JSXText' || child.value.trim(),
|
|
6096
|
+
(/** @type {any} */ child) => child.type !== 'JSXText' || child.value.trim(),
|
|
5872
6097
|
);
|
|
5873
6098
|
const singleMeaningfulChild = meaningfulChildren.length === 1 ? meaningfulChildren[0] : null;
|
|
5874
6099
|
if (
|
|
6100
|
+
!forceMultiline &&
|
|
5875
6101
|
childrenDocs.length === 1 &&
|
|
5876
6102
|
singleMeaningfulChild?.type === 'JSXExpressionContainer' &&
|
|
5877
|
-
|
|
6103
|
+
isSimpleJSXExpressionChild(/** @type {AST.Node} */ (singleMeaningfulChild))
|
|
5878
6104
|
) {
|
|
5879
6105
|
return group([openingTag, childrenDocs[0], '</', tagName, '>']);
|
|
5880
6106
|
}
|
|
6107
|
+
if (
|
|
6108
|
+
!forceMultiline &&
|
|
6109
|
+
childrenDocs.length > 1 &&
|
|
6110
|
+
wasOriginallySingleLine(node) &&
|
|
6111
|
+
node.children.some((/** @type {any} */ child) => child.type === 'JSXText') &&
|
|
6112
|
+
node.children.every(
|
|
6113
|
+
(/** @type {any} */ child) =>
|
|
6114
|
+
child.type === 'JSXText' || isSimpleJSXExpressionChild(/** @type {AST.Node} */ (child)),
|
|
6115
|
+
)
|
|
6116
|
+
) {
|
|
6117
|
+
return group([openingTag, ...childrenDocs, '</', tagName, '>']);
|
|
6118
|
+
}
|
|
5881
6119
|
|
|
5882
|
-
// Multiple children or complex children - format with line breaks
|
|
6120
|
+
// Multiple children or complex children - format with line breaks. Text runs
|
|
6121
|
+
// fill/wrap to printWidth.
|
|
5883
6122
|
const formattedChildren = [];
|
|
5884
6123
|
for (let i = 0; i < childrenDocs.length; i++) {
|
|
5885
|
-
|
|
6124
|
+
const childDoc = childrenDocs[i];
|
|
6125
|
+
formattedChildren.push(typeof childDoc === 'string' ? printRawText(childDoc) : childDoc);
|
|
5886
6126
|
if (i < childrenDocs.length - 1) {
|
|
5887
|
-
|
|
6127
|
+
// Preserve a single authored blank line between children (2+ collapse to 1).
|
|
6128
|
+
const blank = getBlankLinesBetweenNodes(childNodes[i], leadingAnchor(childNodes[i + 1])) > 0;
|
|
6129
|
+
formattedChildren.push(blank ? [hardline, hardline] : hardline);
|
|
5888
6130
|
}
|
|
5889
6131
|
}
|
|
5890
6132
|
|
|
5891
6133
|
// Build the final element
|
|
5892
6134
|
return group([
|
|
5893
6135
|
openingTag,
|
|
5894
|
-
indent([hardline, ...formattedChildren]),
|
|
6136
|
+
indent([hardline, ...formattedChildren, ...closingCommentDocs]),
|
|
5895
6137
|
hardline,
|
|
5896
6138
|
'</',
|
|
5897
6139
|
tagName,
|
|
@@ -5914,23 +6156,46 @@ function printJSXFragment(node, path, options, print) {
|
|
|
5914
6156
|
return '<></>';
|
|
5915
6157
|
}
|
|
5916
6158
|
|
|
5917
|
-
//
|
|
6159
|
+
// A `@{ … }` code block is the whole body and hugs the tags: `<>@{ … }</>`.
|
|
6160
|
+
if (node.children.length === 1 && /** @type {any} */ (node.children[0]).type === 'JSXCodeBlock') {
|
|
6161
|
+
return group(['<>', path.call(print, 'children', 0), '</>']);
|
|
6162
|
+
}
|
|
6163
|
+
|
|
6164
|
+
// Format children - filter out empty text nodes. childNodes tracks the source
|
|
6165
|
+
// node behind each doc so the join can preserve authored blank lines.
|
|
5918
6166
|
const childrenDocs = [];
|
|
6167
|
+
const childNodes = [];
|
|
5919
6168
|
for (let i = 0; i < node.children.length; i++) {
|
|
5920
6169
|
const child = node.children[i];
|
|
5921
6170
|
|
|
5922
6171
|
if (child.type === 'JSXText') {
|
|
6172
|
+
if (hasComment(/** @type {AST.Node & AST.NodeWithMaybeComments} */ (child))) {
|
|
6173
|
+
const printedChild = path.call(print, 'children', i);
|
|
6174
|
+
if (printedChild !== '') {
|
|
6175
|
+
childrenDocs.push(printedChild);
|
|
6176
|
+
childNodes.push(child);
|
|
6177
|
+
}
|
|
6178
|
+
continue;
|
|
6179
|
+
}
|
|
5923
6180
|
// Handle JSX text nodes - trim whitespace and only include if not empty
|
|
5924
|
-
const text = child.value
|
|
6181
|
+
const text = printJSXTextChild(child.value);
|
|
5925
6182
|
if (text) {
|
|
5926
6183
|
childrenDocs.push(text);
|
|
6184
|
+
childNodes.push(child);
|
|
5927
6185
|
}
|
|
5928
6186
|
} else if (child.type === 'JSXExpressionContainer') {
|
|
5929
6187
|
// Handle JSX expression containers
|
|
5930
|
-
childrenDocs.push([
|
|
6188
|
+
childrenDocs.push([
|
|
6189
|
+
...printTemplateChildLeadingComments(child),
|
|
6190
|
+
'{',
|
|
6191
|
+
path.call(print, 'children', i, 'expression'),
|
|
6192
|
+
'}',
|
|
6193
|
+
]);
|
|
6194
|
+
childNodes.push(child);
|
|
5931
6195
|
} else {
|
|
5932
6196
|
// Handle nested JSX elements and fragments
|
|
5933
6197
|
childrenDocs.push(path.call(print, 'children', i));
|
|
6198
|
+
childNodes.push(child);
|
|
5934
6199
|
}
|
|
5935
6200
|
}
|
|
5936
6201
|
|
|
@@ -5938,13 +6203,33 @@ function printJSXFragment(node, path, options, print) {
|
|
|
5938
6203
|
if (childrenDocs.length === 1 && typeof childrenDocs[0] === 'string') {
|
|
5939
6204
|
return ['<>', childrenDocs[0], '</>'];
|
|
5940
6205
|
}
|
|
6206
|
+
const meaningfulChildren = node.children.filter(
|
|
6207
|
+
(child) => child.type !== 'JSXText' || child.value.trim(),
|
|
6208
|
+
);
|
|
6209
|
+
if (
|
|
6210
|
+
childrenDocs.length === 1 &&
|
|
6211
|
+
meaningfulChildren.length === 1 &&
|
|
6212
|
+
meaningfulChildren[0].type === 'JSXElement' &&
|
|
6213
|
+
wasOriginallySingleLine(node) &&
|
|
6214
|
+
!willBreak(childrenDocs[0])
|
|
6215
|
+
) {
|
|
6216
|
+
// Keep the fragment inline when it fits; otherwise expand `<>` onto its own
|
|
6217
|
+
// lines so a breaking single child reads as `<>\n <Child …/>\n</>` rather than
|
|
6218
|
+
// `<><Child` with only the child's attributes broken.
|
|
6219
|
+
return conditionalGroup([
|
|
6220
|
+
['<>', childrenDocs[0], '</>'],
|
|
6221
|
+
group(['<>', indent([hardline, childrenDocs[0]]), hardline, '</>']),
|
|
6222
|
+
]);
|
|
6223
|
+
}
|
|
5941
6224
|
|
|
5942
6225
|
// Multiple children or complex children - format with line breaks
|
|
5943
6226
|
const formattedChildren = [];
|
|
5944
6227
|
for (let i = 0; i < childrenDocs.length; i++) {
|
|
5945
6228
|
formattedChildren.push(childrenDocs[i]);
|
|
5946
6229
|
if (i < childrenDocs.length - 1) {
|
|
5947
|
-
|
|
6230
|
+
// Preserve a single authored blank line between children (2+ collapse to 1).
|
|
6231
|
+
const blank = getBlankLinesBetweenNodes(childNodes[i], leadingAnchor(childNodes[i + 1])) > 0;
|
|
6232
|
+
formattedChildren.push(blank ? [hardline, hardline] : hardline);
|
|
5948
6233
|
}
|
|
5949
6234
|
}
|
|
5950
6235
|
|
|
@@ -5952,6 +6237,169 @@ function printJSXFragment(node, path, options, print) {
|
|
|
5952
6237
|
return group(['<>', indent([hardline, ...formattedChildren]), hardline, '</>']);
|
|
5953
6238
|
}
|
|
5954
6239
|
|
|
6240
|
+
/**
|
|
6241
|
+
* Comments written inside an opening tag, before an attribute, are attached by
|
|
6242
|
+
* the parser to the next visited body child (positionally they sort before the
|
|
6243
|
+
* opening tag's end, but the child is visited first). Pull those out of the
|
|
6244
|
+
* children and return a map from attribute index to the comments that precede it,
|
|
6245
|
+
* so the element printer can render them in the opening tag instead of the body.
|
|
6246
|
+
* @param {AST.TSRXJSXElement} node
|
|
6247
|
+
* @returns {Map<number, AST.Comment[]>}
|
|
6248
|
+
*/
|
|
6249
|
+
function collectOpeningTagComments(node) {
|
|
6250
|
+
/** @type {Map<number, AST.Comment[]>} */
|
|
6251
|
+
const byAttr = new Map();
|
|
6252
|
+
const openingElement = /** @type {AST.NodeWithLocation} */ (node.openingElement);
|
|
6253
|
+
const attributes = /** @type {any[]} */ (node.openingElement?.attributes) ?? [];
|
|
6254
|
+
if (!openingElement || attributes.length === 0 || !Array.isArray(node.children)) {
|
|
6255
|
+
return byAttr;
|
|
6256
|
+
}
|
|
6257
|
+
const openingEnd = openingElement.end;
|
|
6258
|
+
/** @type {AST.Comment[]} */
|
|
6259
|
+
const collected = [];
|
|
6260
|
+
for (const child of node.children) {
|
|
6261
|
+
const lead = /** @type {AST.NodeWithMaybeComments} */ (child).leadingComments;
|
|
6262
|
+
if (!Array.isArray(lead) || lead.length === 0) continue;
|
|
6263
|
+
const keep = [];
|
|
6264
|
+
for (const comment of lead) {
|
|
6265
|
+
if (typeof comment.start === 'number' && comment.start < openingEnd) {
|
|
6266
|
+
collected.push(comment);
|
|
6267
|
+
} else {
|
|
6268
|
+
keep.push(comment);
|
|
6269
|
+
}
|
|
6270
|
+
}
|
|
6271
|
+
if (keep.length !== lead.length) {
|
|
6272
|
+
/** @type {any} */ (child).leadingComments = keep;
|
|
6273
|
+
}
|
|
6274
|
+
}
|
|
6275
|
+
if (collected.length === 0) return byAttr;
|
|
6276
|
+
collected.sort((a, b) => /** @type {number} */ (a.start) - /** @type {number} */ (b.start));
|
|
6277
|
+
let ci = 0;
|
|
6278
|
+
for (let ai = 0; ai < attributes.length; ai++) {
|
|
6279
|
+
const attrStart = /** @type {AST.NodeWithLocation} */ (attributes[ai]).start;
|
|
6280
|
+
/** @type {AST.Comment[]} */
|
|
6281
|
+
const forAttr = [];
|
|
6282
|
+
while (ci < collected.length && /** @type {number} */ (collected[ci].start) < attrStart) {
|
|
6283
|
+
forAttr.push(collected[ci]);
|
|
6284
|
+
ci++;
|
|
6285
|
+
}
|
|
6286
|
+
if (forAttr.length > 0) byAttr.set(ai, forAttr);
|
|
6287
|
+
}
|
|
6288
|
+
return byAttr;
|
|
6289
|
+
}
|
|
6290
|
+
|
|
6291
|
+
/**
|
|
6292
|
+
* Build doc parts for a template child's leading comments (each on its own line).
|
|
6293
|
+
* Used for `{expr}` children, whose `{ … }` form is printed inline by the JSX
|
|
6294
|
+
* printers and so would otherwise skip the node's attached leading comments.
|
|
6295
|
+
* @param {AST.Node & AST.NodeWithMaybeComments} child
|
|
6296
|
+
* @returns {Doc[]}
|
|
6297
|
+
*/
|
|
6298
|
+
function printTemplateChildLeadingComments(child) {
|
|
6299
|
+
const comments = child.leadingComments;
|
|
6300
|
+
if (!comments || comments.length === 0) {
|
|
6301
|
+
return [];
|
|
6302
|
+
}
|
|
6303
|
+
/** @type {Doc[]} */
|
|
6304
|
+
const parts = [];
|
|
6305
|
+
for (let i = 0; i < comments.length; i++) {
|
|
6306
|
+
const comment = comments[i];
|
|
6307
|
+
if (comment.type === 'Line') {
|
|
6308
|
+
parts.push('//' + comment.value);
|
|
6309
|
+
} else if (comment.type === 'Block') {
|
|
6310
|
+
parts.push('/*' + comment.value + '*/');
|
|
6311
|
+
}
|
|
6312
|
+
parts.push(hardline);
|
|
6313
|
+
const next = comments[i + 1];
|
|
6314
|
+
if (next && getBlankLinesBetweenNodes(comment, next) > 0) {
|
|
6315
|
+
parts.push(hardline);
|
|
6316
|
+
}
|
|
6317
|
+
}
|
|
6318
|
+
return parts;
|
|
6319
|
+
}
|
|
6320
|
+
|
|
6321
|
+
/**
|
|
6322
|
+
* Build doc parts for `//` line comments attached to an element body — trailing
|
|
6323
|
+
* comments before `</tag>` (`closingElement.leadingComments`) or the comments of a
|
|
6324
|
+
* comment-only element (`innerComments`). Block comments are intentionally skipped:
|
|
6325
|
+
* they survive in the adjacent JSXText value and are already rendered as text, so
|
|
6326
|
+
* emitting them here would duplicate them. Each comment is emitted on its own line
|
|
6327
|
+
* at the children indent.
|
|
6328
|
+
* @param {AST.Comment[] | null | undefined} commentList
|
|
6329
|
+
* @param {any} [previousNode]
|
|
6330
|
+
* @returns {Doc[]}
|
|
6331
|
+
*/
|
|
6332
|
+
function printElementBodyLineComments(commentList, previousNode = null) {
|
|
6333
|
+
const comments = (commentList ?? []).filter((comment) => comment.type === 'Line');
|
|
6334
|
+
if (comments.length === 0) {
|
|
6335
|
+
return [];
|
|
6336
|
+
}
|
|
6337
|
+
/** @type {Doc[]} */
|
|
6338
|
+
const parts = [];
|
|
6339
|
+
/** @type {AST.Node | AST.Comment | null | undefined} */
|
|
6340
|
+
let prev = previousNode;
|
|
6341
|
+
for (let i = 0; i < comments.length; i++) {
|
|
6342
|
+
parts.push(hardline);
|
|
6343
|
+
// Preserve a blank line before this comment if one existed in source.
|
|
6344
|
+
if (prev && getBlankLinesBetweenNodes(prev, comments[i]) > 0) {
|
|
6345
|
+
parts.push(hardline);
|
|
6346
|
+
}
|
|
6347
|
+
parts.push('//' + comments[i].value);
|
|
6348
|
+
prev = comments[i];
|
|
6349
|
+
}
|
|
6350
|
+
return parts;
|
|
6351
|
+
}
|
|
6352
|
+
|
|
6353
|
+
/**
|
|
6354
|
+
* Print a TSRX code block: setup statements then the single render output.
|
|
6355
|
+
* Callers in element/fragment body position hug it to the surrounding tags;
|
|
6356
|
+
* on its own as an arrow body it stands alone.
|
|
6357
|
+
* @param {AST.JSXCodeBlock} node
|
|
6358
|
+
* @param {AstPath<AST.JSXCodeBlock>} path
|
|
6359
|
+
* @param {RippleFormatOptions} options
|
|
6360
|
+
* @param {PrintFn} print
|
|
6361
|
+
* @returns {Doc}
|
|
6362
|
+
*/
|
|
6363
|
+
function printJSXCodeBlock(node, path, options, print) {
|
|
6364
|
+
/** @type {Doc[]} */
|
|
6365
|
+
const parts = [];
|
|
6366
|
+
for (let i = 0; i < node.body.length; i++) {
|
|
6367
|
+
parts.push(path.call(print, 'body', i));
|
|
6368
|
+
if (i < node.body.length - 1) {
|
|
6369
|
+
parts.push(
|
|
6370
|
+
shouldAddBlankLine(node.body[i], node.body[i + 1]) ? [hardline, hardline] : hardline,
|
|
6371
|
+
);
|
|
6372
|
+
}
|
|
6373
|
+
}
|
|
6374
|
+
if (node.render) {
|
|
6375
|
+
if (node.body.length > 0) {
|
|
6376
|
+
// Preserve a blank line between the last setup statement and the render
|
|
6377
|
+
// output (measured to the render's leading comment, if any).
|
|
6378
|
+
const last = node.body[node.body.length - 1];
|
|
6379
|
+
const renderStart =
|
|
6380
|
+
/** @type {AST.NodeWithMaybeComments} */ (node.render).leadingComments?.[0] ?? node.render;
|
|
6381
|
+
parts.push(
|
|
6382
|
+
getBlankLinesBetweenNodes(last, renderStart) > 0 ? [hardline, hardline] : hardline,
|
|
6383
|
+
);
|
|
6384
|
+
}
|
|
6385
|
+
parts.push(path.call(print, 'render'));
|
|
6386
|
+
}
|
|
6387
|
+
// Trailing comments after the last statement/render inside the block.
|
|
6388
|
+
const innerCommentDocs = printElementBodyLineComments(node.innerComments);
|
|
6389
|
+
if (innerCommentDocs.length > 0) {
|
|
6390
|
+
const lastNode = node.render ?? node.body[node.body.length - 1];
|
|
6391
|
+
const firstComment = (node.innerComments ?? []).find((c) => c.type === 'Line');
|
|
6392
|
+
if (lastNode && firstComment && getBlankLinesBetweenNodes(lastNode, firstComment) > 0) {
|
|
6393
|
+
parts.push(hardline);
|
|
6394
|
+
}
|
|
6395
|
+
parts.push(...innerCommentDocs);
|
|
6396
|
+
}
|
|
6397
|
+
if (parts.length === 0) {
|
|
6398
|
+
return '@{}';
|
|
6399
|
+
}
|
|
6400
|
+
return group(['@{', indent([hardline, ...parts]), hardline, '}']);
|
|
6401
|
+
}
|
|
6402
|
+
|
|
5955
6403
|
/**
|
|
5956
6404
|
* Print a JSX attribute
|
|
5957
6405
|
* @param {ESTreeJSX.JSXAttribute} attr - The JSX attribute node
|
|
@@ -5963,6 +6411,10 @@ function printJSXFragment(node, path, options, print) {
|
|
|
5963
6411
|
function printJSXAttribute(attr, path, options, print) {
|
|
5964
6412
|
const name = /** @type {ESTreeJSX.JSXIdentifier} */ (attr.name).name;
|
|
5965
6413
|
|
|
6414
|
+
if (attr.shorthand) {
|
|
6415
|
+
return ['{', name, '}'];
|
|
6416
|
+
}
|
|
6417
|
+
|
|
5966
6418
|
if (!attr.value) {
|
|
5967
6419
|
return name;
|
|
5968
6420
|
}
|
|
@@ -5980,12 +6432,16 @@ function printJSXAttribute(attr, path, options, print) {
|
|
|
5980
6432
|
|
|
5981
6433
|
if (attr.value.type === 'JSXExpressionContainer') {
|
|
5982
6434
|
const expression = attr.value.expression;
|
|
6435
|
+
if (expression.type === 'Literal' && typeof expression.value === 'string') {
|
|
6436
|
+
const quote = options.jsxSingleQuote ? "'" : '"';
|
|
6437
|
+
return [name, '=', quote, /** @type {string} */ (expression.value), quote];
|
|
6438
|
+
}
|
|
5983
6439
|
const exprDoc = path.call(
|
|
5984
6440
|
(valuePath) => print(valuePath, { isInAttribute: true }),
|
|
5985
6441
|
'value',
|
|
5986
6442
|
'expression',
|
|
5987
6443
|
);
|
|
5988
|
-
if (shouldBreakAttributeExpressionClosingBrace(expression)) {
|
|
6444
|
+
if (shouldBreakAttributeExpressionClosingBrace(expression, options, attr)) {
|
|
5989
6445
|
return [name, '={', exprDoc, hardline, '}'];
|
|
5990
6446
|
}
|
|
5991
6447
|
return [name, '={', exprDoc, '}'];
|
|
@@ -5995,16 +6451,21 @@ function printJSXAttribute(attr, path, options, print) {
|
|
|
5995
6451
|
}
|
|
5996
6452
|
|
|
5997
6453
|
/**
|
|
5998
|
-
* Print a JSX
|
|
5999
|
-
* @param {AST.Node} node - The JSX
|
|
6454
|
+
* Print a JSX element name.
|
|
6455
|
+
* @param {AST.Node} node - The JSX element name node
|
|
6000
6456
|
* @returns {string}
|
|
6001
6457
|
*/
|
|
6002
|
-
function
|
|
6458
|
+
function printJSXElementName(node) {
|
|
6003
6459
|
if (node.type === 'JSXIdentifier') {
|
|
6004
6460
|
return node.name;
|
|
6005
6461
|
}
|
|
6006
6462
|
if (node.type === 'JSXMemberExpression') {
|
|
6007
|
-
return
|
|
6463
|
+
return printJSXElementName(node.object) + '.' + printJSXElementName(node.property);
|
|
6464
|
+
}
|
|
6465
|
+
if (node.type === 'JSXNamespacedName') {
|
|
6466
|
+
const namespace_name = node.namespace.name;
|
|
6467
|
+
const local_name = node.name.name;
|
|
6468
|
+
return namespace_name + ':' + local_name;
|
|
6008
6469
|
}
|
|
6009
6470
|
return 'Unknown';
|
|
6010
6471
|
}
|
|
@@ -6017,8 +6478,24 @@ function printJSXMemberExpression(node) {
|
|
|
6017
6478
|
* @returns {string}
|
|
6018
6479
|
*/
|
|
6019
6480
|
function printMemberExpressionSimple(node, options, computed = false) {
|
|
6481
|
+
if (node.type === 'JSXIdentifier') {
|
|
6482
|
+
return node.name;
|
|
6483
|
+
}
|
|
6484
|
+
|
|
6485
|
+
if (node.type === 'JSXMemberExpression') {
|
|
6486
|
+
return (
|
|
6487
|
+
printMemberExpressionSimple(node.object, options) +
|
|
6488
|
+
'.' +
|
|
6489
|
+
printMemberExpressionSimple(node.property, options, true)
|
|
6490
|
+
);
|
|
6491
|
+
}
|
|
6492
|
+
|
|
6493
|
+
if (node.type === 'JSXNamespacedName') {
|
|
6494
|
+
return node.namespace.name + ':' + node.name.name;
|
|
6495
|
+
}
|
|
6496
|
+
|
|
6020
6497
|
if (node.type === 'Identifier') {
|
|
6021
|
-
return
|
|
6498
|
+
return node.name;
|
|
6022
6499
|
}
|
|
6023
6500
|
|
|
6024
6501
|
if (node.type === 'MemberExpression') {
|
|
@@ -6064,7 +6541,7 @@ function is_attribute_value_breakable(value, is_nested_in_object = false) {
|
|
|
6064
6541
|
}
|
|
6065
6542
|
|
|
6066
6543
|
/**
|
|
6067
|
-
* Print a
|
|
6544
|
+
* Print a JSX element node
|
|
6068
6545
|
* @param {AST.Element} element - The element node
|
|
6069
6546
|
* @param {AstPath<AST.Element>} path - The AST path
|
|
6070
6547
|
* @param {RippleFormatOptions} options - Prettier options
|
|
@@ -6072,7 +6549,7 @@ function is_attribute_value_breakable(value, is_nested_in_object = false) {
|
|
|
6072
6549
|
* @returns {Doc}
|
|
6073
6550
|
*/
|
|
6074
6551
|
function printElement(element, path, options, print) {
|
|
6075
|
-
const node = /** @type {
|
|
6552
|
+
const node = /** @type {any} */ (element);
|
|
6076
6553
|
const tagName = printMemberExpressionSimple(node.id, options);
|
|
6077
6554
|
const openingElement = /** @type {any} */ (node.openingElement);
|
|
6078
6555
|
/** @type {Doc} */
|
|
@@ -6080,7 +6557,7 @@ function printElement(element, path, options, print) {
|
|
|
6080
6557
|
if (openingElement?.typeArguments) {
|
|
6081
6558
|
typeArgsDoc = path.call(print, 'openingElement', 'typeArguments');
|
|
6082
6559
|
}
|
|
6083
|
-
const elementLeadingComments = getElementLeadingComments(node);
|
|
6560
|
+
const elementLeadingComments = getElementLeadingComments(/** @type {any} */ (node));
|
|
6084
6561
|
|
|
6085
6562
|
// `metadata.elementLeadingComments` may include comments that actually appear *inside* the element
|
|
6086
6563
|
// body (after the opening tag). Those must not be hoisted before the element.
|
|
@@ -6115,7 +6592,7 @@ function printElement(element, path, options, print) {
|
|
|
6115
6592
|
const openingEnd = /** @type {AST.NodeWithLocation} */ (node.openingElement).end;
|
|
6116
6593
|
for (const child of node.children) {
|
|
6117
6594
|
if (
|
|
6118
|
-
(child.type === '
|
|
6595
|
+
(child.type === 'JSXExpressionContainer' || child.type === 'JSXText') &&
|
|
6119
6596
|
Array.isArray(child.leadingComments)
|
|
6120
6597
|
) {
|
|
6121
6598
|
for (const comment of child.leadingComments) {
|
|
@@ -6192,11 +6669,14 @@ function printElement(element, path, options, print) {
|
|
|
6192
6669
|
parts.push(attrLineBreak);
|
|
6193
6670
|
const attrDoc = print(attrPath);
|
|
6194
6671
|
parts.push(attrDoc);
|
|
6195
|
-
const attr_node = /** @type {
|
|
6672
|
+
const attr_node = /** @type {ESTreeJSX.JSXAttribute | ESTreeJSX.JSXSpreadAttribute} */ (
|
|
6673
|
+
/** @type {unknown} */ (attrPath.node)
|
|
6674
|
+
);
|
|
6196
6675
|
if (
|
|
6197
6676
|
!hasBreakingAttribute &&
|
|
6198
6677
|
(willBreak(attrDoc) ||
|
|
6199
|
-
(attr_node.type === '
|
|
6678
|
+
(attr_node.type === 'JSXAttribute' &&
|
|
6679
|
+
is_attribute_value_breakable(/** @type {any} */ (attr_node.value))))
|
|
6200
6680
|
) {
|
|
6201
6681
|
hasBreakingAttribute = true;
|
|
6202
6682
|
}
|
|
@@ -6304,15 +6784,17 @@ function printElement(element, path, options, print) {
|
|
|
6304
6784
|
}
|
|
6305
6785
|
}
|
|
6306
6786
|
|
|
6307
|
-
const isTextLikeChild =
|
|
6787
|
+
const isTextLikeChild =
|
|
6788
|
+
currentChild.type === 'JSXExpressionContainer' || currentChild.type === 'JSXText';
|
|
6308
6789
|
const hasTextLeadingComments =
|
|
6309
6790
|
shouldLiftTextLevelComments &&
|
|
6310
6791
|
isTextLikeChild &&
|
|
6311
6792
|
Array.isArray(currentChild.leadingComments) &&
|
|
6312
6793
|
currentChild.leadingComments.length > 0;
|
|
6794
|
+
const currentChildAny = /** @type {any} */ (currentChild);
|
|
6313
6795
|
const rawExpressionLeadingComments =
|
|
6314
|
-
isTextLikeChild && Array.isArray(
|
|
6315
|
-
?
|
|
6796
|
+
isTextLikeChild && Array.isArray(currentChildAny.expression?.leadingComments)
|
|
6797
|
+
? currentChildAny.expression.leadingComments
|
|
6316
6798
|
: null;
|
|
6317
6799
|
const elementBodyLeadingComments =
|
|
6318
6800
|
hasTextLeadingComments && node.openingElement
|
|
@@ -6418,10 +6900,10 @@ function printElement(element, path, options, print) {
|
|
|
6418
6900
|
: nextChild;
|
|
6419
6901
|
const whitespaceLinesCount = getBlankLinesBetweenNodes(currentChild, whitespaceTarget);
|
|
6420
6902
|
const isTextOrExpressionChild =
|
|
6421
|
-
currentChild.type === '
|
|
6422
|
-
currentChild.type === '
|
|
6423
|
-
nextChild.type === '
|
|
6424
|
-
nextChild.type === '
|
|
6903
|
+
currentChild.type === 'JSXExpressionContainer' ||
|
|
6904
|
+
currentChild.type === 'JSXText' ||
|
|
6905
|
+
nextChild.type === 'JSXExpressionContainer' ||
|
|
6906
|
+
nextChild.type === 'JSXText';
|
|
6425
6907
|
|
|
6426
6908
|
if (whitespaceLinesCount > 0) {
|
|
6427
6909
|
finalChildren.push(hardline);
|
|
@@ -6472,10 +6954,15 @@ function printElement(element, path, options, print) {
|
|
|
6472
6954
|
|
|
6473
6955
|
const closingTag = ['</', tagName, '>'];
|
|
6474
6956
|
let elementOutput;
|
|
6957
|
+
const shouldTryInlineMultipleChildren =
|
|
6958
|
+
!openingTagAlwaysBreaks &&
|
|
6959
|
+
fallbackCommentParts.length === 0 &&
|
|
6960
|
+
closingElementComments.length === 0 &&
|
|
6961
|
+
shouldTryInlineMultipleTextChildren(node);
|
|
6475
6962
|
|
|
6476
6963
|
if (finalChildren.length === 1) {
|
|
6477
6964
|
const child = finalChildren[0];
|
|
6478
|
-
const firstChild = node.children[0];
|
|
6965
|
+
const firstChild = /** @type {any} */ (node.children[0]);
|
|
6479
6966
|
const isNonSelfClosingElement =
|
|
6480
6967
|
firstChild && firstChild.type === 'Element' && !firstChild.selfClosing;
|
|
6481
6968
|
const isElementChild = firstChild && firstChild.type === 'Element';
|
|
@@ -6506,63 +6993,15 @@ function printElement(element, path, options, print) {
|
|
|
6506
6993
|
} else {
|
|
6507
6994
|
elementOutput = [openingTag, indent([hardline, ...finalChildren]), hardline, closingTag];
|
|
6508
6995
|
}
|
|
6996
|
+
} else if (shouldTryInlineMultipleChildren) {
|
|
6997
|
+
const inlineChildren = path.map(print, 'children');
|
|
6998
|
+
elementOutput = conditionalGroup([
|
|
6999
|
+
group([openingTag, ...inlineChildren, closingTag]),
|
|
7000
|
+
[openingTag, indent([hardline, ...finalChildren]), hardline, closingTag],
|
|
7001
|
+
]);
|
|
6509
7002
|
} else {
|
|
6510
7003
|
elementOutput = group([openingTag, indent([hardline, ...finalChildren]), hardline, closingTag]);
|
|
6511
7004
|
}
|
|
6512
7005
|
|
|
6513
7006
|
return leadingCommentParts.length > 0 ? [...leadingCommentParts, elementOutput] : elementOutput;
|
|
6514
7007
|
}
|
|
6515
|
-
|
|
6516
|
-
/**
|
|
6517
|
-
* Print a Ripple attribute node
|
|
6518
|
-
* @param {AST.Attribute} node - The attribute node
|
|
6519
|
-
* @param {AstPath<AST.Attribute>} path - The AST path
|
|
6520
|
-
* @param {RippleFormatOptions} options - Prettier options
|
|
6521
|
-
* @param {PrintFn} print - Print callback
|
|
6522
|
-
* @returns {Doc[]}
|
|
6523
|
-
*/
|
|
6524
|
-
function printAttribute(node, path, options, print) {
|
|
6525
|
-
/** @type {Doc[]} */
|
|
6526
|
-
const parts = [];
|
|
6527
|
-
|
|
6528
|
-
// Handle shorthand syntax: {id} instead of id={id}
|
|
6529
|
-
// Check if either node.shorthand is true, OR if the value is an Identifier with the same name
|
|
6530
|
-
const isShorthand =
|
|
6531
|
-
node.shorthand ||
|
|
6532
|
-
(node.value && node.value.type === 'Identifier' && node.value.name === node.name.name);
|
|
6533
|
-
|
|
6534
|
-
if (isShorthand) {
|
|
6535
|
-
parts.push('{');
|
|
6536
|
-
parts.push(node.name.name);
|
|
6537
|
-
parts.push('}');
|
|
6538
|
-
return parts;
|
|
6539
|
-
}
|
|
6540
|
-
|
|
6541
|
-
parts.push(node.name.name);
|
|
6542
|
-
|
|
6543
|
-
if (node.value) {
|
|
6544
|
-
if (node.value.type === 'Literal' && typeof node.value.value === 'string') {
|
|
6545
|
-
// String literals don't need curly braces
|
|
6546
|
-
// Use jsxSingleQuote option if available, otherwise use double quotes
|
|
6547
|
-
parts.push('=');
|
|
6548
|
-
const useJsxSingleQuote = options.jsxSingleQuote === true;
|
|
6549
|
-
parts.push(
|
|
6550
|
-
formatStringLiteral(node.value.value, {
|
|
6551
|
-
...options,
|
|
6552
|
-
singleQuote: useJsxSingleQuote,
|
|
6553
|
-
}),
|
|
6554
|
-
);
|
|
6555
|
-
} else {
|
|
6556
|
-
// All other values need curly braces: numbers, booleans, null, expressions, etc.
|
|
6557
|
-
parts.push('={');
|
|
6558
|
-
// Pass inline context for attribute values (keep objects compact)
|
|
6559
|
-
parts.push(path.call((attrPath) => print(attrPath, { isInAttribute: true }), 'value'));
|
|
6560
|
-
if (shouldBreakAttributeExpressionClosingBrace(node.value)) {
|
|
6561
|
-
parts.push(hardline);
|
|
6562
|
-
}
|
|
6563
|
-
parts.push('}');
|
|
6564
|
-
}
|
|
6565
|
-
}
|
|
6566
|
-
|
|
6567
|
-
return parts;
|
|
6568
|
-
}
|