@tsrx/prettier-plugin 0.3.64 → 0.3.65

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.64",
3
+ "version": "0.3.65",
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.12"
30
+ "@tsrx/core": "0.1.13"
31
31
  },
32
32
  "files": [
33
33
  "src/"
package/src/index.js CHANGED
@@ -1506,6 +1506,15 @@ function printRippleNode(node, path, options, print, args) {
1506
1506
  break;
1507
1507
  }
1508
1508
 
1509
+ case 'TSSatisfiesExpression': {
1510
+ nodeContent = [
1511
+ path.call(print, 'expression'),
1512
+ ' satisfies ',
1513
+ path.call(print, 'typeAnnotation'),
1514
+ ];
1515
+ break;
1516
+ }
1517
+
1509
1518
  case 'TSNonNullExpression': {
1510
1519
  nodeContent = [path.call(print, 'expression'), '!'];
1511
1520
  break;
@@ -1930,21 +1939,21 @@ function printRippleNode(node, path, options, print, args) {
1930
1939
  (node.left.type === 'CallExpression' ||
1931
1940
  node.left.type === 'ChainExpression' ||
1932
1941
  node.left.type === 'NewExpression');
1933
- // Don't add indent if we're in a conditional test context
1934
- if (args?.isConditionalTest) {
1942
+ if (shouldKeepNullishFallbackInline) {
1935
1943
  logicalResult = group([
1936
- path.call((childPath) => print(childPath, { isConditionalTest: true }), 'left'),
1944
+ path.call(print, 'left'),
1937
1945
  ' ',
1938
1946
  node.operator,
1939
- [line, path.call((childPath) => print(childPath, { isConditionalTest: true }), 'right')],
1947
+ ' ',
1948
+ path.call(print, 'right'),
1940
1949
  ]);
1941
- } else if (shouldKeepNullishFallbackInline) {
1950
+ } else if (args?.isConditionalTest) {
1951
+ // Don't add indent if we're in a conditional test context
1942
1952
  logicalResult = group([
1943
- path.call(print, 'left'),
1953
+ path.call((childPath) => print(childPath, { isConditionalTest: true }), 'left'),
1944
1954
  ' ',
1945
1955
  node.operator,
1946
- ' ',
1947
- path.call(print, 'right'),
1956
+ [line, path.call((childPath) => print(childPath, { isConditionalTest: true }), 'right')],
1948
1957
  ]);
1949
1958
  } else {
1950
1959
  logicalResult = group([
@@ -2839,20 +2848,15 @@ function printArrowFunction(node, path, options, print, args) {
2839
2848
  // to avoid ambiguity with block statements or to clarify intent
2840
2849
  const bodyDoc = path.call(print, 'body');
2841
2850
  const groupId = Symbol('arrow');
2842
- const shouldBreakBody = shouldBreakArrowExpressionBody(node.body, options);
2851
+ const shouldBreakBody = shouldBreakArrowExpressionBody(node.body, options, args);
2843
2852
  /** @type {Doc | Doc[]} */
2844
2853
  let bodyContent;
2845
2854
  if (
2846
2855
  node.body.type === 'ObjectExpression' ||
2847
2856
  node.body.type === 'AssignmentExpression' ||
2848
- node.body.type === 'SequenceExpression' ||
2849
- (args?.isInAttribute && isTemplateExpression(node.body))
2857
+ node.body.type === 'SequenceExpression'
2850
2858
  ) {
2851
- if (isTemplateExpression(node.body)) {
2852
- bodyContent = ['(', indent([hardline, bodyDoc]), hardline, ')'];
2853
- } else {
2854
- bodyContent = ['(', bodyDoc, ')'];
2855
- }
2859
+ bodyContent = ['(', bodyDoc, ')'];
2856
2860
  } else {
2857
2861
  bodyContent = bodyDoc;
2858
2862
  }
@@ -2885,6 +2889,15 @@ function isTemplateExpression(node) {
2885
2889
  );
2886
2890
  }
2887
2891
 
2892
+ /**
2893
+ * Check whether a braced attribute expression should close on its own line.
2894
+ * @param {AST.Node} node - The expression inside the attribute braces
2895
+ * @returns {boolean}
2896
+ */
2897
+ function shouldBreakAttributeExpressionClosingBrace(node) {
2898
+ return node.type === 'ArrowFunctionExpression' && node.body && isTemplateExpression(node.body);
2899
+ }
2900
+
2888
2901
  /**
2889
2902
  * Print an export default declaration
2890
2903
  * @param {AST.ExportDefaultDeclaration} node - The export default node
@@ -3095,9 +3108,13 @@ function sourceSpanExceedsPrintWidth(node, options) {
3095
3108
  * Check if an arrow expression body should break immediately after `=>`.
3096
3109
  * @param {AST.Expression} node - The arrow body expression
3097
3110
  * @param {RippleFormatOptions} options - Prettier options
3111
+ * @param {PrintArgs} [args] - Additional context arguments
3098
3112
  * @returns {boolean}
3099
3113
  */
3100
- function shouldBreakArrowExpressionBody(node, options) {
3114
+ function shouldBreakArrowExpressionBody(node, options, args) {
3115
+ if (args?.isInAttribute && isTemplateExpression(node)) {
3116
+ return true;
3117
+ }
3101
3118
  return (
3102
3119
  (node.type === 'BinaryExpression' || node.type === 'LogicalExpression') &&
3103
3120
  sourceSpanExceedsPrintWidth(/** @type {AST.NodeWithLocation} */ (node), options)
@@ -5778,7 +5795,7 @@ function printTsx(node, path, options, print) {
5778
5795
  return [tagName, closingTagName];
5779
5796
  }
5780
5797
 
5781
- if (printedChildren.length > 1) {
5798
+ if (!is_fragment || printedChildren.length > 1) {
5782
5799
  return group([
5783
5800
  tagName,
5784
5801
  indent([hardline, join(hardline, printedChildren)]),
@@ -6192,11 +6209,15 @@ function printJSXAttribute(attr, path, options, print) {
6192
6209
  }
6193
6210
 
6194
6211
  if (attr.value.type === 'JSXExpressionContainer') {
6212
+ const expression = attr.value.expression;
6195
6213
  const exprDoc = path.call(
6196
6214
  (valuePath) => print(valuePath, { isInAttribute: true }),
6197
6215
  'value',
6198
6216
  'expression',
6199
6217
  );
6218
+ if (shouldBreakAttributeExpressionClosingBrace(expression)) {
6219
+ return [name, '={', exprDoc, hardline, '}'];
6220
+ }
6200
6221
  return [name, '={', exprDoc, '}'];
6201
6222
  }
6202
6223
 
@@ -6774,6 +6795,9 @@ function printAttribute(node, path, options, print) {
6774
6795
  parts.push('={');
6775
6796
  // Pass inline context for attribute values (keep objects compact)
6776
6797
  parts.push(path.call((attrPath) => print(attrPath, { isInAttribute: true }), 'value'));
6798
+ if (shouldBreakAttributeExpressionClosingBrace(node.value)) {
6799
+ parts.push(hardline);
6800
+ }
6777
6801
  parts.push('}');
6778
6802
  }
6779
6803
  }
package/src/index.test.js CHANGED
@@ -200,6 +200,58 @@ describe('prettier-plugin', () => {
200
200
  expect(result).toBeWithNewline(expected);
201
201
  });
202
202
 
203
+ it('should format explicit tsx arrow returns like tsrx blocks', async () => {
204
+ const input = `component Test(props) {
205
+ const func = (item) => <tsx><ItemView item={item} onSelect={props.onSelect} /></tsx>;
206
+
207
+ <List
208
+ items={props.items}
209
+ renderItem={(item) => <tsx><ItemView item={item} onSelect={props.onSelect} /></tsx>}
210
+ />
211
+ }`;
212
+ const expected = `component Test(props) {
213
+ const func = (item) => <tsx>
214
+ <ItemView item={item} onSelect={props.onSelect} />
215
+ </tsx>;
216
+
217
+ <List
218
+ items={props.items}
219
+ renderItem={(item) =>
220
+ <tsx>
221
+ <ItemView item={item} onSelect={props.onSelect} />
222
+ </tsx>
223
+ }
224
+ />
225
+ }`;
226
+ const result = await format(input);
227
+ expect(result).toBeWithNewline(expected);
228
+ });
229
+
230
+ it('should format template arrow returns in tsx attributes like ripple attributes', async () => {
231
+ const input = `component Test(props) {
232
+ const view = <tsx>
233
+ <List
234
+ items={props.items}
235
+ renderItem={(item) => <tsx><ItemView item={item} onSelect={props.onSelect} /></tsx>}
236
+ />
237
+ </tsx>;
238
+ }`;
239
+ const expected = `component Test(props) {
240
+ const view = <tsx>
241
+ <List
242
+ items={props.items}
243
+ renderItem={(item) =>
244
+ <tsx>
245
+ <ItemView item={item} onSelect={props.onSelect} />
246
+ </tsx>
247
+ }
248
+ />
249
+ </tsx>;
250
+ }`;
251
+ const result = await format(input);
252
+ expect(result).toBeWithNewline(expected);
253
+ });
254
+
203
255
  it('should preserve comments before expressions after nested tsx and tsrx blocks', async () => {
204
256
  const input = `component App() {
205
257
  const content = <tsx>
@@ -255,7 +307,7 @@ describe('prettier-plugin', () => {
255
307
  }`,
256
308
  expected: `component Test() {
257
309
  <A
258
- fallback={(error) => (
310
+ fallback={(error) =>
259
311
  <>
260
312
  <B
261
313
  id="xyz"
@@ -265,7 +317,7 @@ describe('prettier-plugin', () => {
265
317
  otherProp={2}
266
318
  />
267
319
  </>
268
- )}
320
+ }
269
321
  />
270
322
  }`,
271
323
  },
@@ -279,7 +331,7 @@ describe('prettier-plugin', () => {
279
331
  }`,
280
332
  expected: `component Test() {
281
333
  <A
282
- fallback={(error) => (
334
+ fallback={(error) =>
283
335
  <tsx>
284
336
  <B
285
337
  id="xyz"
@@ -289,7 +341,7 @@ describe('prettier-plugin', () => {
289
341
  otherProp={2}
290
342
  />
291
343
  </tsx>
292
- )}
344
+ }
293
345
  />
294
346
  }`,
295
347
  },
@@ -303,7 +355,7 @@ describe('prettier-plugin', () => {
303
355
  }`,
304
356
  expected: `component Test() {
305
357
  <A
306
- fallback={(error) => (
358
+ fallback={(error) =>
307
359
  <tsrx>
308
360
  <B
309
361
  id="xyz"
@@ -313,7 +365,7 @@ describe('prettier-plugin', () => {
313
365
  otherProp={2}
314
366
  />
315
367
  </tsrx>
316
- )}
368
+ }
317
369
  />
318
370
  }`,
319
371
  },
@@ -327,7 +379,7 @@ describe('prettier-plugin', () => {
327
379
  }`,
328
380
  expected: `component Test() {
329
381
  <A
330
- fallback={(error) => (
382
+ fallback={(error) =>
331
383
  <tsx:react>
332
384
  <B
333
385
  id="xyz"
@@ -337,7 +389,7 @@ describe('prettier-plugin', () => {
337
389
  otherProp={2}
338
390
  />
339
391
  </tsx:react>
340
- )}
392
+ }
341
393
  />
342
394
  }`,
343
395
  },
@@ -1015,7 +1067,9 @@ import { Something, type Props, track } from 'ripple';`;
1015
1067
  const foo = <tsx><Bar {...props} /></tsx>;`;
1016
1068
 
1017
1069
  const expected = `const props = {};
1018
- const foo = <tsx><Bar {...props} /></tsx>;`;
1070
+ const foo = <tsx>
1071
+ <Bar {...props} />
1072
+ </tsx>;`;
1019
1073
 
1020
1074
  const result = await format(input, { singleQuote: true });
1021
1075
  expect(result).toBeWithNewline(expected);
@@ -1393,6 +1447,18 @@ import { effect, track } from 'ripple';`;
1393
1447
  expect(result).toBeWithNewline(expected);
1394
1448
  });
1395
1449
 
1450
+ it('keeps nullish fallback inline in a conditional test', async () => {
1451
+ const input = `const test = menuRef.current?.querySelector<HTMLElement>('[role="menuitem"]:not([aria-disabled="true"])') ?? null ? a : b;`;
1452
+ const expected = `const test =
1453
+ menuRef.current?.querySelector<HTMLElement>(
1454
+ '[role="menuitem"]:not([aria-disabled="true"])',
1455
+ ) ?? null
1456
+ ? a
1457
+ : b;`;
1458
+ const result = await format(input, { singleQuote: true });
1459
+ expect(result).toBeWithNewline(expected);
1460
+ });
1461
+
1396
1462
  it('does not add spaces around inlined array elements in destructured arguments', async () => {
1397
1463
  const expected = `for (const [key, value] of Object.entries(attributes).filter(([_key, value]) => value !== '')) {
1398
1464
  }
@@ -2267,7 +2333,9 @@ files = [...(files ?? []), ...dt.files];`;
2267
2333
 
2268
2334
  const expected = `class Foo {
2269
2335
  bar() {
2270
- return <tsx>{'Hello'}</tsx>;
2336
+ return <tsx>
2337
+ {'Hello'}
2338
+ </tsx>;
2271
2339
  }
2272
2340
  }`;
2273
2341
 
@@ -2626,6 +2694,35 @@ component Child({ something }) {
2626
2694
  expect(result).toBeWithNewline(expected);
2627
2695
  });
2628
2696
 
2697
+ it('prints satisfies expressions in switch default cases', async () => {
2698
+ const input = `export component Test(props: { status: "ok" | "error" }) {
2699
+ switch (props.status) {
2700
+ case "ok":
2701
+ <div>"ok"</div>
2702
+ return
2703
+ case "error":
2704
+ <div>"error"</div>
2705
+ return
2706
+ default:
2707
+ props.status satisfies never
2708
+ }
2709
+ }`;
2710
+ const expected = `export component Test(props: { status: "ok" | "error" }) {
2711
+ switch (props.status) {
2712
+ case "ok":
2713
+ <div>"ok"</div>
2714
+ return;
2715
+ case "error":
2716
+ <div>"error"</div>
2717
+ return;
2718
+ default:
2719
+ props.status satisfies never;
2720
+ }
2721
+ }`;
2722
+ const result = await format(input);
2723
+ expect(result).toBeWithNewline(expected);
2724
+ });
2725
+
2629
2726
  it('prints function with a rest parameter correctly', async () => {
2630
2727
  const expected = `function TestRest(...args: string[]) {
2631
2728
  console.log(args);