@tsrx/prettier-plugin 0.3.66 → 0.3.67

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.66",
3
+ "version": "0.3.67",
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.14"
30
+ "@tsrx/core": "0.1.15"
31
31
  },
32
32
  "files": [
33
33
  "src/"
package/src/index.js CHANGED
@@ -16,7 +16,7 @@
16
16
 
17
17
  /** @typedef {Partial<Pick<ParserOptions, 'singleQuote' | 'jsxSingleQuote' | 'semi' | 'trailingComma' | 'useTabs' | 'tabWidth' | 'singleAttributePerLine' | 'bracketSameLine' | 'bracketSpacing' | 'arrowParens' | 'originalText' | 'printWidth'>> & { locStart: (node: AST.NodeWithLocation) => number, locEnd: (node: AST.NodeWithLocation) => number }} RippleFormatOptions */
18
18
 
19
- /** @typedef {{ isInAttribute?: boolean, isInArray?: boolean, allowInlineObject?: boolean, isConditionalTest?: boolean, isNestedConditional?: boolean, suppressLeadingComments?: boolean, suppressExpressionLeadingComments?: boolean, isInlineContext?: boolean, isStatement?: boolean, isLogicalAndOr?: boolean, allowShorthandProperty?: boolean, isFirstChild?: boolean, skipComponentLabel?: boolean, noBreakInside?: boolean, expandLastArg?: boolean }} PrintArgs */
19
+ /** @typedef {{ isInAttribute?: boolean, isInArray?: boolean, allowInlineObject?: boolean, isConditionalTest?: boolean, isNestedConditional?: boolean, suppressLeadingComments?: boolean, suppressExpressionLeadingComments?: boolean, isInlineContext?: boolean, isStatement?: boolean, isLogicalAndOr?: boolean, allowShorthandProperty?: boolean, isFirstChild?: boolean, skipComponentLabel?: boolean, noBreakInside?: boolean, expandLastArg?: boolean, preferInlineSimpleUnionType?: boolean }} PrintArgs */
20
20
 
21
21
  import { parseModule } from '@tsrx/core';
22
22
  import { doc } from 'prettier';
@@ -35,6 +35,7 @@ const {
35
35
  breakParent,
36
36
  indentIfBreak,
37
37
  lineSuffix,
38
+ align,
38
39
  } = builders;
39
40
  const { replaceEndOfLine, willBreak } = utils;
40
41
 
@@ -1502,16 +1503,24 @@ function printRippleNode(node, path, options, print, args) {
1502
1503
  break;
1503
1504
 
1504
1505
  case 'TSAsExpression': {
1505
- nodeContent = [path.call(print, 'expression'), ' as ', path.call(print, 'typeAnnotation')];
1506
+ const typeAnnotation = path.call(
1507
+ (typePath) => print(typePath, { preferInlineSimpleUnionType: true }),
1508
+ 'typeAnnotation',
1509
+ );
1510
+ nodeContent = willBreak(typeAnnotation)
1511
+ ? [path.call(print, 'expression'), ' as', indent([line, typeAnnotation])]
1512
+ : [path.call(print, 'expression'), ' as ', typeAnnotation];
1506
1513
  break;
1507
1514
  }
1508
1515
 
1509
1516
  case 'TSSatisfiesExpression': {
1510
- nodeContent = [
1511
- path.call(print, 'expression'),
1512
- ' satisfies ',
1513
- path.call(print, 'typeAnnotation'),
1514
- ];
1517
+ const typeAnnotation = path.call(
1518
+ (typePath) => print(typePath, { preferInlineSimpleUnionType: true }),
1519
+ 'typeAnnotation',
1520
+ );
1521
+ nodeContent = willBreak(typeAnnotation)
1522
+ ? [path.call(print, 'expression'), ' satisfies', indent([line, typeAnnotation])]
1523
+ : [path.call(print, 'expression'), ' satisfies ', typeAnnotation];
1515
1524
  break;
1516
1525
  }
1517
1526
 
@@ -2137,8 +2146,7 @@ function printRippleNode(node, path, options, print, args) {
2137
2146
  break;
2138
2147
 
2139
2148
  case 'TSUnionType': {
2140
- const types = path.map(print, 'types');
2141
- nodeContent = join(' | ', types);
2149
+ nodeContent = printTSUnionType(node, path, print, args);
2142
2150
  break;
2143
2151
  }
2144
2152
 
@@ -4418,6 +4426,35 @@ function printTSTypeAliasDeclaration(node, path, options, print) {
4418
4426
  return group([head, ' =', indent([line, path.call(print, 'typeAnnotation')]), semi(options)]);
4419
4427
  }
4420
4428
 
4429
+ /**
4430
+ * Print a TypeScript union type
4431
+ * @param {AST.TSUnionType} node - The union node
4432
+ * @param {AstPath<AST.TSUnionType>} path - The AST path
4433
+ * @param {PrintFn} print - Print callback
4434
+ * @param {PrintArgs} [args] - Additional context arguments
4435
+ * @returns {Doc}
4436
+ */
4437
+ function printTSUnionType(node, path, print, args) {
4438
+ const types = path.map(print, 'types');
4439
+ const inlineDoc = join(' | ', types);
4440
+ const multilineDoc = [
4441
+ '| ',
4442
+ join(
4443
+ [hardline, '| '],
4444
+ types.map((typeDoc) => align(2, typeDoc)),
4445
+ ),
4446
+ ];
4447
+ const shouldBreak = node.types.some(
4448
+ (typeNode, index) => !wasOriginallySingleLine(typeNode) || willBreak(types[index]),
4449
+ );
4450
+
4451
+ if (args?.preferInlineSimpleUnionType && !types.some((typeDoc) => willBreak(typeDoc))) {
4452
+ return inlineDoc;
4453
+ }
4454
+
4455
+ return shouldBreak ? group(multilineDoc) : conditionalGroup([inlineDoc, multilineDoc]);
4456
+ }
4457
+
4421
4458
  /**
4422
4459
  * Print a TypeScript enum declaration
4423
4460
  * @param {AST.TSEnumDeclaration} node - The enum declaration node
package/src/index.test.js CHANGED
@@ -3035,6 +3035,41 @@ function test() {
3035
3035
  expect(result).toBeWithNewline(expected);
3036
3036
  });
3037
3037
 
3038
+ it('should preserve comments in destructured typed component parameters', async () => {
3039
+ const expected = `component Child({
3040
+ tr: &[count, tr],
3041
+ // test,
3042
+ }: {
3043
+ tr: [number, Tracked<number>];
3044
+ // test: (node: HTMLDivElement) => void;
3045
+ }) {
3046
+ <button
3047
+ onClick={() => {
3048
+ count++;
3049
+ tr[0]++;
3050
+ }}
3051
+ >
3052
+ {count}
3053
+ </button>
3054
+ }`;
3055
+
3056
+ const result = await format(expected);
3057
+ expect(result).toBeWithNewline(expected);
3058
+ });
3059
+
3060
+ it('should preserve comments in destructured typed function parameters', async () => {
3061
+ const expected = `function Child({
3062
+ tr: &[count, tr],
3063
+ // test,
3064
+ }: {
3065
+ tr: [number, Tracked<number>];
3066
+ // test: (node: HTMLDivElement) => void;
3067
+ }) {}`;
3068
+
3069
+ const result = await format(expected);
3070
+ expect(result).toBeWithNewline(expected);
3071
+ });
3072
+
3038
3073
  it('should preserve trailing comments in call arguments', async () => {
3039
3074
  const expected = `fn(
3040
3075
  arg1,
@@ -3782,6 +3817,70 @@ second"</pre>
3782
3817
  expect(result).toBeWithNewline(expected);
3783
3818
  });
3784
3819
 
3820
+ it('should normalize simple cast union types at print width 100', async () => {
3821
+ const input = `const alphaLink = container.querySelector('[data-route-id="alpha"]') as HTMLAnchorElement | null;
3822
+ const saveButton = container.querySelector('[data-action-id="save"]') as HTMLButtonElement | null;
3823
+ const deleteButton = container.querySelector('[data-action-id="delete"]') as | HTMLButtonElement
3824
+ | null;`;
3825
+
3826
+ const expected = `const alphaLink = container.querySelector('[data-route-id="alpha"]') as HTMLAnchorElement | null;
3827
+ const saveButton = container.querySelector('[data-action-id="save"]') as HTMLButtonElement | null;
3828
+ const deleteButton = container.querySelector(
3829
+ '[data-action-id="delete"]',
3830
+ ) as HTMLButtonElement | null;`;
3831
+
3832
+ const result = await format(input, { printWidth: 100, singleQuote: true });
3833
+ expect(result).toBeWithNewline(expected);
3834
+ });
3835
+
3836
+ it('should normalize simple cast union types at print width 80', async () => {
3837
+ const input = `const alphaLink = container.querySelector('[data-route-id="alpha"]') as HTMLAnchorElement | null;
3838
+ const saveButton = container.querySelector('[data-action-id="save"]') as HTMLButtonElement | null;
3839
+ const deleteButton = container.querySelector('[data-action-id="delete"]') as | HTMLButtonElement
3840
+ | null;`;
3841
+
3842
+ const expected = `const alphaLink = container.querySelector(
3843
+ '[data-route-id="alpha"]',
3844
+ ) as HTMLAnchorElement | null;
3845
+ const saveButton = container.querySelector(
3846
+ '[data-action-id="save"]',
3847
+ ) as HTMLButtonElement | null;
3848
+ const deleteButton = container.querySelector(
3849
+ '[data-action-id="delete"]',
3850
+ ) as HTMLButtonElement | null;`;
3851
+
3852
+ const result = await format(input, { printWidth: 80, singleQuote: true });
3853
+ expect(result).toBeWithNewline(expected);
3854
+ });
3855
+
3856
+ it('should format multiline TypeScript union object types like Prettier TypeScript', async () => {
3857
+ const input = `type SvgIconSource = { name: SvgIconName; data?: never } | {
3858
+ data: SvgIconData;
3859
+ name?: never;
3860
+ }`;
3861
+
3862
+ const expected = `type SvgIconSource =
3863
+ | { name: SvgIconName; data?: never }
3864
+ | {
3865
+ data: SvgIconData;
3866
+ name?: never;
3867
+ };`;
3868
+
3869
+ const result = await format(input);
3870
+ expect(result).toBeWithNewline(expected);
3871
+ });
3872
+
3873
+ it('should break long TypeScript union types with leading operators', async () => {
3874
+ const input = `type Source = SomeVeryLongTypeNameThatWillDefinitelyNotFit | AnotherVeryLongTypeNameThatWillDefinitelyNotFit;`;
3875
+
3876
+ const expected = `type Source =
3877
+ | SomeVeryLongTypeNameThatWillDefinitelyNotFit
3878
+ | AnotherVeryLongTypeNameThatWillDefinitelyNotFit;`;
3879
+
3880
+ const result = await format(input, { printWidth: 50 });
3881
+ expect(result).toBeWithNewline(expected);
3882
+ });
3883
+
3785
3884
  it('should not overindent multiline object type aliases', async () => {
3786
3885
  const input = `type ModuleShape = {
3787
3886
  default: ComponentType<{ value: string }>;