@ripple-ts/prettier-plugin 0.2.196 → 0.2.198

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": "@ripple-ts/prettier-plugin",
3
- "version": "0.2.196",
3
+ "version": "0.2.198",
4
4
  "description": "Ripple plugin for Prettier",
5
5
  "type": "module",
6
6
  "module": "src/index.js",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "devDependencies": {
27
27
  "prettier": "^3.6.2",
28
- "ripple": "0.2.196"
28
+ "ripple": "0.2.198"
29
29
  },
30
30
  "dependencies": {},
31
31
  "files": [
package/src/index.js CHANGED
@@ -85,6 +85,26 @@ export const printers = {
85
85
  };
86
86
  }
87
87
 
88
+ if (node.type === 'ScriptContent' && node.content) {
89
+ return async (textToDoc) => {
90
+ try {
91
+ // Format JS/TS using Prettier's textToDoc
92
+ const body = await textToDoc(node.content, {
93
+ parser: 'babel-ts',
94
+ });
95
+
96
+ // Return complete element with tags
97
+ // return ['<script>', indent([hardline, formattedContent]), hardline, '</script>'];
98
+ return body;
99
+ } catch (error) {
100
+ // If JS/TS has syntax errors, return original unformatted content
101
+ console.error('Error formatting JS/TS inside <script>:', error);
102
+ return node.content;
103
+ // return ['<script>', indent([hardline, node.content]), hardline, '</script>'];
104
+ }
105
+ };
106
+ }
107
+
88
108
  return null;
89
109
  },
90
110
  getVisitorKeys(node) {
@@ -98,6 +118,7 @@ export const printers = {
98
118
  'css', // Handled by embed()
99
119
  'raw',
100
120
  'regex',
121
+ 'content', // Handled by embed() for <script> tags
101
122
  ]);
102
123
 
