@tsrx/prettier-plugin 0.3.51 → 0.3.53

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.51",
3
+ "version": "0.3.53",
4
4
  "description": "Ripple plugin for Prettier",
5
5
  "type": "module",
6
6
  "module": "src/index.js",
@@ -27,8 +27,7 @@
27
27
  "prettier": "^3.8.3"
28
28
  },
29
29
  "dependencies": {
30
- "@tsrx/core": "0.1.1",
31
- "@tsrx/ripple": "0.1.1"
30
+ "@tsrx/core": "0.1.3"
32
31
  },
33
32
  "files": [
34
33
  "src/"
package/src/index.js CHANGED
@@ -18,7 +18,7 @@
18
18
 
19
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 */
20
20
 
21
- import { parse } from '@tsrx/ripple';
21
+ import { parseModule } from '@tsrx/core';
22
22
  import { doc } from 'prettier';
23
23
 
24
24
  const { builders, utils } = doc;
@@ -58,7 +58,7 @@ export const parsers = {
58
58
  * @returns {AST.Program}
59
59
  */
60
60
  parse(text, _options) {
61
- return parse(text);
61
+ return parseModule(text);
62
62
  },
63
63
 
64
64
  /**
@@ -1687,7 +1687,7 @@ function printRippleNode(node, path, options, print, args) {
1687
1687
  break;
1688
1688
 
1689
1689
  case 'ArrowFunctionExpression':
1690
- nodeContent = printArrowFunction(node, path, options, print);
1690
+ nodeContent = printArrowFunction(node, path, options, print, args);
1691
1691
  break;
1692
1692
 
1693
1693
  case 'FunctionExpression':
@@ -2723,9 +2723,10 @@ function printFunctionExpression(node, path, options, print) {
2723
2723
  * @param {AstPath<AST.ArrowFunctionExpression>} path - The AST path
2724
2724
  * @param {RippleFormatOptions} options - Prettier options
2725
2725
  * @param {PrintFn} print - Print callback
2726
+ * @param {PrintArgs} [args] - Additional context arguments
2726
2727
  * @returns {Doc}
2727
2728
  */
2728
- function printArrowFunction(node, path, options, print) {
2729
+ function printArrowFunction(node, path, options, print, args) {
2729
2730
  /** @type {Doc[]} */
2730
2731
  const parts = [];
2731
2732
 
@@ -2773,22 +2774,44 @@ function printArrowFunction(node, path, options, print) {
2773
2774
  // For expression bodies, check if we need to wrap in parens
2774
2775
  // Wrap ObjectExpression, AssignmentExpression, and SequenceExpression in parens
2775
2776
  // to avoid ambiguity with block statements or to clarify intent
2777
+ const bodyDoc = path.call(print, 'body');
2776
2778
  if (
2777
2779
  node.body.type === 'ObjectExpression' ||
2778
2780
  node.body.type === 'AssignmentExpression' ||
2779
- node.body.type === 'SequenceExpression'
2781
+ node.body.type === 'SequenceExpression' ||
2782
+ (args?.isInAttribute && isTemplateExpression(node.body))
2780
2783
  ) {
2781
2784
  parts.push('(');
2782
- parts.push(path.call(print, 'body'));
2785
+ if (isTemplateExpression(node.body)) {
2786
+ parts.push(indent([hardline, bodyDoc]));
2787
+ parts.push(hardline);
2788
+ } else {
2789
+ parts.push(bodyDoc);
2790
+ }
2783
2791
  parts.push(')');
2784
2792
  } else {
2785
- parts.push(path.call(print, 'body'));
2793
+ parts.push(bodyDoc);
2786
2794
  }
2787
2795
  }
2788
2796
 
2789
2797
  return parts;
2790
2798
  }
2791
2799
 
2800
+ /**
2801
+ * Check whether an expression is one of TSRX's template expression wrappers.
2802
+ * @param {AST.Node} node - The node to check
2803
+ * @returns {boolean}
2804
+ */
2805
+ function isTemplateExpression(node) {
2806
+ return (
2807
+ node.type === 'Tsx' ||
2808
+ node.type === 'TsxCompat' ||
2809
+ node.type === 'Tsrx' ||
2810
+ node.type === 'JSXElement' ||
2811
+ node.type === 'JSXFragment'
2812
+ );
2813
+ }
2814
+
2792
2815
  /**
2793
2816
  * Print an export default declaration
2794
2817
  * @param {AST.ExportDefaultDeclaration} node - The export default node
@@ -5816,12 +5839,15 @@ function printJSXElement(node, path, options, print) {
5816
5839
  // Format attributes
5817
5840
  /** @type {Doc} */
5818
5841
  let attributesDoc = '';
5842
+ let hasBreakingAttribute = false;
5819
5843
  if (hasAttributes) {
5820
5844
  /** @type {Doc[]} */
5821
5845
  const attrs = openingElement.attributes.map(
5822
5846
  (/** @type {AST.Node} */ attr, /** @type {number} */ i) => {
5847
+ /** @type {Doc} */
5848
+ let attrDoc = '';
5823
5849
  if (attr.type === 'JSXAttribute') {
5824
- return path.call(
5850
+ attrDoc = path.call(
5825
5851
  (attrPath) =>
5826
5852
  printJSXAttribute(
5827
5853
  /** @type {ESTreeJSX.JSXAttribute} */ (attrPath.node),
@@ -5834,20 +5860,39 @@ function printJSXElement(node, path, options, print) {
5834
5860
  i,
5835
5861
  );
5836
5862
  } else if (attr.type === 'JSXSpreadAttribute' || attr.type === 'SpreadAttribute') {
5837
- return ['{...', path.call(print, 'openingElement', 'attributes', i, 'argument'), '}'];
5863
+ attrDoc = ['{...', path.call(print, 'openingElement', 'attributes', i, 'argument'), '}'];
5838
5864
  }
5839
- return '';
5865
+ if (!hasBreakingAttribute && attrDoc && willBreak(attrDoc)) {
5866
+ hasBreakingAttribute = true;
5867
+ }
5868
+ return attrDoc;
5840
5869
  },
5841
5870
  );
5842
- attributesDoc = [' ', join(' ', attrs)];
5871
+ const attrLineBreak = options.singleAttributePerLine ? hardline : line;
5872
+ attributesDoc = indent([attrLineBreak, join(attrLineBreak, attrs)]);
5843
5873
  }
5874
+ const shouldForceBreak = hasBreakingAttribute;
5844
5875
 
5845
5876
  if (isSelfClosing) {
5846
- return ['<', tagName, typeArgsDoc, attributesDoc, ' />'];
5877
+ return group(['<', tagName, typeArgsDoc, attributesDoc, hasAttributes ? line : ' ', '/>'], {
5878
+ shouldBreak: shouldForceBreak,
5879
+ });
5847
5880
  }
5848
5881
 
5882
+ const openingTag = group(
5883
+ [
5884
+ '<',
5885
+ tagName,
5886
+ typeArgsDoc,
5887
+ attributesDoc,
5888
+ hasAttributes && !options.bracketSameLine ? softline : '',
5889
+ '>',
5890
+ ],
5891
+ { shouldBreak: shouldForceBreak },
5892
+ );
5893
+
5849
5894
  if (!hasChildren) {
5850
- return ['<', tagName, typeArgsDoc, attributesDoc, '></', tagName, '>'];
5895
+ return [openingTag, '</', tagName, '>'];
5851
5896
  }
5852
5897
 
5853
5898
  // Format children - filter out empty text nodes and merge adjacent text nodes
@@ -5891,7 +5936,7 @@ function printJSXElement(node, path, options, print) {
5891
5936
 
5892
5937
  // Check if content can be inlined (single text node or single expression)
5893
5938
  if (childrenDocs.length === 1 && typeof childrenDocs[0] === 'string') {
5894
- return ['<', tagName, typeArgsDoc, attributesDoc, '>', childrenDocs[0], '</', tagName, '>'];
5939
+ return group([openingTag, childrenDocs[0], '</', tagName, '>']);
5895
5940
  }
5896
5941
  const meaningfulChildren = node.children.filter(
5897
5942
  (child) => child.type !== 'JSXText' || child.value.trim(),
@@ -5902,7 +5947,7 @@ function printJSXElement(node, path, options, print) {
5902
5947
  singleMeaningfulChild?.type === 'JSXExpressionContainer' &&
5903
5948
  singleMeaningfulChild.expression.type === 'Identifier'
5904
5949
  ) {
5905
- return ['<', tagName, typeArgsDoc, attributesDoc, '>', childrenDocs[0], '</', tagName, '>'];
5950
+ return group([openingTag, childrenDocs[0], '</', tagName, '>']);
5906
5951
  }
5907
5952
 
5908
5953
  // Multiple children or complex children - format with line breaks
@@ -5916,11 +5961,7 @@ function printJSXElement(node, path, options, print) {
5916
5961
 
5917
5962
  // Build the final element
5918
5963
  return group([
5919
- '<',
5920
- tagName,
5921
- typeArgsDoc,
5922
- attributesDoc,
5923
- '>',
5964
+ openingTag,
5924
5965
  indent([hardline, ...formattedChildren]),
5925
5966
  hardline,
5926
5967
  '</',
@@ -6009,7 +6050,11 @@ function printJSXAttribute(attr, path, options, print) {
6009
6050
  }
6010
6051
 
6011
6052
  if (attr.value.type === 'JSXExpressionContainer') {
6012
- const exprDoc = path.call(print, 'value', 'expression');
6053
+ const exprDoc = path.call(
6054
+ (valuePath) => print(valuePath, { isInAttribute: true }),
6055
+ 'value',
6056
+ 'expression',
6057
+ );
6013
6058
  return [name, '={', exprDoc, '}'];
6014
6059
  }
6015
6060
 
package/src/index.test.js CHANGED
@@ -157,6 +157,112 @@ describe('prettier-plugin', () => {
157
157
  expect(result).toBeWithNewline(expected);
158
158
  });
159
159
 
160
+ it('should break nested TSX element attributes inside expression props', async () => {
161
+ const cases = [
162
+ {
163
+ input: `component Test() {
164
+ <A fallback={(error) => <>
165
+ <B id="xyz" status="error" moreProps={{ a: 1, b: 2 }} value={getErrorMessage(
166
+ error,
167
+ )} otherProp={2} />
168
+ </>} />
169
+ }`,
170
+ expected: `component Test() {
171
+ <A
172
+ fallback={(error) => (
173
+ <>
174
+ <B
175
+ id="xyz"
176
+ status="error"
177
+ moreProps={{ a: 1, b: 2 }}
178
+ value={getErrorMessage(error)}
179
+ otherProp={2}
180
+ />
181
+ </>
182
+ )}
183
+ />
184
+ }`,
185
+ },
186
+ {
187
+ input: `component Test() {
188
+ <A fallback={(error) => <tsx>
189
+ <B id="xyz" status="error" moreProps={{ a: 1, b: 2 }} value={getErrorMessage(
190
+ error,
191
+ )} otherProp={2} />
192
+ </tsx>} />
193
+ }`,
194
+ expected: `component Test() {
195
+ <A
196
+ fallback={(error) => (
197
+ <tsx>
198
+ <B
199
+ id="xyz"
200
+ status="error"
201
+ moreProps={{ a: 1, b: 2 }}
202
+ value={getErrorMessage(error)}
203
+ otherProp={2}
204
+ />
205
+ </tsx>
206
+ )}
207
+ />
208
+ }`,
209
+ },
210
+ {
211
+ input: `component Test() {
212
+ <A fallback={(error) => <tsrx>
213
+ <B id="xyz" status="error" moreProps={{ a: 1, b: 2 }} value={getErrorMessage(
214
+ error,
215
+ )} otherProp={2} />
216
+ </tsrx>} />
217
+ }`,
218
+ expected: `component Test() {
219
+ <A
220
+ fallback={(error) => (
221
+ <tsrx>
222
+ <B
223
+ id="xyz"
224
+ status="error"
225
+ moreProps={{ a: 1, b: 2 }}
226
+ value={getErrorMessage(error)}
227
+ otherProp={2}
228
+ />
229
+ </tsrx>
230
+ )}
231
+ />
232
+ }`,
233
+ },
234
+ {
235
+ input: `component Test() {
236
+ <A fallback={(error) => <tsx:react>
237
+ <B id="xyz" status="error" moreProps={{ a: 1, b: 2 }} value={getErrorMessage(
238
+ error,
239
+ )} otherProp={2} />
240
+ </tsx:react>} />
241
+ }`,
242
+ expected: `component Test() {
243
+ <A
244
+ fallback={(error) => (
245
+ <tsx:react>
246
+ <B
247
+ id="xyz"
248
+ status="error"
249
+ moreProps={{ a: 1, b: 2 }}
250
+ value={getErrorMessage(error)}
251
+ otherProp={2}
252
+ />
253
+ </tsx:react>
254
+ )}
255
+ />
256
+ }`,
257
+ },
258
+ ];
259
+
260
+ for (const { input, expected } of cases) {
261
+ const result = await format(input);
262
+ expect(result).toBeWithNewline(expected);
263
+ }
264
+ });
265
+
160
266
  it('should format whitespace correctly', async () => {
161
267
  const input = `export component Test(){
162
268
  let count=0