@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tsrx/prettier-plugin",
3
- "version": "0.3.61",
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.11"
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 the element, not as part of the expression
767
+ // as separate children within elements, not as part of the expression.
724
768
  const shouldSkipLeadingComments =
725
- node.type === 'TSRXExpression' || node.type === 'Text' || node.type === 'Html';
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];`;