103
124
  const keys = Object.keys(node).filter((key) => {
@@ -1290,6 +1311,12 @@ function printRippleNode(node, path, options, print, args) {
1290
1311
  break;
1291
1312
  }
1292
1313
 
1314
+ case 'TSInstantiationExpression': {
1315
+ // Explicit type instantiation: foo<Type>, identity<string>
1316
+ nodeContent = concat([path.call(print, 'expression'), path.call(print, 'typeArguments')]);
1317
+ break;
1318
+ }
1319
+
1293
1320
  case 'JSXExpressionContainer': {
1294
1321
  nodeContent = concat(['{', path.call(print, 'expression'), '}']);
1295
1322
  break;
@@ -1869,7 +1896,7 @@ function printRippleNode(node, path, options, print, args) {
1869
1896
  case 'TSTypeOperator': {
1870
1897
  const operator = node.operator;
1871
1898
  const type = path.call(print, 'typeAnnotation');
1872
- nodeContent = `${operator} ${type}`;
1899
+ nodeContent = concat([operator, ' ', type]);
1873
1900
  break;
1874
1901
  }
1875
1902
 
@@ -2175,7 +2202,7 @@ function printExportNamedDeclaration(node, path, options, print) {
2175
2202
 
2176
2203
  function printComponent(node, path, options, print, innerCommentParts = []) {
2177
2204
  // Use arrays instead of string concatenation
2178
- const signatureParts = ['component ', node.id.name];
2205
+ const signatureParts = node.id ? ['component ', node.id.name] : ['component'];
2179
2206
 
2180
2207
  // Add TypeScript generics if present
2181
2208
  if (node.typeParameters) {
@@ -3137,6 +3164,11 @@ function printTryStatement(node, path, options, print) {
3137
3164
  parts.push('try ');
3138
3165
  parts.push(path.call(print, 'block'));
3139
3166
 
3167
+ if (node.pending) {
3168
+ parts.push(' pending ');
3169
+ parts.push(path.call(print, 'pending'));
3170
+ }
3171
+
3140
3172
  if (node.handler) {
3141
3173
  parts.push(' catch');
3142
3174
  if (node.handler.param) {
@@ -3153,11 +3185,6 @@ function printTryStatement(node, path, options, print) {
3153
3185
  parts.push(path.call(print, 'finalizer'));
3154
3186
  }
3155
3187
 
3156
- if (node.pending) {
3157
- parts.push(' pending ');
3158
- parts.push(path.call(print, 'pending'));
3159
- }
3160
-
3161
3188
  return parts;
3162
3189
  }
3163
3190
 
@@ -3168,8 +3195,22 @@ function printClassBody(node, path, options, print) {
3168
3195
 
3169
3196
  const members = path.map(print, 'body');
3170
3197
 
3171
- // Use AST builders for proper formatting
3172
- return group(['{', indent(concat([line, join(concat([line, line]), members)])), line, '}']);
3198
+ // Build content with proper blank line handling
3199
+ const contentParts = [];
3200
+ for (let i = 0; i < members.length; i++) {
3201
+ if (i > 0) {
3202
+ // Check if we should add a blank line between members
3203
+ const prevNode = node.body[i - 1];
3204
+ const currNode = node.body[i];
3205
+ if (shouldAddBlankLine(prevNode, currNode)) {
3206
+ contentParts.push(line);
3207
+ }
3208
+ }
3209
+ contentParts.push(line);
3210
+ contentParts.push(members[i]);
3211
+ }
3212
+
3213
+ return group(['{', indent(concat(contentParts)), line, '}']);
3173
3214
  }
3174
3215
 
3175
3216
  function printPropertyDefinition(node, path, options, print) {
@@ -3572,6 +3613,9 @@ function printTSTypeParameterDeclaration(node, path, options, print) {
3572
3613
  if (i > 0) parts.push(', ');
3573
3614
  parts.push(paramList[i]);
3574
3615
  }
3616
+ if (node.params.length === 1 && node.extra?.trailingComma !== undefined) {
3617
+ parts.push(',');
3618
+ }
3575
3619
  parts.push('>');
3576
3620
  return parts;
3577
3621
  }
@@ -3598,15 +3642,38 @@ function printTSTypeParameterInstantiation(node, path, options, print) {
3598
3642
  return '';
3599
3643
  }
3600
3644
 
3601
- const parts = [];
3602
- parts.push('<');
3603
3645
  const paramList = path.map(print, 'params');
3646
+
3647
+ // Check if any param has line breaks (e.g., contains object types)
3648
+ const hasBreakingParam = paramList.some((param) => willBreak(param));
3649
+
3650
+ // Build inline version: <T, U>
3651
+ const inlineParts = ['<'];
3604
3652
  for (let i = 0; i < paramList.length; i++) {
3605
- if (i > 0) parts.push(', ');
3653
+ if (i > 0) inlineParts.push(', ');
3654
+ inlineParts.push(paramList[i]);
3655
+ }
3656
+ inlineParts.push('>');
3657
+
3658
+ // If any param breaks, use the breaking version with proper indentation
3659
+ if (hasBreakingParam) {
3660
+ // Build breaking version: <\n T,\n U\n>
3661
+ const breakingParts = [];
3662
+ for (let i = 0; i < paramList.length; i++) {
3663
+ if (i > 0) breakingParts.push(',', hardline);
3664
+ breakingParts.push(paramList[i]);
3665
+ }
3666
+ return group(concat(['<', indent(concat([hardline, ...breakingParts])), hardline, '>']));
3667
+ }
3668
+
3669
+ // Otherwise use group to allow natural breaking
3670
+ const parts = [];
3671
+ for (let i = 0; i < paramList.length; i++) {
3672
+ if (i > 0) parts.push(',', line);
3606
3673
  parts.push(paramList[i]);
3607
3674
  }
3608
- parts.push('>');
3609
- return concat(parts);
3675
+
3676
+ return group(concat(['<', indent(concat([softline, ...parts])), softline, '>']));
3610
3677
  }
3611
3678
 
3612
3679
  function printSwitchStatement(node, path, options, print) {
@@ -4725,33 +4792,19 @@ function printMemberExpressionSimple(node, options, computed = false) {
4725
4792
 
4726
4793
  function printElement(node, path, options, print) {
4727
4794
  const tagName = printMemberExpressionSimple(node.id, options);
4728
-
4729
4795
  const elementLeadingComments = getElementLeadingComments(node);
4730
- const openingTagEndIndex =
4731
- node?.metadata && typeof node.metadata.openingTagEnd === 'number'
4732
- ? node.metadata.openingTagEnd
4733
- : null;
4734
- const nodeStartIndex = typeof node.start === 'number' ? node.start : null;
4735
- const nodeEndIndex = typeof node.end === 'number' ? node.end : null;
4736
4796
 
4737
4797
  // `metadata.elementLeadingComments` may include comments that actually appear *inside* the element
4738
4798
  // body (after the opening tag). Those must not be hoisted before the element.
4739
- const outerElementLeadingComments =
4740
- openingTagEndIndex == null || nodeStartIndex == null
4741
- ? elementLeadingComments
4742
- : elementLeadingComments.filter(
4743
- (comment) => typeof comment.start !== 'number' || comment.start < nodeStartIndex,
4744
- );
4745
- const innerElementBodyComments =
4746
- openingTagEndIndex != null && nodeEndIndex != null
4747
- ? elementLeadingComments.filter(
4748
- (comment) =>
4749
- typeof comment.start === 'number' &&
4750
- comment.start >= openingTagEndIndex &&
4751
- comment.start < nodeEndIndex,
4752
- )
4753
- : [];
4754
-
4799
+ const outerElementLeadingComments = elementLeadingComments.filter(
4800
+ (comment) => typeof comment.start !== 'number' || comment.start < node.start,
4801
+ );
4802
+ const innerElementBodyComments = elementLeadingComments.filter(
4803
+ (comment) =>
4804
+ typeof comment.start === 'number' &&
4805
+ comment.start >= node.openingElement.end &&
4806
+ comment.start < node.end,
4807
+ );
4755
4808
  const metadataCommentParts =
4756
4809
  outerElementLeadingComments.length > 0
4757
4810
  ? createElementLevelCommentParts(outerElementLeadingComments)
@@ -4842,9 +4895,7 @@ function printElement(node, path, options, print) {
4842
4895
  const finalChildren = [];
4843
4896
  const sortedInnerElementBodyComments =
4844
4897
  innerElementBodyComments.length > 0
4845
- ? innerElementBodyComments
4846
- .slice()
4847
- .sort((a, b) => (a.start ?? 0) - (b.start ?? 0))
4898
+ ? innerElementBodyComments.slice().sort((a, b) => (a.start ?? 0) - (b.start ?? 0))
4848
4899
  : [];
4849
4900
  let innerElementBodyCommentIndex = 0;
4850
4901
 
@@ -4915,7 +4966,8 @@ function printElement(node, path, options, print) {
4915
4966
  let insertedBodyCommentsBetween = false;
4916
4967
  if (innerElementBodyCommentIndex < sortedInnerElementBodyComments.length) {
4917
4968
  const currentChildEnd = getNodeEndIndex(currentChild);
4918
- const nextChildStart = nextChild && typeof nextChild.start === 'number' ? nextChild.start : null;
4969
+ const nextChildStart =
4970
+ nextChild && typeof nextChild.start === 'number' ? nextChild.start : null;
4919
4971
  if (typeof currentChildEnd === 'number') {
4920
4972
  const commentsBetween = [];
4921
4973
  while (innerElementBodyCommentIndex < sortedInnerElementBodyComments.length) {
package/src/index.test.js CHANGED
@@ -1729,6 +1729,58 @@ files = [...(files ?? []), ...dt.files];`;
1729
1729
  const result = await format(expected, { singleQuote: true, printWidth: 100 });
1730
1730
  expect(result).toBeWithNewline(expected);
1731
1731
  });
1732
+
1733
+ it('should preserve <script> tags', async () => {
1734
+ const expected = `<script>
1735
+ const i = 2;
1736
+ </script>`;
1737
+
1738
+ const result = await format(expected, { singleQuote: true, printWidth: 100 });
1739
+ expect(result).toBeWithNewline(expected);
1740
+ });
1741
+
1742
+ it('should preserve component as a named or an anonymous property', async () => {
1743
+ const expected = `const UI = {
1744
+ span: component Span() {
1745
+ <span>{'Hello from Span'}</span>
1746
+ },
1747
+ button: component({ children }) {
1748
+ <button>
1749
+ <children />
1750
+ </button>
1751
+ },
1752
+ };`;
1753
+
1754
+ const result = await format(expected, { singleQuote: true, printWidth: 100 });
1755
+ expect(result).toBeWithNewline(expected);
1756
+ });
1757
+
1758
+ it('should preserve the order of try / pending / catch block', async () => {
1759
+ const expected = `component Test() {
1760
+ let items: TrackedArray<string> | null = null;
1761
+ let error: string | null = null;
1762
+
1763
+ async function* throwingIterable() {
1764
+ throw new Error('Async error');
1765
+ }
1766
+
1767
+ try {
1768
+ items = await TrackedArray.fromAsync(throwingIterable());
1769
+ for (const item of items) {
1770
+ <li>{item}</li>
1771
+ }
1772
+ } pending {
1773
+ <div>{'Loading...'}</div>
1774
+ } catch (e) {
1775
+ error = (e as Error).message;
1776
+ } finally {
1777
+ <div>{'finally block'}</div>
1778
+ }
1779
+ }`;
1780
+
1781
+ const result = await format(expected, { singleQuote: true, printWidth: 100 });
1782
+ expect(result).toBeWithNewline(expected);
1783
+ });
1732
1784
  });
1733
1785
 
1734
1786
  describe('edge cases', () => {
@@ -2164,6 +2216,28 @@ const obj2 = #{
2164
2216
  expect(result).toBeWithNewline(expected);
2165
2217
  });
2166
2218
 
2219
+ it('should preserve comments inside js/ts blocks inside markup', async () => {
2220
+ const expected = `component App() {
2221
+ <button
2222
+ onClick={() => {
2223
+ @hasError = false;
2224
+ try {
2225
+ @hasError = false;
2226
+ // @ts-ignore
2227
+ obj['nonexistent']();
2228
+ } catch {
2229
+ // @hasError = true;
2230
+ }
2231
+ }}
2232
+ >
2233
+ {'Nonexistent'}
2234
+ </button>
2235
+ }`;
2236
+
2237
+ const result = await format(expected, { singleQuote: true, printWidth: 100 });
2238
+ expect(result).toBeWithNewline(expected);
2239
+ });
2240
+
2167
2241
  it('should properly format array with various sized strings and 100 printWidth', async () => {
2168
2242
  const expected = `component App() {
2169
2243
  const d = [
@@ -2566,6 +2640,26 @@ const items = [] as unknown[];`;
2566
2640
  expect(result).toBeWithNewline(expected);
2567
2641
  });
2568
2642
 
2643
+ it('should format nested generics types', async () => {
2644
+ const expected = `component Test() {
2645
+ const [children, rest] = trackSplit<
2646
+ PropsWithChildren<{
2647
+ class: string;
2648
+ id: string;
2649
+ onClick: EventListener;
2650
+ }>,
2651
+ keyof PropsWithChildren<{
2652
+ class: string;
2653
+ id: string;
2654
+ onClick: EventListener;
2655
+ }>
2656
+ >(props as Props, ['children']);
2657
+ }`;
2658
+
2659
+ const result = await format(expected, { singleQuote: true });
2660
+ expect(result).toBeWithNewline(expected);
2661
+ });
2662
+
2569
2663
  it('should format TypeScript tuple types (TSTupleType)', async () => {
2570
2664
  const input = `type T = [string, number, boolean];`;
2571
2665
  const expected = `type T = [string, number, boolean];`;
@@ -2650,6 +2744,15 @@ const items = [] as unknown[];`;
2650
2744
  expect(result).toBeWithNewline(expected);
2651
2745
  });
2652
2746
 
2747
+ it('should keep the TSInstantiationExpression ', async () => {
2748
+ const expected = `component Test() {
2749
+ const items = (Promise<string[]>).reject(new Error('Async error'));
2750
+ }`;
2751
+
2752
+ const result = await format(expected, { singleQuote: true });
2753
+ expect(result).toBeWithNewline(expected);
2754
+ });
2755
+
2653
2756
  it('should format TSNonNullExpression in complex expressions', async () => {
2654
2757
  const input = `function getValue(x?:string){return x!.toUpperCase()}`;
2655
2758
  const expected = `function getValue(x?: string) {