@tsrx/prettier-plugin 0.3.61 → 0.3.63
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 +73 -2
- package/src/index.test.js +76 -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.63",
|
|
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.12"
|
|
31
31
|
},
|
|
32
32
|
"files": [
|
|
33
33
|
"src/"
|
package/src/index.js
CHANGED
|
@@ -360,6 +360,49 @@ function binaryExpressionNeedsParens(node, parent) {
|
|
|
360
360
|
return false;
|
|
361
361
|
}
|
|
362
362
|
|
|
363
|
+
/**
|
|
364
|
+
* Check if a parenthesized AssignmentExpression needs its parentheses preserved.
|
|
365
|
+
* @param {AST.AssignmentExpression} node - The expression node
|
|
366
|
+
* @param {AST.Node | null} parent - The parent node
|
|
367
|
+
* @returns {boolean} - True if parentheses are needed
|
|
368
|
+
*/
|
|
369
|
+
function assignmentExpressionNeedsParens(node, parent) {
|
|
370
|
+
if (!node.metadata?.parenthesized || !parent) {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (parent.type === 'BinaryExpression' || parent.type === 'LogicalExpression') {
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (parent.type === 'ConditionalExpression') {
|
|
379
|
+
return parent.test === node;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (parent.type === 'AwaitExpression' || parent.type === 'YieldExpression') {
|
|
383
|
+
return parent.argument === node;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (parent.type === 'CallExpression' || parent.type === 'NewExpression') {
|
|
387
|
+
return parent.callee === node;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (parent.type === 'TaggedTemplateExpression') {
|
|
391
|
+
return parent.tag === node;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (
|
|
395
|
+
parent.type === 'TSAsExpression' ||
|
|
396
|
+
parent.type === 'TSSatisfiesExpression' ||
|
|
397
|
+
parent.type === 'TSNonNullExpression' ||
|
|
398
|
+
parent.type === 'TSInstantiationExpression'
|
|
399
|
+
) {
|
|
400
|
+
return parent.expression === node;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
|
|
363
406
|
/**
|
|
364
407
|
* Create a function that skips specified characters in text
|
|
365
408
|
* @param {string | RegExp} characters - Characters to skip
|
|
@@ -718,11 +761,13 @@ function printRippleNode(node, path, options, print, args) {
|
|
|
718
761
|
const isInlineContext = args && args.isInlineContext;
|
|
719
762
|
const suppressLeadingComments = args && args.suppressLeadingComments;
|
|
720
763
|
const suppressExpressionLeadingComments = args && args.suppressExpressionLeadingComments;
|
|
764
|
+
const parentNode = /** @type {AST.Node | null} */ (path.getParentNode());
|
|
721
765
|
|
|
722
766
|
// For TSRXExpression, Text, and Html nodes, don't add leading comments here - they should be handled
|
|
723
|
-
// as separate children within
|
|
767
|
+
// as separate children within elements, not as part of the expression.
|
|
724
768
|
const shouldSkipLeadingComments =
|
|
725
|
-
|
|
769
|
+
parentNode?.type === 'Element' &&
|
|
770
|
+
(node.type === 'TSRXExpression' || node.type === 'Text' || node.type === 'Html');
|
|
726
771
|
|
|
727
772
|
// Handle leading comments
|
|
728
773
|
if (node.leadingComments && !shouldSkipLeadingComments && !suppressLeadingComments) {
|
|
@@ -1335,6 +1380,10 @@ function printRippleNode(node, path, options, print, args) {
|
|
|
1335
1380
|
group(indent(line), { id: groupId }),
|
|
1336
1381
|
indentIfBreak(rightSide, { groupId }),
|
|
1337
1382
|
]);
|
|
1383
|
+
const parent = path.getParentNode();
|
|
1384
|
+
if (assignmentExpressionNeedsParens(node, parent)) {
|
|
1385
|
+
nodeContent = ['(', nodeContent, ')'];
|
|
1386
|
+
}
|
|
1338
1387
|
break;
|
|
1339
1388
|
}
|
|
1340
1389
|
|
|
@@ -4296,6 +4345,10 @@ function printTSTypeAliasDeclaration(node, path, options, print) {
|
|
|
4296
4345
|
head.push(path.call(print, 'typeParameters'));
|
|
4297
4346
|
}
|
|
4298
4347
|
|
|
4348
|
+
if (node.typeAnnotation.type === 'TSTypeLiteral') {
|
|
4349
|
+
return group([head, ' = ', path.call(print, 'typeAnnotation'), semi(options)]);
|
|
4350
|
+
}
|
|
4351
|
+
|
|
4299
4352
|
return group([head, ' =', indent([line, path.call(print, 'typeAnnotation')]), semi(options)]);
|
|
4300
4353
|
}
|
|
4301
4354
|
|
|
@@ -5926,6 +5979,24 @@ function printJSXElement(node, path, options, print) {
|
|
|
5926
5979
|
// Accumulate text content, preserving spaces between words
|
|
5927
5980
|
const trimmed = child.value.trim();
|
|
5928
5981
|
if (trimmed) {
|
|
5982
|
+
const nextChild = node.children[i + 1];
|
|
5983
|
+
const afterNextChild = node.children[i + 2];
|
|
5984
|
+
const nextText = afterNextChild?.type === 'JSXText' ? afterNextChild.value.trim() : '';
|
|
5985
|
+
if (
|
|
5986
|
+
tagName === 'tsrx' &&
|
|
5987
|
+
trimmed.endsWith('=') &&
|
|
5988
|
+
nextChild?.type === 'JSXElement' &&
|
|
5989
|
+
nextText === ';'
|
|
5990
|
+
) {
|
|
5991
|
+
if (currentText) {
|
|
5992
|
+
childrenDocs.push(currentText);
|
|
5993
|
+
currentText = '';
|
|
5994
|
+
}
|
|
5995
|
+
childrenDocs.push([trimmed, ' ', path.call(print, 'children', i + 1), ';']);
|
|
5996
|
+
i += 2;
|
|
5997
|
+
continue;
|
|
5998
|
+
}
|
|
5999
|
+
|
|
5929
6000
|
if (currentText) {
|
|
5930
6001
|
currentText += ' ' + trimmed;
|
|
5931
6002
|
} else {
|
package/src/index.test.js
CHANGED
|
@@ -200,6 +200,49 @@ describe('prettier-plugin', () => {
|
|
|
200
200
|
expect(result).toBeWithNewline(expected);
|
|
201
201
|
});
|
|
202
202
|
|
|
203
|
+
it('should preserve comments before expressions after nested tsx and tsrx blocks', async () => {
|
|
204
|
+
const input = `component App() {
|
|
205
|
+
const content = <tsx>
|
|
206
|
+
{<tsrx>
|
|
207
|
+
const nested =
|
|
208
|
+
<tsx>
|
|
209
|
+
<span class="nested-tsx">
|
|
210
|
+
{'inside nested tsx'}
|
|
211
|
+
</span>
|
|
212
|
+
</tsx>
|
|
213
|
+
;
|
|
214
|
+
<div class="native">{nested}</div>
|
|
215
|
+
</tsrx>}
|
|
216
|
+
</tsx>;
|
|
217
|
+
|
|
218
|
+
// const content = <tsrx>
|
|
219
|
+
// <div>{hey()}</div>
|
|
220
|
+
// </tsrx>;
|
|
221
|
+
|
|
222
|
+
{content}
|
|
223
|
+
}`;
|
|
224
|
+
const expected = `component App() {
|
|
225
|
+
const content = <tsx>
|
|
226
|
+
{<tsrx>
|
|
227
|
+
const nested = <tsx>
|
|
228
|
+
<span class="nested-tsx">
|
|
229
|
+
{'inside nested tsx'}
|
|
230
|
+
</span>
|
|
231
|
+
</tsx>;
|
|
232
|
+
<div class="native">{nested}</div>
|
|
233
|
+
</tsrx>}
|
|
234
|
+
</tsx>;
|
|
235
|
+
|
|
236
|
+
// const content = <tsrx>
|
|
237
|
+
// <div>{hey()}</div>
|
|
238
|
+
// </tsrx>;
|
|
239
|
+
|
|
240
|
+
{content}
|
|
241
|
+
}`;
|
|
242
|
+
const result = await format(input, { singleQuote: true });
|
|
243
|
+
expect(result).toBeWithNewline(expected);
|
|
244
|
+
});
|
|
245
|
+
|
|
203
246
|
it('should break nested TSX element attributes inside expression props', async () => {
|
|
204
247
|
const cases = [
|
|
205
248
|
{
|
|
@@ -762,6 +805,28 @@ function test() {
|
|
|
762
805
|
expect(result).toBeWithNewline(expected);
|
|
763
806
|
});
|
|
764
807
|
|
|
808
|
+
it('should preserve required parentheses around assignment expressions', async () => {
|
|
809
|
+
const input = `const openSignal = useRef<Signal<boolean> | null>(null)
|
|
810
|
+
const open = props.open ?? (openSignal.current ??= signal(false))
|
|
811
|
+
const sum = a + (b = c)
|
|
812
|
+
const condition = (a = b) ? c : d
|
|
813
|
+
const called = (factory = getFactory())()
|
|
814
|
+
async function load() {
|
|
815
|
+
await (promise = getPromise())
|
|
816
|
+
}`;
|
|
817
|
+
const expected = `const openSignal = useRef<Signal<boolean> | null>(null);
|
|
818
|
+
const open = props.open ?? (openSignal.current ??= signal(false));
|
|
819
|
+
const sum = a + (b = c);
|
|
820
|
+
const condition = (a = b) ? c : d;
|
|
821
|
+
const called = (factory = getFactory())();
|
|
822
|
+
async function load() {
|
|
823
|
+
await (promise = getPromise());
|
|
824
|
+
}`;
|
|
825
|
+
|
|
826
|
+
const result = await format(input, { singleQuote: true });
|
|
827
|
+
expect(result).toBeWithNewline(expected);
|
|
828
|
+
});
|
|
829
|
+
|
|
765
830
|
it('should not change formatting for function object properties and properties in square brackets', async () => {
|
|
766
831
|
const expected = `export component App() {
|
|
767
832
|
const SYMBOL_PROP = Symbol();
|
|
@@ -3607,6 +3672,17 @@ second"</pre>
|
|
|
3607
3672
|
expect(result).toBeWithNewline(expected);
|
|
3608
3673
|
});
|
|
3609
3674
|
|
|
3675
|
+
it('should not overindent multiline object type aliases', async () => {
|
|
3676
|
+
const input = `type ModuleShape = {
|
|
3677
|
+
default: ComponentType<{ value: string }>;
|
|
3678
|
+
}`;
|
|
3679
|
+
const expected = `type ModuleShape = {
|
|
3680
|
+
default: ComponentType<{ value: string }>;
|
|
3681
|
+
};`;
|
|
3682
|
+
const result = await format(input);
|
|
3683
|
+
expect(result).toBeWithNewline(expected);
|
|
3684
|
+
});
|
|
3685
|
+
|
|
3610
3686
|
it('should format TypeScript tuple types (TSTupleType)', async () => {
|
|
3611
3687
|
const input = `type T = [string, number, boolean];`;
|
|
3612
3688
|
const expected = `type T = [string, number, boolean];`;
|