@tsrx/prettier-plugin 0.3.28 → 0.3.30

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.28",
3
+ "version": "0.3.30",
4
4
  "description": "Ripple plugin for Prettier",
5
5
  "type": "module",
6
6
  "module": "src/index.js",
@@ -26,11 +26,11 @@
26
26
  "devDependencies": {
27
27
  "@types/node": "^24.3.0",
28
28
  "prettier": "^3.8.3",
29
- "ripple": "0.3.28"
29
+ "ripple": "0.3.30"
30
30
  },
31
31
  "dependencies": {
32
- "@tsrx/core": "0.0.8",
33
- "@tsrx/ripple": "0.0.10"
32
+ "@tsrx/core": "0.0.10",
33
+ "@tsrx/ripple": "0.0.12"
34
34
  },
35
35
  "files": [
36
36
  "src/"
package/src/index.js CHANGED
@@ -5554,6 +5554,7 @@ function createElementLevelCommentPartsTrimmed(comments) {
5554
5554
 
5555
5555
  /**
5556
5556
  * Print a Tsx node - renders Ripple template children inside <tsx>...</tsx>
5557
+ * or fragment shorthand <>...</> when the original source used a fragment.
5557
5558
  * @param {AST.Tsx} node - The Tsx node
5558
5559
  * @param {AstPath<AST.Tsx>} path - The AST path
5559
5560
  * @param {RippleFormatOptions} options - Prettier options
@@ -5561,8 +5562,9 @@ function createElementLevelCommentPartsTrimmed(comments) {
5561
5562
  * @returns {Doc}
5562
5563
  */
5563
5564
  function printTsx(node, path, options, print) {
5564
- const tagName = '<tsx>';
5565
- const closingTagName = '</tsx>';
5565
+ const is_fragment = !node.openingElement?.name;
5566
+ const tagName = is_fragment ? '<>' : '<tsx>';
5567
+ const closingTagName = is_fragment ? '</>' : '</tsx>';
5566
5568
 
5567
5569
  const hasChildren = Array.isArray(node.children) && node.children.length > 0;
5568
5570
 
@@ -5947,6 +5949,31 @@ function printMemberExpressionSimple(node, options, computed = false) {
5947
5949
  return '';
5948
5950
  }
5949
5951
 
5952
+ /**
5953
+ * Check whether an attribute value can expand into multiline content.
5954
+ * @param {AST.Expression | null | undefined} value - The attribute value node
5955
+ * @param {boolean} [is_nested_in_object=false] - Whether this value is nested within an object literal
5956
+ * @returns {boolean}
5957
+ */
5958
+ function is_attribute_value_breakable(value, is_nested_in_object = false) {
5959
+ if (!value) return false;
5960
+
5961
+ switch (value.type) {
5962
+ case 'ConditionalExpression':
5963
+ // Keep simple top-level ternary attributes inline when they fit.
5964
+ // We only force-break when a conditional is nested in an object literal value.
5965
+ return is_nested_in_object;
5966
+ case 'ObjectExpression':
5967
+ return value.properties.some(
5968
+ (property) =>
5969
+ property.type === 'Property' &&
5970
+ is_attribute_value_breakable(/** @type {AST.Expression} */ (property.value), true),
5971
+ );
5972
+ default:
5973
+ return false;
5974
+ }
5975
+ }
5976
+
5950
5977
  /**
5951
5978
  * Print a Ripple Element node
5952
5979
  * @param {AST.Element} element - The element node
@@ -6070,7 +6097,12 @@ function printElement(element, path, options, print) {
6070
6097
  parts.push(attrLineBreak);
6071
6098
  const attrDoc = print(attrPath);
6072
6099
  parts.push(attrDoc);
6073
- if (!hasBreakingAttribute && willBreak(attrDoc)) {
6100
+ const attr_node = /** @type {AST.Attribute | AST.SpreadAttribute} */ (attrPath.node);
6101
+ if (
6102
+ !hasBreakingAttribute &&
6103
+ (willBreak(attrDoc) ||
6104
+ (attr_node.type === 'Attribute' && is_attribute_value_breakable(attr_node.value)))
6105
+ ) {
6074
6106
  hasBreakingAttribute = true;
6075
6107
  }
6076
6108
  return parts;
package/src/index.test.js CHANGED
@@ -926,7 +926,7 @@ export component Test({ a, b }: Props) {}`;
926
926
  expect(result).toBeWithNewline(expected);
927
927
  });
928
928
 
929
- it('should prefer breaking attributes over breaking expression values (multiline object)', async () => {
929
+ it('should prefer breaking attributes over inline breakable object values', async () => {
930
930
  const input = `component App() {
931
931
  <div class={styles.item} data-active={state.active ? "true" : "false"} style={{ gridTemplateColumns: Icon ? "16px minmax(0, 1fr) auto" : "minmax(0, 1fr) auto" }}>
932
932
  {'content'}
@@ -936,21 +936,17 @@ export component Test({ a, b }: Props) {}`;
936
936
  <div
937
937
  class={styles.item}
938
938
  data-active={state.active ? 'true' : 'false'}
939
- style={{
940
- gridTemplateColumns: Icon
941
- ? '16px minmax(0, 1fr) auto'
942
- : 'minmax(0, 1fr) auto',
943
- }}
939
+ style={{ gridTemplateColumns: Icon ? '16px minmax(0, 1fr) auto' : 'minmax(0, 1fr) auto' }}
944
940
  >
945
941
  {'content'}
946
942
  </div>
947
943
  }`;
948
944
 
949
- const result = await format(input, { singleQuote: true, printWidth: 80 });
945
+ const result = await format(input, { singleQuote: true, printWidth: 200 });
950
946
  expect(result).toBeWithNewline(expected);
951
947
  });
952
948
 
953
- it('should prefer breaking attributes over breaking expression values (multiline object, bracketSameLine)', async () => {
949
+ it('should prefer breaking attributes over inline breakable object values (bracketSameLine)', async () => {
954
950
  const input = `component App() {
955
951
  <div class={styles.item} data-active={state.active ? "true" : "false"} style={{ gridTemplateColumns: Icon ? "16px minmax(0, 1fr) auto" : "minmax(0, 1fr) auto" }}>
956
952
  {'content'}
@@ -960,18 +956,14 @@ export component Test({ a, b }: Props) {}`;
960
956
  <div
961
957
  class={styles.item}
962
958
  data-active={state.active ? 'true' : 'false'}
963
- style={{
964
- gridTemplateColumns: Icon
965
- ? '16px minmax(0, 1fr) auto'
966
- : 'minmax(0, 1fr) auto',
967
- }}>
959
+ style={{ gridTemplateColumns: Icon ? '16px minmax(0, 1fr) auto' : 'minmax(0, 1fr) auto' }}>
968
960
  {'content'}
969
961
  </div>
970
962
  }`;
971
963
 
972
964
  const result = await format(input, {
973
965
  singleQuote: true,
974
- printWidth: 80,
966
+ printWidth: 200,
975
967
  bracketSameLine: true,
976
968
  });
977
969
  expect(result).toBeWithNewline(expected);
@@ -991,6 +983,18 @@ export component Test({ a, b }: Props) {}`;
991
983
  expect(result).toBeWithNewline(expected);
992
984
  });
993
985
 
986
+ it('should keep short top-level ternary attributes inline when they fit', async () => {
987
+ const input = `component App() {
988
+ <div class={selected === 0 ? "selected" : ""}>{\`div 1\`}</div>
989
+ }`;
990
+ const expected = `component App() {
991
+ <div class={selected === 0 ? 'selected' : ''}>{\`div 1\`}</div>
992
+ }`;
993
+
994
+ const result = await format(input, { singleQuote: true, printWidth: 100 });
995
+ expect(result).toBeWithNewline(expected);
996
+ });
997
+
994
998
  it('should not format function parameter spread', async () => {
995
999
  const expected = `component Two({ arg1, ...rest }) {}`;
996
1000
 
@@ -1935,6 +1939,40 @@ files = [...(files ?? []), ...dt.files];`;
1935
1939
  expect(result).toBeWithNewline(expected);
1936
1940
  });
1937
1941
 
1942
+ it('should preserve fragment shorthand in class methods', async () => {
1943
+ const input = `class Foo {
1944
+ bar() {
1945
+ return <>{"Hello"}</>;
1946
+ }
1947
+ }`;
1948
+
1949
+ const expected = `class Foo {
1950
+ bar() {
1951
+ return <>{'Hello'}</>;
1952
+ }
1953
+ }`;
1954
+
1955
+ const result = await format(input, { singleQuote: true, printWidth: 100 });
1956
+ expect(result).toBeWithNewline(expected);
1957
+ });
1958
+
1959
+ it('should preserve explicit tsx blocks in class methods', async () => {
1960
+ const input = `class Foo {
1961
+ bar() {
1962
+ return <tsx>{"Hello"}</tsx>;
1963
+ }
1964
+ }`;
1965
+
1966
+ const expected = `class Foo {
1967
+ bar() {
1968
+ return <tsx>{'Hello'}</tsx>;
1969
+ }
1970
+ }`;
1971
+
1972
+ const result = await format(input, { singleQuote: true, printWidth: 100 });
1973
+ expect(result).toBeWithNewline(expected);
1974
+ });
1975
+
1938
1976
  it('should format class with a literal component method', async () => {
1939
1977
  const input = `class TestClass {
1940
1978
  component 'something'() {