@ripple-ts/prettier-plugin 0.2.199 → 0.2.201

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.199",
3
+ "version": "0.2.201",
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.199"
28
+ "ripple": "0.2.201"
29
29
  },
30
30
  "dependencies": {},
31
31
  "files": [
package/src/index.js CHANGED
@@ -550,6 +550,36 @@ function buildInlineArrayCommentDoc(comments) {
550
550
  return docs.length > 0 ? concat(docs) : null;
551
551
  }
552
552
 
553
+ /**
554
+ * @param {AST.Property | AST.MethodDefinition} node
555
+ */
556
+ function printKey(node, path, options, print) {
557
+ const parts = [];
558
+ if (node.computed) {
559
+ // computed are never converted to identifiers
560
+ parts.push('[', path.call(print, 'key'), ']');
561
+ return parts;
562
+ }
563
+
564
+ if (node.key.type === 'Literal' && typeof node.key.value === 'string') {
565
+ // Check if the key is a valid identifier that doesn't need quotes
566
+ const key = node.key.value;
567
+ const isValidIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key);
568
+
569
+ if (isValidIdentifier) {
570
+ // Don't quote valid identifiers
571
+ parts.push(key);
572
+ } else {
573
+ // Quote keys that need it (e.g., contain special characters)
574
+ parts.push(formatStringLiteral(key, options));
575
+ }
576
+ } else {
577
+ parts.push(path.call(print, 'key'));
578
+ }
579
+
580
+ return parts;
581
+ }
582
+
553
583
  function printRippleNode(node, path, options, print, args) {
554
584
  if (!node || typeof node !== 'object') {
555
585
  return String(node || '');
@@ -682,7 +712,7 @@ function printRippleNode(node, path, options, print, args) {
682
712
  break;
683
713
 
684
714
  case 'Component':
685
- nodeContent = printComponent(node, path, options, print, innerCommentParts);
715
+ nodeContent = printComponent(node, path, options, print, innerCommentParts, args);
686
716
  break;
687
717
 
688
718
  case 'ExportNamedDeclaration':
@@ -2200,9 +2230,20 @@ function printExportNamedDeclaration(node, path, options, print) {
2200
2230
  return 'export';
2201
2231
  }
2202
2232
 
2203
- function printComponent(node, path, options, print, innerCommentParts = []) {
2233
+ function printComponent(
2234
+ node,
2235
+ path,
2236
+ options,
2237
+ print,
2238
+ innerCommentParts = [],
2239
+ args = { skipComponentLabel: false },
2240
+ ) {
2204
2241
  // Use arrays instead of string concatenation
2205
- const signatureParts = node.id ? ['component ', node.id.name] : ['component'];
2242
+ const signatureParts = args.skipComponentLabel
2243
+ ? []
2244
+ : node.id
2245
+ ? ['component ', node.id.name]
2246
+ : ['component'];
2206
2247
 
2207
2248
  // Add TypeScript generics if present
2208
2249
  if (node.typeParameters) {
@@ -3106,9 +3147,24 @@ function printObjectExpression(node, path, options, print, args) {
3106
3147
  propertyParts.push(',');
3107
3148
 
3108
3149
  // Check for blank lines between properties and preserve them
3150
+ // Need to account for trailing comments on previous property and
3151
+ // leading comments on current property
3109
3152
  const prevProp = node.properties[i - 1];
3110
3153
  const currentProp = node.properties[i];
3111
- if (prevProp && currentProp && getBlankLinesBetweenNodes(prevProp, currentProp) > 0) {
3154
+
3155
+ // Determine the source node (end of previous property or its trailing comments)
3156
+ let sourceNode = prevProp;
3157
+ if (prevProp?.trailingComments?.length > 0) {
3158
+ sourceNode = prevProp.trailingComments[prevProp.trailingComments.length - 1];
3159
+ }
3160
+
3161
+ // Determine the target node (start of current property or its leading comments)
3162
+ let targetNode = currentProp;
3163
+ if (currentProp?.leadingComments?.length > 0) {
3164
+ targetNode = currentProp.leadingComments[0];
3165
+ }
3166
+
3167
+ if (sourceNode && targetNode && getBlankLinesBetweenNodes(sourceNode, targetNode) > 0) {
3112
3168
  propertyParts.push(hardline);
3113
3169
  propertyParts.push(hardline); // Two hardlines = blank line
3114
3170
  } else {
@@ -3259,6 +3315,7 @@ function printPropertyDefinition(node, path, options, print) {
3259
3315
 
3260
3316
  function printMethodDefinition(node, path, options, print) {
3261
3317
  const parts = [];
3318
+ const is_component = node.value?.type === 'Component';
3262
3319
 
3263
3320
  // Access modifiers (public, private, protected)
3264
3321
  if (node.accessibility) {
@@ -3271,24 +3328,37 @@ function printMethodDefinition(node, path, options, print) {
3271
3328
  parts.push('static ');
3272
3329
  }
3273
3330
 
3274
- // Async keyword
3275
- if (node.value && node.value.async) {
3276
- parts.push('async ');
3277
- }
3278
-
3279
3331
  // Method kind and name
3280
3332
  if (node.kind === 'constructor') {
3281
- parts.push('constructor');
3333
+ // skip as it's covered by the key
3282
3334
  } else if (node.kind === 'get') {
3283
3335
  parts.push('get ');
3284
- parts.push(path.call(print, 'key'));
3285
3336
  } else if (node.kind === 'set') {
3286
3337
  parts.push('set ');
3287
- parts.push(path.call(print, 'key'));
3288
- } else {
3289
- parts.push(path.call(print, 'key'));
3290
3338
  }
3291
3339
 
3340
+ // Async keyword
3341
+ if (node.value && node.value.async) {
3342
+ parts.push('async ');
3343
+ }
3344
+
3345
+ if (node.value.generator) {
3346
+ parts.push('*');
3347
+ }
3348
+
3349
+ if (is_component) {
3350
+ if (node.value.id) {
3351
+ // takes care of component methods
3352
+ parts.push(path.call(print, 'value'));
3353
+ return concat(parts);
3354
+ }
3355
+
3356
+ parts.push('component ');
3357
+ }
3358
+
3359
+ // the key is 'constructor' and we already handled that above
3360
+ parts.push(...printKey(node, path, options, print));
3361
+
3292
3362
  // Add TypeScript generics if present (always on the method node, not on value)
3293
3363
  if (node.typeParameters) {
3294
3364
  const typeParams = path.call(print, 'typeParameters');
@@ -3299,6 +3369,13 @@ function printMethodDefinition(node, path, options, print) {
3299
3369
  }
3300
3370
  }
3301
3371
 
3372
+ if (is_component) {
3373
+ parts.push(
3374
+ ...path.call((childPath) => print(childPath, { skipComponentLabel: true }), 'value'),
3375
+ );
3376
+ return concat(parts);
3377
+ }
3378
+
3302
3379
  // Parameters - use proper path.map for TypeScript support
3303
3380
  parts.push('(');
3304
3381
  if (node.value && node.value.params && node.value.params.length > 0) {
@@ -3960,7 +4037,7 @@ function printProperty(node, path, options, print) {
3960
4037
  return path.call(print, 'key');
3961
4038
  }
3962
4039
 
3963
- const parts = [];
4040
+ const is_component = node.value?.type === 'Component';
3964
4041
 
3965
4042
  // Handle getter/setter methods
3966
4043
  if (node.kind === 'get' || node.kind === 'set') {
@@ -3970,20 +4047,7 @@ function printProperty(node, path, options, print) {
3970
4047
  // Add get/set keyword
3971
4048
  methodParts.push(node.kind, ' ');
3972
4049
 
3973
- // Print key (with computed property brackets if needed)
3974
- if (node.computed) {
3975
- methodParts.push('[', path.call(print, 'key'), ']');
3976
- } else if (node.key.type === 'Literal' && typeof node.key.value === 'string') {
3977
- const key = node.key.value;
3978
- const isValidIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key);
3979
- if (isValidIdentifier) {
3980
- methodParts.push(key);
3981
- } else {
3982
- methodParts.push(formatStringLiteral(key, options));
3983
- }
3984
- } else {
3985
- methodParts.push(path.call(print, 'key'));
3986
- }
4050
+ methodParts.push(...printKey(node, path, options, print));
3987
4051
 
3988
4052
  // Print parameters by calling into the value path
3989
4053
  const paramsPart = path.call(
@@ -4002,7 +4066,7 @@ function printProperty(node, path, options, print) {
4002
4066
  }
4003
4067
 
4004
4068
  // Handle method shorthand: increment() {} instead of increment: function() {}
4005
- if (node.method && node.value.type === 'FunctionExpression') {
4069
+ if (node.method && (node.value.type === 'FunctionExpression' || is_component)) {
4006
4070
  const methodParts = [];
4007
4071
  const funcValue = node.value;
4008
4072
 
@@ -4011,26 +4075,23 @@ function printProperty(node, path, options, print) {
4011
4075
  methodParts.push('async ');
4012
4076
  }
4013
4077
 
4014
- // Print key (with computed property brackets if needed)
4015
- if (node.computed) {
4016
- methodParts.push('[', path.call(print, 'key'), ']');
4017
- } else if (node.key.type === 'Literal' && typeof node.key.value === 'string') {
4018
- // Check if the key is a valid identifier that doesn't need quotes
4019
- const key = node.key.value;
4020
- const isValidIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key);
4021
- if (isValidIdentifier) {
4022
- methodParts.push(key);
4023
- } else {
4024
- methodParts.push(formatStringLiteral(key, options));
4025
- }
4026
- } else {
4027
- methodParts.push(path.call(print, 'key'));
4078
+ if (is_component) {
4079
+ methodParts.push('component ');
4028
4080
  }
4029
4081
 
4030
4082
  if (funcValue.generator) {
4031
4083
  methodParts.push('*');
4032
4084
  }
4033
4085
 
4086
+ methodParts.push(...printKey(node, path, options, print));
4087
+
4088
+ if (is_component) {
4089
+ methodParts.push(
4090
+ path.call((childPath) => print(childPath, { skipComponentLabel: true }), 'value'),
4091
+ );
4092
+ return concat(methodParts);
4093
+ }
4094
+
4034
4095
  // Print parameters by calling into the value path
4035
4096
  const paramsPart = path.call(
4036
4097
  (valuePath) => printFunctionParameters(valuePath, options, print),
@@ -4047,27 +4108,8 @@ function printProperty(node, path, options, print) {
4047
4108
  return concat(methodParts);
4048
4109
  }
4049
4110
 
4050
- // Handle property key - if it's a Literal (quoted string in source),
4051
- // check if it needs quotes or can be unquoted
4052
- if (node.computed) {
4053
- // Computed property: [key]
4054
- parts.push('[', path.call(print, 'key'), ']');
4055
- } else if (node.key.type === 'Literal' && typeof node.key.value === 'string') {
4056
- // Check if the key is a valid identifier that doesn't need quotes
4057
- const key = node.key.value;
4058
- const isValidIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key);
4059
-
4060
- if (isValidIdentifier) {
4061
- // Don't quote valid identifiers
4062
- parts.push(key);
4063
- } else {
4064
- // Quote keys that need it (e.g., contain special characters)
4065
- parts.push(formatStringLiteral(key, options));
4066
- }
4067
- } else {
4068
- // For non-literal keys, print normally
4069
- parts.push(path.call(print, 'key'));
4070
- }
4111
+ const parts = [];
4112
+ parts.push(...printKey(node, path, options, print));
4071
4113
 
4072
4114
  parts.push(': ');
4073
4115
  parts.push(path.call(print, 'value'));
package/src/index.test.js CHANGED
@@ -1781,6 +1781,116 @@ files = [...(files ?? []), ...dt.files];`;
1781
1781
  const result = await format(expected, { singleQuote: true, printWidth: 100 });
1782
1782
  expect(result).toBeWithNewline(expected);
1783
1783
  });
1784
+
1785
+ it('should preserve class component method', async () => {
1786
+ const expected = `class TestClass {
1787
+ component something() {
1788
+ <div>{'Nested component'}</div>
1789
+ }
1790
+ }`;
1791
+
1792
+ const result = await format(expected, { singleQuote: true, printWidth: 100 });
1793
+ expect(result).toBeWithNewline(expected);
1794
+ });
1795
+
1796
+ it('should preserve class computed method', async () => {
1797
+ const expected = `class TestClass {
1798
+ ['something']() {
1799
+ const i = 10;
1800
+ }
1801
+ }`;
1802
+
1803
+ const result = await format(expected, { singleQuote: true, printWidth: 100 });
1804
+ expect(result).toBeWithNewline(expected);
1805
+ });
1806
+
1807
+ it('should preserve class computed component method', async () => {
1808
+ const expected = `class TestClass {
1809
+ component ['something']() {
1810
+ <div>{'Nested component'}</div>
1811
+ }
1812
+ }`;
1813
+
1814
+ const result = await format(expected, { singleQuote: true, printWidth: 100 });
1815
+ expect(result).toBeWithNewline(expected);
1816
+ });
1817
+
1818
+ it('should format class with a literal component method', async () => {
1819
+ const input = `class TestClass {
1820
+ component 'something'() {
1821
+ <div>{'Nested component'}</div>
1822
+ }
1823
+ }`;
1824
+
1825
+ const expected = `class TestClass {
1826
+ component something() {
1827
+ <div>{'Nested component'}</div>
1828
+ }
1829
+ }`;
1830
+
1831
+ const result = await format(input, { singleQuote: true, printWidth: 100 });
1832
+ expect(result).toBeWithNewline(expected);
1833
+ });
1834
+
1835
+ it('should preserve object component methods', async () => {
1836
+ const expected = `const obj = {
1837
+ component something() {
1838
+ <div>{'Nested component'}</div>
1839
+ },
1840
+ };`;
1841
+
1842
+ const result = await format(expected, { singleQuote: true, printWidth: 100 });
1843
+ expect(result).toBeWithNewline(expected);
1844
+ });
1845
+
1846
+ it('should preserve object computed methods', async () => {
1847
+ const expected = `const obj = {
1848
+ ['something']() {
1849
+ const i = 10;
1850
+ },
1851
+ };`;
1852
+
1853
+ const result = await format(expected, { singleQuote: true, printWidth: 100 });
1854
+ expect(result).toBeWithNewline(expected);
1855
+ });
1856
+
1857
+ it('should preserve object computed component method', async () => {
1858
+ const expected = `const obj = {
1859
+ component ['something']() {
1860
+ <div>{'Nested component'}</div>
1861
+ },
1862
+ };`;
1863
+
1864
+ const result = await format(expected, { singleQuote: true, printWidth: 100 });
1865
+ expect(result).toBeWithNewline(expected);
1866
+ });
1867
+
1868
+ it('should format object with a literal component method', async () => {
1869
+ const input = `const obj = {
1870
+ component 'something'() {
1871
+ <div>{'Nested component'}</div>
1872
+ },
1873
+ };`;
1874
+ const expected = `const obj = {
1875
+ component something() {
1876
+ <div>{'Nested component'}</div>
1877
+ },
1878
+ };`;
1879
+
1880
+ const result = await format(input, { singleQuote: true, printWidth: 100 });
1881
+ expect(result).toBeWithNewline(expected);
1882
+ });
1883
+
1884
+ it('should print class constructor method only once', async () => {
1885
+ const expected = `class TestClass {
1886
+ constructor(value: T) {
1887
+ this.value = value;
1888
+ }
1889
+ }`;
1890
+
1891
+ const result = await format(expected, { singleQuote: true, printWidth: 100 });
1892
+ expect(result).toBeWithNewline(expected);
1893
+ });
1784
1894
  });
1785
1895
 
1786
1896
  describe('edge cases', () => {
@@ -2110,6 +2220,21 @@ const obj2 = #{
2110
2220
  expect(result).toBeWithNewline(expected);
2111
2221
  });
2112
2222
 
2223
+ it('should not add an extra new line above a comment inside objects and in between properties', async () => {
2224
+ const expected = `let obj = {
2225
+ ['hey']: function () {
2226
+ const i = 'yo';
2227
+ },
2228
+ // <div>{'Weird name component'}</div>
2229
+ normal() {
2230
+ const b = 'hey';
2231
+ },
2232
+ };`;
2233
+
2234
+ const result = await format(expected, { singleQuote: true });
2235
+ expect(result).toBeWithNewline(expected);
2236
+ });
2237
+
2113
2238
  it('should preserve comment if the whole component code is commented out', async () => {
2114
2239
  const expected = `export component Test() {
2115
2240
  // thing
@@ -2939,6 +3064,10 @@ try {
2939
3064
  {
2940
3065
  "id": "toast:6",
2941
3066
  "stacked": false,
3067
+ },
3068
+ {
3069
+ ["id"]: "toast:6",
3070
+ ["stacked"]: false,
2942
3071
  }
2943
3072
  ];`;
2944
3073
 
@@ -2963,6 +3092,10 @@ try {
2963
3092
  id: 'toast:6',
2964
3093
  stacked: false,
2965
3094
  },
3095
+ {
3096
+ ['id']: 'toast:6',
3097
+ ['stacked']: false,
3098
+ },
2966
3099
  ];`;
2967
3100
 
2968
3101
  const result = await format(input, { singleQuote: true, arrowParens: 'always' });