@tsrx/prettier-plugin 0.3.78 → 0.3.79
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 +89 -727
- package/src/index.test.js +98 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tsrx/prettier-plugin",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.79",
|
|
4
4
|
"description": "Ripple plugin for Prettier",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"prettier": "^3.8.3"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@tsrx/core": "0.1.
|
|
30
|
+
"@tsrx/core": "0.1.27"
|
|
31
31
|
},
|
|
32
32
|
"files": [
|
|
33
33
|
"src/"
|
package/src/index.js
CHANGED
|
@@ -2292,20 +2292,20 @@ function printRippleNode(node, path, options, print, args) {
|
|
|
2292
2292
|
break;
|
|
2293
2293
|
|
|
2294
2294
|
case 'JSXStyleElement':
|
|
2295
|
-
nodeContent = printJSXElement(
|
|
2296
|
-
/** @type {ESTreeJSX.JSXElement} */ (/** @type {unknown} */ (node)),
|
|
2297
|
-
path,
|
|
2298
|
-
options,
|
|
2299
|
-
print,
|
|
2300
|
-
);
|
|
2295
|
+
nodeContent = printJSXElement(node, path, options, print);
|
|
2301
2296
|
break;
|
|
2302
2297
|
|
|
2303
2298
|
case 'JSXElement':
|
|
2304
|
-
nodeContent = printJSXElement(node, path, options, print);
|
|
2299
|
+
nodeContent = printJSXElement(/** @type {AST.TSRXJSXElement} */ (node), path, options, print);
|
|
2305
2300
|
break;
|
|
2306
2301
|
|
|
2307
2302
|
case 'JSXFragment':
|
|
2308
|
-
nodeContent = printJSXFragment(
|
|
2303
|
+
nodeContent = printJSXFragment(
|
|
2304
|
+
/** @type {AST.TSRXJSXFragment} */ (node),
|
|
2305
|
+
path,
|
|
2306
|
+
options,
|
|
2307
|
+
print,
|
|
2308
|
+
);
|
|
2309
2309
|
break;
|
|
2310
2310
|
|
|
2311
2311
|
case 'JSXText':
|
|
@@ -5694,86 +5694,6 @@ function normalizeInlineJSXText(raw) {
|
|
|
5694
5694
|
return text.trim() || !/[\r\n]/u.test(text) ? text : '';
|
|
5695
5695
|
}
|
|
5696
5696
|
|
|
5697
|
-
/**
|
|
5698
|
-
* @param {AST.Node} parentNode
|
|
5699
|
-
* @param {AST.Node} firstChild
|
|
5700
|
-
* @param {Doc} childDoc
|
|
5701
|
-
* @returns {boolean}
|
|
5702
|
-
*/
|
|
5703
|
-
function shouldInlineSingleChild(parentNode, firstChild, childDoc) {
|
|
5704
|
-
if (!firstChild || childDoc == null) {
|
|
5705
|
-
return false;
|
|
5706
|
-
}
|
|
5707
|
-
|
|
5708
|
-
if (firstChild.type === 'JSXText') {
|
|
5709
|
-
return true;
|
|
5710
|
-
}
|
|
5711
|
-
|
|
5712
|
-
if (typeof childDoc === 'string') {
|
|
5713
|
-
return childDoc.length <= 20 && !childDoc.includes('\n');
|
|
5714
|
-
}
|
|
5715
|
-
|
|
5716
|
-
// Inline JSX expressions if they fit, but respect original multi-line formatting
|
|
5717
|
-
// for non-literal expressions (e.g. {children} should stay multi-line if written that way)
|
|
5718
|
-
if (firstChild.type === 'JSXExpressionContainer') {
|
|
5719
|
-
if (wasOriginallySingleLine(parentNode)) {
|
|
5720
|
-
return true;
|
|
5721
|
-
}
|
|
5722
|
-
// For multi-line parents, only inline if the expression is a simple literal
|
|
5723
|
-
const expr = firstChild.expression;
|
|
5724
|
-
if (expr && (expr.type === 'Literal' || expr.type === 'TemplateLiteral')) {
|
|
5725
|
-
return true;
|
|
5726
|
-
}
|
|
5727
|
-
return false;
|
|
5728
|
-
}
|
|
5729
|
-
|
|
5730
|
-
// Respect original formatting for elements: if parent was originally multi-line, keep it multi-line
|
|
5731
|
-
// This follows Prettier's philosophy for decorators and objects
|
|
5732
|
-
if (!wasOriginallySingleLine(parentNode)) {
|
|
5733
|
-
return false;
|
|
5734
|
-
}
|
|
5735
|
-
|
|
5736
|
-
if (firstChild.type === 'JSXElement' && firstChild.openingElement?.selfClosing) {
|
|
5737
|
-
const parent = /** @type {any} */ (parentNode);
|
|
5738
|
-
return !parent.openingElement?.attributes?.length;
|
|
5739
|
-
}
|
|
5740
|
-
|
|
5741
|
-
return false;
|
|
5742
|
-
}
|
|
5743
|
-
|
|
5744
|
-
/**
|
|
5745
|
-
* Check whether a child can participate in compact inline TSRX content.
|
|
5746
|
-
* @param {any} child
|
|
5747
|
-
* @returns {boolean}
|
|
5748
|
-
*/
|
|
5749
|
-
function isInlineableTextOrExpressionChild(child) {
|
|
5750
|
-
if (!child || (child.type !== 'JSXText' && child.type !== 'JSXExpressionContainer')) {
|
|
5751
|
-
return false;
|
|
5752
|
-
}
|
|
5753
|
-
|
|
5754
|
-
if (hasComment(/** @type {AST.Node & AST.NodeWithMaybeComments} */ (child))) {
|
|
5755
|
-
return false;
|
|
5756
|
-
}
|
|
5757
|
-
|
|
5758
|
-
const expression = /** @type {{ expression?: AST.Node & AST.NodeWithMaybeComments }} */ (child)
|
|
5759
|
-
.expression;
|
|
5760
|
-
return !expression || !hasComment(expression);
|
|
5761
|
-
}
|
|
5762
|
-
|
|
5763
|
-
/**
|
|
5764
|
-
* @param {any} node
|
|
5765
|
-
* @returns {boolean}
|
|
5766
|
-
*/
|
|
5767
|
-
function shouldTryInlineMultipleTextChildren(node) {
|
|
5768
|
-
return (
|
|
5769
|
-
wasOriginallySingleLine(node) &&
|
|
5770
|
-
Array.isArray(node.children) &&
|
|
5771
|
-
node.children.length > 1 &&
|
|
5772
|
-
node.children.some((/** @type {any} */ child) => child.type === 'JSXText') &&
|
|
5773
|
-
node.children.every(isInlineableTextOrExpressionChild)
|
|
5774
|
-
);
|
|
5775
|
-
}
|
|
5776
|
-
|
|
5777
5697
|
/**
|
|
5778
5698
|
* @param {AST.Node} child
|
|
5779
5699
|
* @returns {boolean}
|
|
@@ -5795,72 +5715,10 @@ function isSimpleJSXExpressionChild(child) {
|
|
|
5795
5715
|
);
|
|
5796
5716
|
}
|
|
5797
5717
|
|
|
5798
|
-
/**
|
|
5799
|
-
* Get leading comments from element metadata
|
|
5800
|
-
* @param {ESTreeJSX.JSXElement} node - The element node
|
|
5801
|
-
* @returns {AST.Comment[]}
|
|
5802
|
-
*/
|
|
5803
|
-
function getElementLeadingComments(node) {
|
|
5804
|
-
const fromMetadata = node?.metadata?.elementLeadingComments;
|
|
5805
|
-
if (Array.isArray(fromMetadata)) {
|
|
5806
|
-
return fromMetadata;
|
|
5807
|
-
}
|
|
5808
|
-
return [];
|
|
5809
|
-
}
|
|
5810
|
-
|
|
5811
|
-
/**
|
|
5812
|
-
* Create doc parts for element-level comments
|
|
5813
|
-
* @param {AST.Comment[]} comments - Array of comments
|
|
5814
|
-
* @returns {Doc[]}
|
|
5815
|
-
*/
|
|
5816
|
-
function createElementLevelCommentParts(comments) {
|
|
5817
|
-
if (!comments || comments.length === 0) {
|
|
5818
|
-
return [];
|
|
5819
|
-
}
|
|
5820
|
-
|
|
5821
|
-
/** @type {Doc[]} */
|
|
5822
|
-
const parts = [];
|
|
5823
|
-
|
|
5824
|
-
for (let i = 0; i < comments.length; i++) {
|
|
5825
|
-
const comment = comments[i];
|
|
5826
|
-
const nextComment = comments[i + 1];
|
|
5827
|
-
|
|
5828
|
-
if (comment.type === 'Line') {
|
|
5829
|
-
parts.push('//' + comment.value);
|
|
5830
|
-
parts.push(hardline);
|
|
5831
|
-
} else if (comment.type === 'Block') {
|
|
5832
|
-
parts.push('/*' + comment.value + '*/');
|
|
5833
|
-
parts.push(hardline);
|
|
5834
|
-
}
|
|
5835
|
-
|
|
5836
|
-
if (nextComment) {
|
|
5837
|
-
const blankLinesBetween = getBlankLinesBetweenNodes(comment, nextComment);
|
|
5838
|
-
if (blankLinesBetween > 0) {
|
|
5839
|
-
parts.push(hardline);
|
|
5840
|
-
}
|
|
5841
|
-
}
|
|
5842
|
-
}
|
|
5843
|
-
|
|
5844
|
-
return parts;
|
|
5845
|
-
}
|
|
5846
|
-
|
|
5847
|
-
/**
|
|
5848
|
-
* Create element-level comment parts with trailing hardline trimmed
|
|
5849
|
-
* @param {AST.Comment[]} comments - Array of comments
|
|
5850
|
-
* @returns {Doc[]}
|
|
5851
|
-
*/
|
|
5852
|
-
function createElementLevelCommentPartsTrimmed(comments) {
|
|
5853
|
-
const parts = createElementLevelCommentParts(comments);
|
|
5854
|
-
if (parts.length > 0 && parts[parts.length - 1] === hardline) {
|
|
5855
|
-
parts.pop();
|
|
5856
|
-
}
|
|
5857
|
-
return parts;
|
|
5858
|
-
}
|
|
5859
|
-
|
|
5860
5718
|
/**
|
|
5861
5719
|
* Print a JSX element
|
|
5862
|
-
* @param {AST.TSRXJSXElement} node - The JSX element node
|
|
5863
|
-
* @param {AstPath<
|
|
5720
|
+
* @param {AST.TSRXJSXElement | AST.JSXStyleElement} node - The JSX element node
|
|
5721
|
+
* @param {AstPath<AST.TSRXJSXElement>} path - The AST path
|
|
5864
5722
|
* @param {RippleFormatOptions} options - Prettier options
|
|
5865
5723
|
* @param {PrintFn} print - Print callback
|
|
5866
5724
|
* @returns {Doc | Doc[]}
|
|
@@ -5873,7 +5731,7 @@ function printJSXElement(node, path, options, print) {
|
|
|
5873
5731
|
// Dynamic tags (`<{expr}>`) print the opening expression for both tags so
|
|
5874
5732
|
// they stay textually identical; static names print as plain strings.
|
|
5875
5733
|
const tagName =
|
|
5876
|
-
|
|
5734
|
+
openingElement.name.type === 'JSXExpressionContainer'
|
|
5877
5735
|
? ['{', path.call(print, 'openingElement', 'name', 'expression'), '}']
|
|
5878
5736
|
: printJSXElementName(openingElement.name);
|
|
5879
5737
|
|
|
@@ -5960,26 +5818,13 @@ function printJSXElement(node, path, options, print) {
|
|
|
5960
5818
|
{ shouldBreak: shouldForceBreak },
|
|
5961
5819
|
);
|
|
5962
5820
|
|
|
5963
|
-
//
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
const bodyMetaComments = (node.metadata?.elementLeadingComments ?? []).filter(
|
|
5969
|
-
(/** @type {AST.Comment} */ comment) =>
|
|
5970
|
-
typeof comment.start === 'number' && comment.start >= openingTagEnd,
|
|
5821
|
+
// Comments before `</tag>` and the comments of a comment-only element.
|
|
5822
|
+
const { closingCommentDocs, innerCommentDocs } = collectElementBodyCommentDocs(
|
|
5823
|
+
/** @type {AST.TSRXJSXElement} */ (node),
|
|
5824
|
+
openingElement,
|
|
5825
|
+
node.closingElement,
|
|
5971
5826
|
);
|
|
5972
|
-
const trailingComments = [
|
|
5973
|
-
...(node.closingElement?.leadingComments ?? []),
|
|
5974
|
-
...bodyMetaComments,
|
|
5975
|
-
].sort((a, b) => /** @type {number} */ (a.start) - /** @type {number} */ (b.start));
|
|
5976
|
-
const lastMeaningfulChild = [...(node.children ?? [])]
|
|
5977
|
-
.reverse()
|
|
5978
|
-
.find((child) => child.type !== 'JSXText' || child.value.trim());
|
|
5979
|
-
const closingCommentDocs = printElementBodyLineComments(trailingComments, lastMeaningfulChild);
|
|
5980
5827
|
const hasClosingComments = closingCommentDocs.length > 0;
|
|
5981
|
-
// A comment-only element has no children; its comments live in `innerComments`.
|
|
5982
|
-
const innerCommentDocs = printElementBodyLineComments(node.innerComments);
|
|
5983
5828
|
|
|
5984
5829
|
if (!hasChildren) {
|
|
5985
5830
|
const bodyComments = [...innerCommentDocs, ...closingCommentDocs];
|
|
@@ -6112,7 +5957,7 @@ function printJSXElement(node, path, options, print) {
|
|
|
6112
5957
|
];
|
|
6113
5958
|
}
|
|
6114
5959
|
const meaningfulChildren = node.children.filter(
|
|
6115
|
-
(
|
|
5960
|
+
(child) => child.type !== 'JSXText' || child.value.trim(),
|
|
6116
5961
|
);
|
|
6117
5962
|
const singleMeaningfulChild = meaningfulChildren.length === 1 ? meaningfulChildren[0] : null;
|
|
6118
5963
|
if (
|
|
@@ -6127,9 +5972,9 @@ function printJSXElement(node, path, options, print) {
|
|
|
6127
5972
|
!forceMultiline &&
|
|
6128
5973
|
childrenDocs.length > 1 &&
|
|
6129
5974
|
wasOriginallySingleLine(node) &&
|
|
6130
|
-
node.children.some((
|
|
5975
|
+
node.children.some((child) => child.type === 'JSXText') &&
|
|
6131
5976
|
node.children.every(
|
|
6132
|
-
(
|
|
5977
|
+
(child) =>
|
|
6133
5978
|
child.type === 'JSXText' || isSimpleJSXExpressionChild(/** @type {AST.Node} */ (child)),
|
|
6134
5979
|
)
|
|
6135
5980
|
) {
|
|
@@ -6162,8 +6007,8 @@ function printJSXElement(node, path, options, print) {
|
|
|
6162
6007
|
|
|
6163
6008
|
/**
|
|
6164
6009
|
* Print a JSX fragment (<>...</>)
|
|
6165
|
-
* @param {
|
|
6166
|
-
* @param {AstPath<
|
|
6010
|
+
* @param {AST.TSRXJSXFragment} node - The JSX fragment node
|
|
6011
|
+
* @param {AstPath<AST.TSRXJSXFragment>} path - The AST path
|
|
6167
6012
|
* @param {RippleFormatOptions} options - Prettier options
|
|
6168
6013
|
* @param {PrintFn} print - Print callback
|
|
6169
6014
|
* @returns {Doc}
|
|
@@ -6171,12 +6016,23 @@ function printJSXElement(node, path, options, print) {
|
|
|
6171
6016
|
function printJSXFragment(node, path, options, print) {
|
|
6172
6017
|
const hasChildren = node.children && node.children.length > 0;
|
|
6173
6018
|
|
|
6019
|
+
// Comments before `</>` and the comments of a comment-only fragment.
|
|
6020
|
+
const { closingCommentDocs, innerCommentDocs } = collectElementBodyCommentDocs(
|
|
6021
|
+
node,
|
|
6022
|
+
node.openingFragment,
|
|
6023
|
+
node.closingFragment,
|
|
6024
|
+
);
|
|
6025
|
+
|
|
6174
6026
|
if (!hasChildren) {
|
|
6027
|
+
const bodyComments = [...innerCommentDocs, ...closingCommentDocs];
|
|
6028
|
+
if (bodyComments.length > 0) {
|
|
6029
|
+
return group(['<>', indent(bodyComments), hardline, '</>']);
|
|
6030
|
+
}
|
|
6175
6031
|
return '<></>';
|
|
6176
6032
|
}
|
|
6177
6033
|
|
|
6178
6034
|
// A `@{ … }` code block is the whole body and hugs the tags: `<>@{ … }</>`.
|
|
6179
|
-
if (node.children.length === 1 &&
|
|
6035
|
+
if (node.children.length === 1 && node.children[0].type === 'JSXCodeBlock') {
|
|
6180
6036
|
return group(['<>', path.call(print, 'children', 0), '</>']);
|
|
6181
6037
|
}
|
|
6182
6038
|
|
|
@@ -6219,7 +6075,11 @@ function printJSXFragment(node, path, options, print) {
|
|
|
6219
6075
|
}
|
|
6220
6076
|
|
|
6221
6077
|
// Check if content can be inlined (single text node or single expression)
|
|
6222
|
-
if (
|
|
6078
|
+
if (
|
|
6079
|
+
childrenDocs.length === 1 &&
|
|
6080
|
+
typeof childrenDocs[0] === 'string' &&
|
|
6081
|
+
closingCommentDocs.length === 0
|
|
6082
|
+
) {
|
|
6223
6083
|
return ['<>', childrenDocs[0], '</>'];
|
|
6224
6084
|
}
|
|
6225
6085
|
const meaningfulChildren = node.children.filter(
|
|
@@ -6230,6 +6090,7 @@ function printJSXFragment(node, path, options, print) {
|
|
|
6230
6090
|
meaningfulChildren.length === 1 &&
|
|
6231
6091
|
meaningfulChildren[0].type === 'JSXElement' &&
|
|
6232
6092
|
wasOriginallySingleLine(node) &&
|
|
6093
|
+
closingCommentDocs.length === 0 &&
|
|
6233
6094
|
!willBreak(childrenDocs[0])
|
|
6234
6095
|
) {
|
|
6235
6096
|
// Keep the fragment inline when it fits; otherwise expand `<>` onto its own
|
|
@@ -6253,7 +6114,12 @@ function printJSXFragment(node, path, options, print) {
|
|
|
6253
6114
|
}
|
|
6254
6115
|
|
|
6255
6116
|
// Build the final fragment
|
|
6256
|
-
return group([
|
|
6117
|
+
return group([
|
|
6118
|
+
'<>',
|
|
6119
|
+
indent([hardline, ...formattedChildren, ...closingCommentDocs]),
|
|
6120
|
+
hardline,
|
|
6121
|
+
'</>',
|
|
6122
|
+
]);
|
|
6257
6123
|
}
|
|
6258
6124
|
|
|
6259
6125
|
/**
|
|
@@ -6262,14 +6128,14 @@ function printJSXFragment(node, path, options, print) {
|
|
|
6262
6128
|
* opening tag's end, but the child is visited first). Pull those out of the
|
|
6263
6129
|
* children and return a map from attribute index to the comments that precede it,
|
|
6264
6130
|
* so the element printer can render them in the opening tag instead of the body.
|
|
6265
|
-
* @param {AST.TSRXJSXElement} node
|
|
6131
|
+
* @param {AST.TSRXJSXElement | AST.JSXStyleElement} node
|
|
6266
6132
|
* @returns {Map<number, AST.Comment[]>}
|
|
6267
6133
|
*/
|
|
6268
6134
|
function collectOpeningTagComments(node) {
|
|
6269
6135
|
/** @type {Map<number, AST.Comment[]>} */
|
|
6270
6136
|
const byAttr = new Map();
|
|
6271
6137
|
const openingElement = /** @type {AST.NodeWithLocation} */ (node.openingElement);
|
|
6272
|
-
const attributes =
|
|
6138
|
+
const attributes = node.openingElement?.attributes ?? [];
|
|
6273
6139
|
if (!openingElement || attributes.length === 0 || !Array.isArray(node.children)) {
|
|
6274
6140
|
return byAttr;
|
|
6275
6141
|
}
|
|
@@ -6288,7 +6154,7 @@ function collectOpeningTagComments(node) {
|
|
|
6288
6154
|
}
|
|
6289
6155
|
}
|
|
6290
6156
|
if (keep.length !== lead.length) {
|
|
6291
|
-
|
|
6157
|
+
child.leadingComments = keep;
|
|
6292
6158
|
}
|
|
6293
6159
|
}
|
|
6294
6160
|
if (collected.length === 0) return byAttr;
|
|
@@ -6338,18 +6204,48 @@ function printTemplateChildLeadingComments(child) {
|
|
|
6338
6204
|
}
|
|
6339
6205
|
|
|
6340
6206
|
/**
|
|
6341
|
-
*
|
|
6207
|
+
* Collect and print the comments that belong to an element/fragment body:
|
|
6208
|
+
* trailing comments after the last child (attached by the parser to the closing
|
|
6209
|
+
* tag's `leadingComments` or, when the last child is an `{expr}` container, to
|
|
6210
|
+
* `metadata.elementLeadingComments` positioned inside the body) and the comments
|
|
6211
|
+
* of a comment-only body (`innerComments`).
|
|
6212
|
+
* @param {AST.TSRXJSXElement | AST.TSRXJSXFragment} node
|
|
6213
|
+
* @param {AST.TSRXJSXElement['openingElement'] | AST.TSRXJSXFragment['openingFragment']} openingNode
|
|
6214
|
+
* @param {AST.TSRXJSXElement['closingElement'] | AST.TSRXJSXFragment['closingFragment']} closingNode
|
|
6215
|
+
* @returns {{ closingCommentDocs: Doc[], innerCommentDocs: Doc[] }}
|
|
6216
|
+
*/
|
|
6217
|
+
function collectElementBodyCommentDocs(node, openingNode, closingNode) {
|
|
6218
|
+
const openingEnd = openingNode?.end;
|
|
6219
|
+
const bodyMetaComments = (node.metadata?.elementLeadingComments ?? []).filter(
|
|
6220
|
+
(/** @type {AST.Comment} */ comment) =>
|
|
6221
|
+
typeof comment.start === 'number' &&
|
|
6222
|
+
typeof openingEnd === 'number' &&
|
|
6223
|
+
comment.start >= openingEnd,
|
|
6224
|
+
);
|
|
6225
|
+
const trailingComments = [...(closingNode?.leadingComments ?? []), ...bodyMetaComments].sort(
|
|
6226
|
+
(/** @type {AST.Comment} */ a, /** @type {AST.Comment} */ b) =>
|
|
6227
|
+
/** @type {number} */ (a.start) - /** @type {number} */ (b.start),
|
|
6228
|
+
);
|
|
6229
|
+
const lastMeaningfulChild = [...(node.children ?? [])]
|
|
6230
|
+
.reverse()
|
|
6231
|
+
.find((child) => child.type !== 'JSXText' || child.value.trim());
|
|
6232
|
+
return {
|
|
6233
|
+
closingCommentDocs: printElementBodyComments(trailingComments, lastMeaningfulChild),
|
|
6234
|
+
innerCommentDocs: printElementBodyComments(node.innerComments),
|
|
6235
|
+
};
|
|
6236
|
+
}
|
|
6237
|
+
|
|
6238
|
+
/**
|
|
6239
|
+
* Build doc parts for comments attached to an element body — trailing
|
|
6342
6240
|
* comments before `</tag>` (`closingElement.leadingComments`) or the comments of a
|
|
6343
|
-
* comment-only element (`innerComments`).
|
|
6344
|
-
* they survive in the adjacent JSXText value and are already rendered as text, so
|
|
6345
|
-
* emitting them here would duplicate them. Each comment is emitted on its own line
|
|
6241
|
+
* comment-only element (`innerComments`). Each comment is emitted on its own line
|
|
6346
6242
|
* at the children indent.
|
|
6347
6243
|
* @param {AST.Comment[] | null | undefined} commentList
|
|
6348
6244
|
* @param {any} [previousNode]
|
|
6349
6245
|
* @returns {Doc[]}
|
|
6350
6246
|
*/
|
|
6351
|
-
function
|
|
6352
|
-
const comments =
|
|
6247
|
+
function printElementBodyComments(commentList, previousNode = null) {
|
|
6248
|
+
const comments = commentList ?? [];
|
|
6353
6249
|
if (comments.length === 0) {
|
|
6354
6250
|
return [];
|
|
6355
6251
|
}
|
|
@@ -6363,7 +6259,9 @@ function printElementBodyLineComments(commentList, previousNode = null) {
|
|
|
6363
6259
|
if (prev && getBlankLinesBetweenNodes(prev, comments[i]) > 0) {
|
|
6364
6260
|
parts.push(hardline);
|
|
6365
6261
|
}
|
|
6366
|
-
parts.push(
|
|
6262
|
+
parts.push(
|
|
6263
|
+
comments[i].type === 'Line' ? '//' + comments[i].value : '/*' + comments[i].value + '*/',
|
|
6264
|
+
);
|
|
6367
6265
|
prev = comments[i];
|
|
6368
6266
|
}
|
|
6369
6267
|
return parts;
|
|
@@ -6404,10 +6302,10 @@ function printJSXCodeBlock(node, path, options, print) {
|
|
|
6404
6302
|
parts.push(path.call(print, 'render'));
|
|
6405
6303
|
}
|
|
6406
6304
|
// Trailing comments after the last statement/render inside the block.
|
|
6407
|
-
const innerCommentDocs =
|
|
6305
|
+
const innerCommentDocs = printElementBodyComments(node.innerComments);
|
|
6408
6306
|
if (innerCommentDocs.length > 0) {
|
|
6409
6307
|
const lastNode = node.render ?? node.body[node.body.length - 1];
|
|
6410
|
-
const firstComment = (node.innerComments ?? [])
|
|
6308
|
+
const firstComment = (node.innerComments ?? [])[0];
|
|
6411
6309
|
if (lastNode && firstComment && getBlankLinesBetweenNodes(lastNode, firstComment) > 0) {
|
|
6412
6310
|
parts.push(hardline);
|
|
6413
6311
|
}
|
|
@@ -6488,539 +6386,3 @@ function printJSXElementName(node) {
|
|
|
6488
6386
|
}
|
|
6489
6387
|
return 'Unknown';
|
|
6490
6388
|
}
|
|
6491
|
-
|
|
6492
|
-
/**
|
|
6493
|
-
* Print a member expression as simple string (for element tag names)
|
|
6494
|
-
* @param {AST.Node} node - The node to print
|
|
6495
|
-
* @param {RippleFormatOptions} options - Prettier options
|
|
6496
|
-
* @param {boolean} [computed=false] - Whether the property is computed
|
|
6497
|
-
* @returns {string}
|
|
6498
|
-
*/
|
|
6499
|
-
function printMemberExpressionSimple(node, options, computed = false) {
|
|
6500
|
-
if (node.type === 'JSXIdentifier') {
|
|
6501
|
-
return node.name;
|
|
6502
|
-
}
|
|
6503
|
-
|
|
6504
|
-
if (node.type === 'JSXMemberExpression') {
|
|
6505
|
-
return (
|
|
6506
|
-
printMemberExpressionSimple(node.object, options) +
|
|
6507
|
-
'.' +
|
|
6508
|
-
printMemberExpressionSimple(node.property, options, true)
|
|
6509
|
-
);
|
|
6510
|
-
}
|
|
6511
|
-
|
|
6512
|
-
if (node.type === 'JSXNamespacedName') {
|
|
6513
|
-
return node.namespace.name + ':' + node.name.name;
|
|
6514
|
-
}
|
|
6515
|
-
|
|
6516
|
-
if (node.type === 'Identifier') {
|
|
6517
|
-
return node.name;
|
|
6518
|
-
}
|
|
6519
|
-
|
|
6520
|
-
if (node.type === 'MemberExpression') {
|
|
6521
|
-
const obj = printMemberExpressionSimple(node.object, options);
|
|
6522
|
-
let prop;
|
|
6523
|
-
if (node.computed) {
|
|
6524
|
-
prop = '[' + printMemberExpressionSimple(node.property, options, true) + ']';
|
|
6525
|
-
} else {
|
|
6526
|
-
prop = '.' + printMemberExpressionSimple(node.property, options, true);
|
|
6527
|
-
}
|
|
6528
|
-
return obj + prop;
|
|
6529
|
-
}
|
|
6530
|
-
|
|
6531
|
-
if (node.type === 'Literal') {
|
|
6532
|
-
return computed ? formatStringLiteral(node.value, options) : JSON.stringify(node.value);
|
|
6533
|
-
}
|
|
6534
|
-
return '';
|
|
6535
|
-
}
|
|
6536
|
-
|
|
6537
|
-
/**
|
|
6538
|
-
* Check whether an attribute value can expand into multiline content.
|
|
6539
|
-
* @param {AST.Expression | null | undefined} value - The attribute value node
|
|
6540
|
-
* @param {boolean} [is_nested_in_object=false] - Whether this value is nested within an object literal
|
|
6541
|
-
* @returns {boolean}
|
|
6542
|
-
*/
|
|
6543
|
-
function is_attribute_value_breakable(value, is_nested_in_object = false) {
|
|
6544
|
-
if (!value) return false;
|
|
6545
|
-
|
|
6546
|
-
switch (value.type) {
|
|
6547
|
-
case 'ConditionalExpression':
|
|
6548
|
-
// Keep simple top-level ternary attributes inline when they fit.
|
|
6549
|
-
// We only force-break when a conditional is nested in an object literal value.
|
|
6550
|
-
return is_nested_in_object;
|
|
6551
|
-
case 'ObjectExpression':
|
|
6552
|
-
return value.properties.some(
|
|
6553
|
-
(property) =>
|
|
6554
|
-
property.type === 'Property' &&
|
|
6555
|
-
is_attribute_value_breakable(/** @type {AST.Expression} */ (property.value), true),
|
|
6556
|
-
);
|
|
6557
|
-
default:
|
|
6558
|
-
return false;
|
|
6559
|
-
}
|
|
6560
|
-
}
|
|
6561
|
-
|
|
6562
|
-
/**
|
|
6563
|
-
* Print a JSX element node
|
|
6564
|
-
* @param {AST.Element} element - The element node
|
|
6565
|
-
* @param {AstPath<AST.Element>} path - The AST path
|
|
6566
|
-
* @param {RippleFormatOptions} options - Prettier options
|
|
6567
|
-
* @param {PrintFn} print - Print callback
|
|
6568
|
-
* @returns {Doc}
|
|
6569
|
-
*/
|
|
6570
|
-
function printElement(element, path, options, print) {
|
|
6571
|
-
const node = /** @type {any} */ (element);
|
|
6572
|
-
const tagName = printMemberExpressionSimple(node.id, options);
|
|
6573
|
-
const openingElement = /** @type {any} */ (node.openingElement);
|
|
6574
|
-
/** @type {Doc} */
|
|
6575
|
-
let typeArgsDoc = '';
|
|
6576
|
-
if (openingElement?.typeArguments) {
|
|
6577
|
-
typeArgsDoc = path.call(print, 'openingElement', 'typeArguments');
|
|
6578
|
-
}
|
|
6579
|
-
const elementLeadingComments = getElementLeadingComments(/** @type {any} */ (node));
|
|
6580
|
-
|
|
6581
|
-
// `metadata.elementLeadingComments` may include comments that actually appear *inside* the element
|
|
6582
|
-
// body (after the opening tag). Those must not be hoisted before the element.
|
|
6583
|
-
const outerElementLeadingComments = elementLeadingComments.filter(
|
|
6584
|
-
(/** @type {AST.Comment} */ comment) =>
|
|
6585
|
-
typeof comment.start !== 'number' || comment.start < node.start,
|
|
6586
|
-
);
|
|
6587
|
-
const innerElementBodyComments = elementLeadingComments.filter(
|
|
6588
|
-
(/** @type {AST.Comment} */ comment) =>
|
|
6589
|
-
typeof comment.start === 'number' &&
|
|
6590
|
-
comment.start >= /** @type {AST.NodeWithLocation} */ (node.openingElement).end &&
|
|
6591
|
-
comment.start < node.end,
|
|
6592
|
-
);
|
|
6593
|
-
const metadataCommentParts =
|
|
6594
|
-
outerElementLeadingComments.length > 0
|
|
6595
|
-
? createElementLevelCommentParts(outerElementLeadingComments)
|
|
6596
|
-
: [];
|
|
6597
|
-
const fallbackElementComments = [];
|
|
6598
|
-
const shouldLiftTextLevelComments = outerElementLeadingComments.length === 0;
|
|
6599
|
-
|
|
6600
|
-
const hasChildren = Array.isArray(node.children) && node.children.length > 0;
|
|
6601
|
-
const hasInnerComments = Array.isArray(node.innerComments) && node.innerComments.length > 0;
|
|
6602
|
-
const isSelfClosing = !!node.selfClosing;
|
|
6603
|
-
const hasAttributes = Array.isArray(node.attributes) && node.attributes.length > 0;
|
|
6604
|
-
|
|
6605
|
-
// Collect comments that the parser attached to children but actually belong inside
|
|
6606
|
-
// the opening tag (positionally before openingElement.end). These should be printed
|
|
6607
|
-
// as leading comments before the appropriate attribute, not lifted to element-level.
|
|
6608
|
-
/** @type {Set<AST.Comment>} */
|
|
6609
|
-
const openingTagCommentsSet = new Set();
|
|
6610
|
-
if (hasChildren && node.openingElement) {
|
|
6611
|
-
const openingEnd = /** @type {AST.NodeWithLocation} */ (node.openingElement).end;
|
|
6612
|
-
for (const child of node.children) {
|
|
6613
|
-
if (
|
|
6614
|
-
(child.type === 'JSXExpressionContainer' || child.type === 'JSXText') &&
|
|
6615
|
-
Array.isArray(child.leadingComments)
|
|
6616
|
-
) {
|
|
6617
|
-
for (const comment of child.leadingComments) {
|
|
6618
|
-
if (
|
|
6619
|
-
typeof comment.start === 'number' &&
|
|
6620
|
-
comment.start >= node.start &&
|
|
6621
|
-
comment.start < openingEnd
|
|
6622
|
-
) {
|
|
6623
|
-
openingTagCommentsSet.add(comment);
|
|
6624
|
-
}
|
|
6625
|
-
}
|
|
6626
|
-
}
|
|
6627
|
-
}
|
|
6628
|
-
}
|
|
6629
|
-
|
|
6630
|
-
// Build a map from attribute index to comments that should precede that attribute
|
|
6631
|
-
const openingTagCommentsByAttrIndex = new Map();
|
|
6632
|
-
if (openingTagCommentsSet.size > 0 && hasAttributes) {
|
|
6633
|
-
const sortedOTC = [...openingTagCommentsSet].sort(
|
|
6634
|
-
(a, b) => /** @type {number} */ (a.start) - /** @type {number} */ (b.start),
|
|
6635
|
-
);
|
|
6636
|
-
let commentIdx = 0;
|
|
6637
|
-
for (let attrIdx = 0; attrIdx < node.attributes.length; attrIdx++) {
|
|
6638
|
-
const attr = node.attributes[attrIdx];
|
|
6639
|
-
const commentsForAttr = [];
|
|
6640
|
-
while (
|
|
6641
|
-
commentIdx < sortedOTC.length &&
|
|
6642
|
-
/** @type {number} */ (sortedOTC[commentIdx].start) <
|
|
6643
|
-
/** @type {AST.NodeWithLocation} */ (attr).start
|
|
6644
|
-
) {
|
|
6645
|
-
commentsForAttr.push(sortedOTC[commentIdx]);
|
|
6646
|
-
commentIdx++;
|
|
6647
|
-
}
|
|
6648
|
-
if (commentsForAttr.length > 0) {
|
|
6649
|
-
openingTagCommentsByAttrIndex.set(attrIdx, commentsForAttr);
|
|
6650
|
-
}
|
|
6651
|
-
}
|
|
6652
|
-
}
|
|
6653
|
-
|
|
6654
|
-
if (isSelfClosing && !hasInnerComments && !hasAttributes) {
|
|
6655
|
-
const elementDoc = group(['<', tagName, typeArgsDoc, ' />']);
|
|
6656
|
-
return metadataCommentParts.length > 0 ? [...metadataCommentParts, elementDoc] : elementDoc;
|
|
6657
|
-
}
|
|
6658
|
-
|
|
6659
|
-
// Determine the line break type for attributes
|
|
6660
|
-
// When singleAttributePerLine is true, force each attribute on its own line with hardline
|
|
6661
|
-
// Otherwise, use line to allow collapsing when it fits
|
|
6662
|
-
const attrLineBreak = options.singleAttributePerLine ? hardline : line;
|
|
6663
|
-
|
|
6664
|
-
const shouldUseSelfClosingSyntax = isSelfClosing || (!hasChildren && !hasInnerComments);
|
|
6665
|
-
|
|
6666
|
-
const hasOpeningTagComments = openingTagCommentsSet.size > 0;
|
|
6667
|
-
let attrIndex = 0;
|
|
6668
|
-
let hasBreakingAttribute = false;
|
|
6669
|
-
const attrDocs = hasAttributes
|
|
6670
|
-
? path.map((attrPath) => {
|
|
6671
|
-
const idx = attrIndex++;
|
|
6672
|
-
const commentsForAttr = openingTagCommentsByAttrIndex.get(idx);
|
|
6673
|
-
/** @type {Doc[]} */
|
|
6674
|
-
const parts = [];
|
|
6675
|
-
if (commentsForAttr) {
|
|
6676
|
-
for (const comment of commentsForAttr) {
|
|
6677
|
-
// Line comments (//) consume the rest of the line, so they must
|
|
6678
|
-
// use hardline to force a break. Block comments can use normal breaks.
|
|
6679
|
-
if (comment.type === 'Line') {
|
|
6680
|
-
parts.push(hardline);
|
|
6681
|
-
parts.push('//' + comment.value);
|
|
6682
|
-
} else if (comment.type === 'Block') {
|
|
6683
|
-
parts.push(attrLineBreak);
|
|
6684
|
-
parts.push('/*' + comment.value + '*/');
|
|
6685
|
-
}
|
|
6686
|
-
}
|
|
6687
|
-
}
|
|
6688
|
-
parts.push(attrLineBreak);
|
|
6689
|
-
const attrDoc = print(attrPath);
|
|
6690
|
-
parts.push(attrDoc);
|
|
6691
|
-
const attr_node = /** @type {ESTreeJSX.JSXAttribute | ESTreeJSX.JSXSpreadAttribute} */ (
|
|
6692
|
-
/** @type {unknown} */ (attrPath.node)
|
|
6693
|
-
);
|
|
6694
|
-
if (
|
|
6695
|
-
!hasBreakingAttribute &&
|
|
6696
|
-
(willBreak(attrDoc) ||
|
|
6697
|
-
(attr_node.type === 'JSXAttribute' &&
|
|
6698
|
-
is_attribute_value_breakable(/** @type {any} */ (attr_node.value))))
|
|
6699
|
-
) {
|
|
6700
|
-
hasBreakingAttribute = true;
|
|
6701
|
-
}
|
|
6702
|
-
return parts;
|
|
6703
|
-
}, 'attributes')
|
|
6704
|
-
: [];
|
|
6705
|
-
const shouldForceBreak = hasOpeningTagComments || hasBreakingAttribute;
|
|
6706
|
-
const openingTagAlwaysBreaks =
|
|
6707
|
-
(hasAttributes && options.singleAttributePerLine) || shouldForceBreak;
|
|
6708
|
-
const openingTag = group([
|
|
6709
|
-
'<',
|
|
6710
|
-
tagName,
|
|
6711
|
-
typeArgsDoc,
|
|
6712
|
-
hasAttributes
|
|
6713
|
-
? indent([
|
|
6714
|
-
...attrDocs,
|
|
6715
|
-
// Force the group to break when there are line comments in the opening tag,
|
|
6716
|
-
// or when any attribute value would break (e.g. multiline objects, ternaries).
|
|
6717
|
-
// This ensures attributes are broken onto separate lines rather than breaking
|
|
6718
|
-
// expression values inline on the same line as the tag name.
|
|
6719
|
-
...(shouldForceBreak ? [breakParent] : []),
|
|
6720
|
-
])
|
|
6721
|
-
: '',
|
|
6722
|
-
// Add line break opportunity before > or />
|
|
6723
|
-
// Use line for self-closing (keeps space), softline for non-self-closing when attributes present
|
|
6724
|
-
// When bracketSameLine is true, don't add line break for non-self-closing elements
|
|
6725
|
-
shouldUseSelfClosingSyntax
|
|
6726
|
-
? hasAttributes
|
|
6727
|
-
? line
|
|
6728
|
-
: ''
|
|
6729
|
-
: hasAttributes && !options.bracketSameLine
|
|
6730
|
-
? softline
|
|
6731
|
-
: '',
|
|
6732
|
-
shouldUseSelfClosingSyntax ? (hasAttributes ? '/>' : ' />') : '>',
|
|
6733
|
-
]);
|
|
6734
|
-
|
|
6735
|
-
if (!hasChildren) {
|
|
6736
|
-
if (!hasInnerComments) {
|
|
6737
|
-
return metadataCommentParts.length > 0 ? [...metadataCommentParts, openingTag] : openingTag;
|
|
6738
|
-
}
|
|
6739
|
-
|
|
6740
|
-
/** @type {Doc[]} */
|
|
6741
|
-
const innerParts = [];
|
|
6742
|
-
for (const comment of node.innerComments ?? []) {
|
|
6743
|
-
if (comment.type === 'Line') {
|
|
6744
|
-
innerParts.push('//' + comment.value);
|
|
6745
|
-
innerParts.push(hardline);
|
|
6746
|
-
} else if (comment.type === 'Block') {
|
|
6747
|
-
innerParts.push('/*' + comment.value + '*/');
|
|
6748
|
-
innerParts.push(hardline);
|
|
6749
|
-
}
|
|
6750
|
-
}
|
|
6751
|
-
|
|
6752
|
-
if (innerParts.length > 0 && innerParts[innerParts.length - 1] === hardline) {
|
|
6753
|
-
innerParts.pop();
|
|
6754
|
-
}
|
|
6755
|
-
|
|
6756
|
-
const closingTag = ['</', tagName, '>'];
|
|
6757
|
-
const elementOutput = group([
|
|
6758
|
-
openingTag,
|
|
6759
|
-
indent([hardline, ...innerParts]),
|
|
6760
|
-
hardline,
|
|
6761
|
-
closingTag,
|
|
6762
|
-
]);
|
|
6763
|
-
return metadataCommentParts.length > 0
|
|
6764
|
-
? [...metadataCommentParts, elementOutput]
|
|
6765
|
-
: elementOutput;
|
|
6766
|
-
}
|
|
6767
|
-
|
|
6768
|
-
// Has children - use unified children processing
|
|
6769
|
-
// Build children with whitespace preservation
|
|
6770
|
-
/** @type {Doc[]} */
|
|
6771
|
-
const finalChildren = [];
|
|
6772
|
-
const sortedInnerElementBodyComments =
|
|
6773
|
-
innerElementBodyComments.length > 0
|
|
6774
|
-
? innerElementBodyComments.slice().sort((a, b) => (a.start ?? 0) - (b.start ?? 0))
|
|
6775
|
-
: [];
|
|
6776
|
-
let innerElementBodyCommentIndex = 0;
|
|
6777
|
-
|
|
6778
|
-
for (let i = 0; i < node.children.length; i++) {
|
|
6779
|
-
const currentChild = node.children[i];
|
|
6780
|
-
const nextChild = node.children[i + 1];
|
|
6781
|
-
|
|
6782
|
-
// Insert any element-body comments that appear before this child.
|
|
6783
|
-
if (innerElementBodyCommentIndex < sortedInnerElementBodyComments.length) {
|
|
6784
|
-
const currentChildStart = typeof currentChild.start === 'number' ? currentChild.start : null;
|
|
6785
|
-
if (currentChildStart != null) {
|
|
6786
|
-
const commentsBefore = [];
|
|
6787
|
-
while (innerElementBodyCommentIndex < sortedInnerElementBodyComments.length) {
|
|
6788
|
-
/** @type {AST.Comment} */
|
|
6789
|
-
const comment = sortedInnerElementBodyComments[innerElementBodyCommentIndex];
|
|
6790
|
-
if (typeof comment.start !== 'number' || comment.start >= currentChildStart) {
|
|
6791
|
-
break;
|
|
6792
|
-
}
|
|
6793
|
-
commentsBefore.push(comment);
|
|
6794
|
-
innerElementBodyCommentIndex++;
|
|
6795
|
-
}
|
|
6796
|
-
if (commentsBefore.length > 0) {
|
|
6797
|
-
if (finalChildren.length > 0) {
|
|
6798
|
-
finalChildren.push(hardline);
|
|
6799
|
-
}
|
|
6800
|
-
finalChildren.push(...createElementLevelCommentPartsTrimmed(commentsBefore));
|
|
6801
|
-
finalChildren.push(hardline);
|
|
6802
|
-
}
|
|
6803
|
-
}
|
|
6804
|
-
}
|
|
6805
|
-
|
|
6806
|
-
const isTextLikeChild =
|
|
6807
|
-
currentChild.type === 'JSXExpressionContainer' || currentChild.type === 'JSXText';
|
|
6808
|
-
const hasTextLeadingComments =
|
|
6809
|
-
shouldLiftTextLevelComments &&
|
|
6810
|
-
isTextLikeChild &&
|
|
6811
|
-
Array.isArray(currentChild.leadingComments) &&
|
|
6812
|
-
currentChild.leadingComments.length > 0;
|
|
6813
|
-
const currentChildAny = /** @type {any} */ (currentChild);
|
|
6814
|
-
const rawExpressionLeadingComments =
|
|
6815
|
-
isTextLikeChild && Array.isArray(currentChildAny.expression?.leadingComments)
|
|
6816
|
-
? currentChildAny.expression.leadingComments
|
|
6817
|
-
: null;
|
|
6818
|
-
const elementBodyLeadingComments =
|
|
6819
|
-
hasTextLeadingComments && node.openingElement
|
|
6820
|
-
? /** @type {AST.Comment[]} */ (currentChild.leadingComments).filter(
|
|
6821
|
-
(comment) =>
|
|
6822
|
-
comment.context?.containerId === node.metadata?.commentContainerId &&
|
|
6823
|
-
comment.context?.beforeMeaningfulChild &&
|
|
6824
|
-
typeof comment.start === 'number' &&
|
|
6825
|
-
comment.start >= /** @type {AST.NodeWithLocation} */ (node.openingElement).end &&
|
|
6826
|
-
comment.start < /** @type {AST.NodeWithLocation} */ (currentChild).start,
|
|
6827
|
-
)
|
|
6828
|
-
: [];
|
|
6829
|
-
|
|
6830
|
-
if (hasTextLeadingComments) {
|
|
6831
|
-
for (let j = 0; j < /** @type {AST.Comment[]} */ (currentChild.leadingComments).length; j++) {
|
|
6832
|
-
const comment = /** @type {AST.Comment[]} */ (currentChild.leadingComments)[j];
|
|
6833
|
-
// Don't lift comments that belong inside the opening tag (handled in attribute section)
|
|
6834
|
-
if (!openingTagCommentsSet.has(comment) && !elementBodyLeadingComments.includes(comment)) {
|
|
6835
|
-
fallbackElementComments.push(comment);
|
|
6836
|
-
}
|
|
6837
|
-
}
|
|
6838
|
-
}
|
|
6839
|
-
|
|
6840
|
-
const childPrintArgs = /** @type {PrintArgs} */ ({});
|
|
6841
|
-
if (hasTextLeadingComments) {
|
|
6842
|
-
childPrintArgs.suppressLeadingComments = true;
|
|
6843
|
-
}
|
|
6844
|
-
if (rawExpressionLeadingComments && rawExpressionLeadingComments.length > 0) {
|
|
6845
|
-
childPrintArgs.suppressExpressionLeadingComments = true;
|
|
6846
|
-
}
|
|
6847
|
-
|
|
6848
|
-
const printedChild =
|
|
6849
|
-
Object.keys(childPrintArgs).length > 0
|
|
6850
|
-
? path.call((childPath) => print(childPath, childPrintArgs), 'children', i)
|
|
6851
|
-
: path.call(print, 'children', i);
|
|
6852
|
-
|
|
6853
|
-
const childLeadingCommentParts =
|
|
6854
|
-
elementBodyLeadingComments.length > 0
|
|
6855
|
-
? createElementLevelCommentParts(elementBodyLeadingComments)
|
|
6856
|
-
: rawExpressionLeadingComments && rawExpressionLeadingComments.length > 0
|
|
6857
|
-
? createElementLevelCommentParts(rawExpressionLeadingComments)
|
|
6858
|
-
: null;
|
|
6859
|
-
const childDoc = childLeadingCommentParts
|
|
6860
|
-
? [...childLeadingCommentParts, printedChild]
|
|
6861
|
-
: printedChild;
|
|
6862
|
-
finalChildren.push(childDoc);
|
|
6863
|
-
|
|
6864
|
-
// Insert element-body comments that fall between this child and the next child (or the closing tag).
|
|
6865
|
-
let insertedBodyCommentsBetween = false;
|
|
6866
|
-
if (innerElementBodyCommentIndex < sortedInnerElementBodyComments.length) {
|
|
6867
|
-
const currentChildEnd = /** @type {AST.NodeWithLocation} */ (currentChild).end;
|
|
6868
|
-
const nextChildStart =
|
|
6869
|
-
nextChild && typeof nextChild.start === 'number' ? nextChild.start : null;
|
|
6870
|
-
const commentsBetween = [];
|
|
6871
|
-
while (innerElementBodyCommentIndex < sortedInnerElementBodyComments.length) {
|
|
6872
|
-
/** @type {AST.Comment} */
|
|
6873
|
-
const comment = sortedInnerElementBodyComments[innerElementBodyCommentIndex];
|
|
6874
|
-
if (typeof comment.start !== 'number') {
|
|
6875
|
-
innerElementBodyCommentIndex++;
|
|
6876
|
-
continue;
|
|
6877
|
-
}
|
|
6878
|
-
if (comment.start < currentChildEnd) {
|
|
6879
|
-
break;
|
|
6880
|
-
}
|
|
6881
|
-
if (nextChildStart != null && comment.start >= nextChildStart) {
|
|
6882
|
-
break;
|
|
6883
|
-
}
|
|
6884
|
-
commentsBetween.push(comment);
|
|
6885
|
-
innerElementBodyCommentIndex++;
|
|
6886
|
-
}
|
|
6887
|
-
if (commentsBetween.length > 0) {
|
|
6888
|
-
const firstComment = commentsBetween[0];
|
|
6889
|
-
const lastComment = commentsBetween[commentsBetween.length - 1];
|
|
6890
|
-
|
|
6891
|
-
// Preserve any blank line(s) that existed between the previous child and the comment block.
|
|
6892
|
-
const blankLinesBefore = getBlankLinesBetweenNodes(currentChild, firstComment);
|
|
6893
|
-
finalChildren.push(hardline);
|
|
6894
|
-
if (blankLinesBefore > 0) {
|
|
6895
|
-
finalChildren.push(hardline);
|
|
6896
|
-
}
|
|
6897
|
-
|
|
6898
|
-
finalChildren.push(...createElementLevelCommentPartsTrimmed(commentsBetween));
|
|
6899
|
-
|
|
6900
|
-
if (nextChild) {
|
|
6901
|
-
// Preserve any blank line(s) that existed between the comment block and the next child.
|
|
6902
|
-
const blankLinesAfter = getBlankLinesBetweenNodes(lastComment, nextChild);
|
|
6903
|
-
finalChildren.push(hardline);
|
|
6904
|
-
if (blankLinesAfter > 0) {
|
|
6905
|
-
finalChildren.push(hardline);
|
|
6906
|
-
}
|
|
6907
|
-
}
|
|
6908
|
-
insertedBodyCommentsBetween = true;
|
|
6909
|
-
}
|
|
6910
|
-
}
|
|
6911
|
-
|
|
6912
|
-
if (nextChild) {
|
|
6913
|
-
if (insertedBodyCommentsBetween) {
|
|
6914
|
-
continue;
|
|
6915
|
-
}
|
|
6916
|
-
const whitespaceTarget =
|
|
6917
|
-
nextChild.leadingComments && nextChild.leadingComments.length > 0
|
|
6918
|
-
? nextChild.leadingComments[0]
|
|
6919
|
-
: nextChild;
|
|
6920
|
-
const whitespaceLinesCount = getBlankLinesBetweenNodes(currentChild, whitespaceTarget);
|
|
6921
|
-
const isTextOrExpressionChild =
|
|
6922
|
-
currentChild.type === 'JSXExpressionContainer' ||
|
|
6923
|
-
currentChild.type === 'JSXText' ||
|
|
6924
|
-
nextChild.type === 'JSXExpressionContainer' ||
|
|
6925
|
-
nextChild.type === 'JSXText';
|
|
6926
|
-
|
|
6927
|
-
if (whitespaceLinesCount > 0) {
|
|
6928
|
-
finalChildren.push(hardline);
|
|
6929
|
-
finalChildren.push(hardline);
|
|
6930
|
-
} else if (!isTextOrExpressionChild && shouldAddBlankLine(currentChild, nextChild)) {
|
|
6931
|
-
finalChildren.push(hardline);
|
|
6932
|
-
finalChildren.push(hardline);
|
|
6933
|
-
} else {
|
|
6934
|
-
finalChildren.push(hardline);
|
|
6935
|
-
}
|
|
6936
|
-
}
|
|
6937
|
-
}
|
|
6938
|
-
|
|
6939
|
-
// Collect comments attached to the closing element (comments that appear after the last child
|
|
6940
|
-
// but before the closing tag, e.g. `</div>`). The parser attaches these as leadingComments
|
|
6941
|
-
// on either the closingElement node or the closingElement.name node depending on context.
|
|
6942
|
-
const closingElementComments = [
|
|
6943
|
-
...(node.closingElement && Array.isArray(node.closingElement.leadingComments)
|
|
6944
|
-
? node.closingElement.leadingComments
|
|
6945
|
-
: []),
|
|
6946
|
-
...(node.closingElement &&
|
|
6947
|
-
node.closingElement.name &&
|
|
6948
|
-
Array.isArray(node.closingElement.name.leadingComments)
|
|
6949
|
-
? node.closingElement.name.leadingComments
|
|
6950
|
-
: []),
|
|
6951
|
-
];
|
|
6952
|
-
|
|
6953
|
-
if (closingElementComments.length > 0) {
|
|
6954
|
-
const lastChild = node.children[node.children.length - 1];
|
|
6955
|
-
if (lastChild) {
|
|
6956
|
-
const blankLinesBefore = getBlankLinesBetweenNodes(lastChild, closingElementComments[0]);
|
|
6957
|
-
finalChildren.push(hardline);
|
|
6958
|
-
if (blankLinesBefore > 0) {
|
|
6959
|
-
finalChildren.push(hardline);
|
|
6960
|
-
}
|
|
6961
|
-
}
|
|
6962
|
-
finalChildren.push(...createElementLevelCommentPartsTrimmed(closingElementComments));
|
|
6963
|
-
}
|
|
6964
|
-
|
|
6965
|
-
const fallbackCommentParts =
|
|
6966
|
-
fallbackElementComments.length > 0
|
|
6967
|
-
? createElementLevelCommentParts(fallbackElementComments)
|
|
6968
|
-
: [];
|
|
6969
|
-
const leadingCommentParts =
|
|
6970
|
-
metadataCommentParts.length > 0
|
|
6971
|
-
? [...metadataCommentParts, ...fallbackCommentParts]
|
|
6972
|
-
: fallbackCommentParts;
|
|
6973
|
-
|
|
6974
|
-
const closingTag = ['</', tagName, '>'];
|
|
6975
|
-
let elementOutput;
|
|
6976
|
-
const shouldTryInlineMultipleChildren =
|
|
6977
|
-
!openingTagAlwaysBreaks &&
|
|
6978
|
-
fallbackCommentParts.length === 0 &&
|
|
6979
|
-
closingElementComments.length === 0 &&
|
|
6980
|
-
shouldTryInlineMultipleTextChildren(node);
|
|
6981
|
-
|
|
6982
|
-
if (finalChildren.length === 1) {
|
|
6983
|
-
const child = finalChildren[0];
|
|
6984
|
-
const firstChild = /** @type {any} */ (node.children[0]);
|
|
6985
|
-
const isNonSelfClosingElement =
|
|
6986
|
-
firstChild && firstChild.type === 'Element' && !firstChild.selfClosing;
|
|
6987
|
-
const isElementChild = firstChild && firstChild.type === 'Element';
|
|
6988
|
-
const isRawTextChild =
|
|
6989
|
-
firstChild && firstChild.type === 'Text' && typeof firstChild.raw === 'string';
|
|
6990
|
-
|
|
6991
|
-
if (
|
|
6992
|
-
(typeof child === 'string' || isRawTextChild) &&
|
|
6993
|
-
shouldInlineSingleChild(node, firstChild, child)
|
|
6994
|
-
) {
|
|
6995
|
-
elementOutput = openingTagAlwaysBreaks
|
|
6996
|
-
? [openingTag, indent([hardline, child]), hardline, closingTag]
|
|
6997
|
-
: conditionalGroup([
|
|
6998
|
-
group([openingTag, child, closingTag]),
|
|
6999
|
-
[openingTag, indent([hardline, child]), hardline, closingTag],
|
|
7000
|
-
]);
|
|
7001
|
-
} else if (
|
|
7002
|
-
child &&
|
|
7003
|
-
typeof child === 'object' &&
|
|
7004
|
-
!isNonSelfClosingElement &&
|
|
7005
|
-
shouldInlineSingleChild(node, firstChild, child)
|
|
7006
|
-
) {
|
|
7007
|
-
if (isElementChild && hasAttributes) {
|
|
7008
|
-
elementOutput = [openingTag, indent([hardline, child]), hardline, closingTag];
|
|
7009
|
-
} else {
|
|
7010
|
-
elementOutput = group([openingTag, indent([softline, child]), softline, closingTag]);
|
|
7011
|
-
}
|
|
7012
|
-
} else {
|
|
7013
|
-
elementOutput = [openingTag, indent([hardline, ...finalChildren]), hardline, closingTag];
|
|
7014
|
-
}
|
|
7015
|
-
} else if (shouldTryInlineMultipleChildren) {
|
|
7016
|
-
const inlineChildren = path.map(print, 'children');
|
|
7017
|
-
elementOutput = conditionalGroup([
|
|
7018
|
-
group([openingTag, ...inlineChildren, closingTag]),
|
|
7019
|
-
[openingTag, indent([hardline, ...finalChildren]), hardline, closingTag],
|
|
7020
|
-
]);
|
|
7021
|
-
} else {
|
|
7022
|
-
elementOutput = group([openingTag, indent([hardline, ...finalChildren]), hardline, closingTag]);
|
|
7023
|
-
}
|
|
7024
|
-
|
|
7025
|
-
return leadingCommentParts.length > 0 ? [...leadingCommentParts, elementOutput] : elementOutput;
|
|
7026
|
-
}
|
package/src/index.test.js
CHANGED
|
@@ -2753,6 +2753,104 @@ function test() {
|
|
|
2753
2753
|
expect(result).toBeWithNewline(expected);
|
|
2754
2754
|
});
|
|
2755
2755
|
|
|
2756
|
+
it('should preserve template comments and blank lines from unformatted input', async () => {
|
|
2757
|
+
const input = `function TodoList() @{
|
|
2758
|
+
<>
|
|
2759
|
+
/* world 0 */
|
|
2760
|
+
// hello
|
|
2761
|
+
/* world 1 */
|
|
2762
|
+
<ul>
|
|
2763
|
+
// hello
|
|
2764
|
+
/* world 2 */
|
|
2765
|
+
|
|
2766
|
+
</ul>
|
|
2767
|
+
|
|
2768
|
+
<ul>
|
|
2769
|
+
// hello
|
|
2770
|
+
/* world 3 */
|
|
2771
|
+
// hello
|
|
2772
|
+
</ul>
|
|
2773
|
+
/* world 4 */
|
|
2774
|
+
</>
|
|
2775
|
+
}`;
|
|
2776
|
+
|
|
2777
|
+
const expected = `function TodoList() @{
|
|
2778
|
+
<>
|
|
2779
|
+
/* world 0 */
|
|
2780
|
+
// hello
|
|
2781
|
+
/* world 1 */
|
|
2782
|
+
<ul>
|
|
2783
|
+
// hello
|
|
2784
|
+
/* world 2 */
|
|
2785
|
+
</ul>
|
|
2786
|
+
|
|
2787
|
+
<ul>
|
|
2788
|
+
// hello
|
|
2789
|
+
/* world 3 */
|
|
2790
|
+
// hello
|
|
2791
|
+
</ul>
|
|
2792
|
+
/* world 4 */
|
|
2793
|
+
</>
|
|
2794
|
+
}`;
|
|
2795
|
+
|
|
2796
|
+
const result = await format(input, { singleQuote: true });
|
|
2797
|
+
expect(result).toBeWithNewline(expected);
|
|
2798
|
+
// Reformatting the output must be stable.
|
|
2799
|
+
expect(await format(result, { singleQuote: true })).toBeWithNewline(expected);
|
|
2800
|
+
});
|
|
2801
|
+
|
|
2802
|
+
it('should preserve block comments before a closing fragment', async () => {
|
|
2803
|
+
const expected = `function App() @{
|
|
2804
|
+
<>
|
|
2805
|
+
<span>{'child'}</span>
|
|
2806
|
+
|
|
2807
|
+
/* block comment */
|
|
2808
|
+
</>
|
|
2809
|
+
}`;
|
|
2810
|
+
|
|
2811
|
+
const result = await format(expected, { singleQuote: true });
|
|
2812
|
+
expect(result).toBeWithNewline(expected);
|
|
2813
|
+
});
|
|
2814
|
+
|
|
2815
|
+
it('should preserve a blank line before a trailing block comment in elements', async () => {
|
|
2816
|
+
const expected = `function App() {
|
|
2817
|
+
<div>
|
|
2818
|
+
<span>{'child'}</span>
|
|
2819
|
+
|
|
2820
|
+
/* block comment */
|
|
2821
|
+
</div>
|
|
2822
|
+
}`;
|
|
2823
|
+
|
|
2824
|
+
const result = await format(expected, { singleQuote: true });
|
|
2825
|
+
expect(result).toBeWithNewline(expected);
|
|
2826
|
+
});
|
|
2827
|
+
|
|
2828
|
+
it('should preserve a comment-only fragment body', async () => {
|
|
2829
|
+
const expected = `function App() @{
|
|
2830
|
+
<>
|
|
2831
|
+
/* only */
|
|
2832
|
+
</>
|
|
2833
|
+
}`;
|
|
2834
|
+
|
|
2835
|
+
const result = await format(expected, { singleQuote: true });
|
|
2836
|
+
expect(result).toBeWithNewline(expected);
|
|
2837
|
+
});
|
|
2838
|
+
|
|
2839
|
+
it('should keep blank lines around comments between template siblings', async () => {
|
|
2840
|
+
const expected = `function App() @{
|
|
2841
|
+
<>
|
|
2842
|
+
<ul></ul>
|
|
2843
|
+
|
|
2844
|
+
/* between */
|
|
2845
|
+
|
|
2846
|
+
<ul></ul>
|
|
2847
|
+
</>
|
|
2848
|
+
}`;
|
|
2849
|
+
|
|
2850
|
+
const result = await format(expected, { singleQuote: true });
|
|
2851
|
+
expect(result).toBeWithNewline(expected);
|
|
2852
|
+
});
|
|
2853
|
+
|
|
2756
2854
|
it('should preserve trailing comments in function parameters', async () => {
|
|
2757
2855
|
const expected = `function test(
|
|
2758
2856
|
// comment in params
|