@tsrx/prettier-plugin 0.3.76 → 0.3.78

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.76",
3
+ "version": "0.3.78",
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.24"
30
+ "@tsrx/core": "0.1.26"
31
31
  },
32
32
  "files": [
33
33
  "src/"
package/src/index.js CHANGED
@@ -1851,7 +1851,13 @@ function printRippleNode(node, path, options, print, args) {
1851
1851
  blockParent.type === 'ForOfStatement' ||
1852
1852
  blockParent.type === 'WhileStatement' ||
1853
1853
  blockParent.type === 'DoWhileStatement' ||
1854
- blockParent.type === 'SwitchCase');
1854
+ blockParent.type === 'TryStatement' ||
1855
+ blockParent.type === 'CatchClause' ||
1856
+ blockParent.type === 'SwitchCase' ||
1857
+ blockParent.type === 'JSXIfExpression' ||
1858
+ blockParent.type === 'JSXForExpression' ||
1859
+ blockParent.type === 'JSXTryExpression' ||
1860
+ blockParent.type === 'JSXSwitchExpression');
1855
1861
 
1856
1862
  if (isControlFlow) {
1857
1863
  nodeContent = ['{', hardline, '}'];
@@ -5864,7 +5870,12 @@ function printJSXElement(node, path, options, print) {
5864
5870
  const openingElement = node.openingElement;
5865
5871
  const closingElement = node.closingElement;
5866
5872
 
5867
- const tagName = printJSXElementName(openingElement.name);
5873
+ // Dynamic tags (`<{expr}>`) print the opening expression for both tags so
5874
+ // they stay textually identical; static names print as plain strings.
5875
+ const tagName =
5876
+ /** @type {any} */ (openingElement.name).type === 'JSXExpressionContainer'
5877
+ ? ['{', path.call(print, 'openingElement', 'name', 'expression'), '}']
5878
+ : printJSXElementName(openingElement.name);
5868
5879
 
5869
5880
  const isSelfClosing = openingElement.selfClosing;
5870
5881
  const hasAttributes = openingElement.attributes && openingElement.attributes.length > 0;
@@ -6079,11 +6090,19 @@ function printJSXElement(node, path, options, print) {
6079
6090
  return Array.isArray(leadingComments) && leadingComments.length > 0;
6080
6091
  });
6081
6092
  const forceMultiline = hasClosingComments || hasChildLeadingComments;
6093
+ const singleChildNode = childNodes.length === 1 ? childNodes[0] : null;
6094
+ const hasAuthoredMultilineSingleTextChild =
6095
+ singleChildNode?.type === 'JSXText' && /[\r\n]/u.test(singleChildNode.value);
6082
6096
 
6083
6097
  // Check if content can be inlined (single text node or single expression).
6084
6098
  // Trailing or child-leading comments force the multi-line layout. A single
6085
6099
  // text child stays inline when it fits and otherwise fills/wraps to printWidth.
6086
- if (!forceMultiline && childrenDocs.length === 1 && typeof childrenDocs[0] === 'string') {
6100
+ if (
6101
+ !forceMultiline &&
6102
+ !hasAuthoredMultilineSingleTextChild &&
6103
+ childrenDocs.length === 1 &&
6104
+ typeof childrenDocs[0] === 'string'
6105
+ ) {
6087
6106
  // The open tag breaks for attributes independently; the text+closing get
6088
6107
  // their own group so the text only drops to its own (filled) lines when it
6089
6108
  // itself overflows — otherwise it hugs `>text</tag>`.
package/src/index.test.js CHANGED
@@ -74,6 +74,34 @@ describe('prettier-plugin', () => {
74
74
  expect(result).toBeWithNewline(expected);
75
75
  });
76
76
 
77
+ it('formats dynamic element tags', async () => {
78
+ const input = `function App(props){const Child='div';return <{Child} {...props} class="card"><span>Hello</span></{Child}>}`;
79
+ const expected = `function App(props) {
80
+ const Child = "div";
81
+ return <{Child} {...props} class="card">
82
+ <span>Hello</span>
83
+ </{Child}>;
84
+ }`;
85
+
86
+ const result = await format(input);
87
+ expect(result).toBeWithNewline(expected);
88
+ });
89
+
90
+ it('formats dynamic element tag expressions', async () => {
91
+ const input = `function App(){return <><{registry.item}/><{items[0]}/><{'section'}/><{\`article\`}/></>;}`;
92
+ const expected = `function App() {
93
+ return <>
94
+ <{registry.item} />
95
+ <{items[0]} />
96
+ <{"section"} />
97
+ <{\`article\`} />
98
+ </>;
99
+ }`;
100
+
101
+ const result = await format(input);
102
+ expect(result).toBeWithNewline(expected);
103
+ });
104
+
77
105
  it('formats a fragment code block with setup and template control flow', async () => {
78
106
  const input = `function App(){return <>@{
79
107
  const items=[1,2,3];
@@ -298,6 +326,39 @@ const items=[1,2,3];
298
326
  expect(result).toBeWithNewline(expected);
299
327
  });
300
328
 
329
+ it('preserves authored multiline whitespace around a single JSXText child', async () => {
330
+ const input = `function Foo() @{
331
+ @if (props.onRemove) {
332
+ <button
333
+ class={\`\${styles.actionButton} \${styles.actionButtonDanger}\`}
334
+ type="button"
335
+ onClick={() => {
336
+ void props.onRemove?.();
337
+ }}
338
+ >
339
+ Remove shortcut
340
+ </button>
341
+ }
342
+ }`;
343
+
344
+ const expected = `function Foo() @{
345
+ @if (props.onRemove) {
346
+ <button
347
+ class={\`\${styles.actionButton} \${styles.actionButtonDanger}\`}
348
+ type="button"
349
+ onClick={() => {
350
+ void props.onRemove?.();
351
+ }}
352
+ >
353
+ Remove shortcut
354
+ </button>
355
+ }
356
+ }`;
357
+
358
+ const result = await format(input);
359
+ expect(result).toBeWithNewline(expected);
360
+ });
361
+
301
362
  it('preserves inline text spaces around expression children', async () => {
302
363
  const input = `function Test(){return <div><p class="status">Visible: {String(visible)}</p><p>{name} is visible</p><p>Hello {name}!</p></div>}`;
303
364
  const expected = `function Test() {
@@ -2495,6 +2556,51 @@ files = [...(files ?? []), ...dt.files];`;
2495
2556
  expect(result).toBeWithNewline(expected);
2496
2557
  });
2497
2558
 
2559
+ it('expands empty braces for template control-flow blocks', async () => {
2560
+ const input = `const App=()=> <>@if (ready) {} @else {}@for (const item of items) {} @empty {}</>;`;
2561
+ const expected = `const App = () => <>
2562
+ @if (ready) {
2563
+ } @else {
2564
+ }
2565
+ @for (const item of items) {
2566
+ } @empty {
2567
+ }
2568
+ </>;`;
2569
+ const result = await format(input);
2570
+ expect(result).toBeWithNewline(expected);
2571
+ });
2572
+
2573
+ it('expands empty braces for try family blocks', async () => {
2574
+ const input = `function Foo() @{ @try {} @pending {} @catch {} }
2575
+ function Bar() @{ @try {} @catch {} }
2576
+ function Baz() { try {} catch {} finally {} }
2577
+ function Qux() { try {} catch {} }`;
2578
+ const expected = `function Foo() @{
2579
+ @try {
2580
+ } @pending {
2581
+ } @catch {
2582
+ }
2583
+ }
2584
+ function Bar() @{
2585
+ @try {
2586
+ } @catch {
2587
+ }
2588
+ }
2589
+ function Baz() {
2590
+ try {
2591
+ } catch {
2592
+ } finally {
2593
+ }
2594
+ }
2595
+ function Qux() {
2596
+ try {
2597
+ } catch {
2598
+ }
2599
+ }`;
2600
+ const result = await format(input);
2601
+ expect(result).toBeWithNewline(expected);
2602
+ });
2603
+
2498
2604
  it('prints function with a rest parameter correctly', async () => {
2499
2605
  const expected = `function TestRest(...args: string[]) {
2500
2606
  console.log(args);
@@ -5686,7 +5792,8 @@ render(App);`;
5686
5792
  // <div>
5687
5793
  @try {
5688
5794
  <div>b is true</div>
5689
- } @catch (e) {}
5795
+ } @catch (e) {
5796
+ }
5690
5797
  // <div>
5691
5798
  // <div>
5692
5799
  // @if (b) {
@@ -5704,7 +5811,8 @@ render(App);`;
5704
5811
  // <div>
5705
5812
  @try {
5706
5813
  <div>b is true</div>
5707
- } @catch (e) {}
5814
+ } @catch (e) {
5815
+ }
5708
5816
  // <div>
5709
5817
  // <div>
5710
5818
  // @if (b) {