@tsrx/prettier-plugin 0.3.64 → 0.3.66

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.66",
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.14"
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([
@@ -2173,9 +2182,7 @@ function printRippleNode(node, path, options, print, args) {
2173
2182
 
2174
2183
  // Handle return type
2175
2184
  parts.push(' => ');
2176
- if (node.returnType) {
2177
- parts.push(path.call(print, 'returnType'));
2178
- } else if (node.typeAnnotation) {
2185
+ if (node.typeAnnotation) {
2179
2186
  parts.push(path.call(print, 'typeAnnotation'));
2180
2187
  }
2181
2188
 
@@ -2839,20 +2846,15 @@ function printArrowFunction(node, path, options, print, args) {
2839
2846
  // to avoid ambiguity with block statements or to clarify intent
2840
2847
  const bodyDoc = path.call(print, 'body');
2841
2848
  const groupId = Symbol('arrow');
2842
- const shouldBreakBody = shouldBreakArrowExpressionBody(node.body, options);
2849
+ const shouldBreakBody = shouldBreakArrowExpressionBody(node.body, options, args);
2843
2850
  /** @type {Doc | Doc[]} */
2844
2851
  let bodyContent;
2845
2852
  if (
2846
2853
  node.body.type === 'ObjectExpression' ||
2847
2854
  node.body.type === 'AssignmentExpression' ||
2848
- node.body.type === 'SequenceExpression' ||
2849
- (args?.isInAttribute && isTemplateExpression(node.body))
2855
+ node.body.type === 'SequenceExpression'
2850
2856
  ) {
2851
- if (isTemplateExpression(node.body)) {
2852
- bodyContent = ['(', indent([hardline, bodyDoc]), hardline, ')'];
2853
- } else {
2854
- bodyContent = ['(', bodyDoc, ')'];
2855
- }
2857
+ bodyContent = ['(', bodyDoc, ')'];
2856
2858
  } else {
2857
2859
  bodyContent = bodyDoc;
2858
2860
  }
@@ -2885,6 +2887,15 @@ function isTemplateExpression(node) {
2885
2887
  );
2886
2888
  }
2887
2889
 
2890
+ /**
2891
+ * Check whether a braced attribute expression should close on its own line.
2892
+ * @param {AST.Node} node - The expression inside the attribute braces
2893
+ * @returns {boolean}
2894
+ */
2895
+ function shouldBreakAttributeExpressionClosingBrace(node) {
2896
+ return node.type === 'ArrowFunctionExpression' && node.body && isTemplateExpression(node.body);
2897
+ }
2898
+
2888
2899
  /**
2889
2900
  * Print an export default declaration
2890
2901
  * @param {AST.ExportDefaultDeclaration} node - The export default node
@@ -3095,9 +3106,13 @@ function sourceSpanExceedsPrintWidth(node, options) {
3095
3106
  * Check if an arrow expression body should break immediately after `=>`.
3096
3107
  * @param {AST.Expression} node - The arrow body expression
3097
3108
  * @param {RippleFormatOptions} options - Prettier options
3109
+ * @param {PrintArgs} [args] - Additional context arguments
3098
3110
  * @returns {boolean}
3099
3111
  */
3100
- function shouldBreakArrowExpressionBody(node, options) {
3112
+ function shouldBreakArrowExpressionBody(node, options, args) {
3113
+ if (args?.isInAttribute && isTemplateExpression(node)) {
3114
+ return true;
3115
+ }
3101
3116
  return (
3102
3117
  (node.type === 'BinaryExpression' || node.type === 'LogicalExpression') &&
3103
3118
  sourceSpanExceedsPrintWidth(/** @type {AST.NodeWithLocation} */ (node), options)
@@ -5478,9 +5493,7 @@ function printTSConstructorType(node, path, options, print) {
5478
5493
  }
5479
5494
  parts.push(')');
5480
5495
  parts.push(' => ');
5481
- if (node.returnType) {
5482
- parts.push(path.call(print, 'returnType'));
5483
- } else if (node.typeAnnotation) {
5496
+ if (node.typeAnnotation) {
5484
5497
  parts.push(path.call(print, 'typeAnnotation'));
5485
5498
  }
5486
5499
  return parts;
@@ -5778,7 +5791,7 @@ function printTsx(node, path, options, print) {
5778
5791
  return [tagName, closingTagName];
5779
5792
  }
5780
5793
 
5781
- if (printedChildren.length > 1) {
5794
+ if (!is_fragment || printedChildren.length > 1) {
5782
5795
  return group([
5783
5796
  tagName,
5784
5797
  indent([hardline, join(hardline, printedChildren)]),
@@ -6192,11 +6205,15 @@ function printJSXAttribute(attr, path, options, print) {
6192
6205
  }
6193
6206
 
6194
6207
  if (attr.value.type === 'JSXExpressionContainer') {
6208
+ const expression = attr.value.expression;
6195
6209
  const exprDoc = path.call(
6196
6210
  (valuePath) => print(valuePath, { isInAttribute: true }),
6197
6211
  'value',
6198
6212
  'expression',
6199
6213
  );
6214
+ if (shouldBreakAttributeExpressionClosingBrace(expression)) {
6215
+ return [name, '={', exprDoc, hardline, '}'];
6216
+ }
6200
6217
  return [name, '={', exprDoc, '}'];
6201
6218
  }
6202
6219
 
@@ -6774,6 +6791,9 @@ function printAttribute(node, path, options, print) {
6774
6791
  parts.push('={');
6775
6792
  // Pass inline context for attribute values (keep objects compact)
6776
6793
  parts.push(path.call((attrPath) => print(attrPath, { isInAttribute: true }), 'value'));
6794
+ if (shouldBreakAttributeExpressionClosingBrace(node.value)) {
6795
+ parts.push(hardline);
6796
+ }
6777
6797
  parts.push('}');
6778
6798
  }
6779
6799
  }
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);