@ripple-ts/prettier-plugin 0.2.159 → 0.2.160
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 +2 -2
- package/src/index.js +120 -12
- package/src/index.test.js +115 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ripple-ts/prettier-plugin",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.160",
|
|
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.
|
|
28
|
+
"ripple": "0.2.160"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {},
|
|
31
31
|
"files": [
|
package/src/index.js
CHANGED
|
@@ -156,6 +156,79 @@ function iterateFunctionParametersPath(path, iteratee) {
|
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
// Operator precedence (higher number = higher precedence)
|
|
160
|
+
const PRECEDENCE = {
|
|
161
|
+
'||': 1,
|
|
162
|
+
'&&': 2,
|
|
163
|
+
'|': 3,
|
|
164
|
+
'^': 4,
|
|
165
|
+
'&': 5,
|
|
166
|
+
'==': 6,
|
|
167
|
+
'!=': 6,
|
|
168
|
+
'===': 6,
|
|
169
|
+
'!==': 6,
|
|
170
|
+
'<': 7,
|
|
171
|
+
'<=': 7,
|
|
172
|
+
'>': 7,
|
|
173
|
+
'>=': 7,
|
|
174
|
+
in: 7,
|
|
175
|
+
instanceof: 7,
|
|
176
|
+
'<<': 8,
|
|
177
|
+
'>>': 8,
|
|
178
|
+
'>>>': 8,
|
|
179
|
+
'+': 9,
|
|
180
|
+
'-': 9,
|
|
181
|
+
'*': 10,
|
|
182
|
+
'/': 10,
|
|
183
|
+
'%': 10,
|
|
184
|
+
'**': 11,
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
function getPrecedence(operator) {
|
|
188
|
+
return PRECEDENCE[operator] || 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Check if a BinaryExpression needs parentheses
|
|
192
|
+
function binaryExpressionNeedsParens(node, parent) {
|
|
193
|
+
if (!node.metadata?.parenthesized) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// If parent is not an operator context, don't preserve parens
|
|
198
|
+
if (
|
|
199
|
+
!parent ||
|
|
200
|
+
(parent.type !== 'BinaryExpression' &&
|
|
201
|
+
parent.type !== 'LogicalExpression' &&
|
|
202
|
+
parent.type !== 'UnaryExpression')
|
|
203
|
+
) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// If parent is UnaryExpression, it already handles the parentheses
|
|
208
|
+
if (parent.type === 'UnaryExpression') {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// For BinaryExpression/LogicalExpression parents, check precedence
|
|
213
|
+
if (parent.type === 'BinaryExpression' || parent.type === 'LogicalExpression') {
|
|
214
|
+
const nodePrecedence = getPrecedence(node.operator);
|
|
215
|
+
const parentPrecedence = getPrecedence(parent.operator);
|
|
216
|
+
|
|
217
|
+
// Need parens if:
|
|
218
|
+
// 1. Child has lower precedence than parent
|
|
219
|
+
// 2. Same precedence but different operators (for clarity)
|
|
220
|
+
// 3. Child is on the right side and precedence is equal (for left-associative operators)
|
|
221
|
+
if (nodePrecedence < parentPrecedence) {
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
if (nodePrecedence === parentPrecedence && node.operator !== parent.operator) {
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
|
|
159
232
|
function createSkip(characters) {
|
|
160
233
|
return (text, startIndex, options) => {
|
|
161
234
|
const backwards = Boolean(options && options.backwards);
|
|
@@ -1445,9 +1518,10 @@ function printRippleNode(node, path, options, print, args) {
|
|
|
1445
1518
|
parent.type === 'AssignmentExpression' ||
|
|
1446
1519
|
parent.type === 'AssignmentPattern');
|
|
1447
1520
|
|
|
1521
|
+
let result;
|
|
1448
1522
|
// Don't add indent if we're in a conditional test context
|
|
1449
1523
|
if (args?.isConditionalTest) {
|
|
1450
|
-
|
|
1524
|
+
result = group(
|
|
1451
1525
|
concat([
|
|
1452
1526
|
path.call((childPath) => print(childPath, { isConditionalTest: true }), 'left'),
|
|
1453
1527
|
' ',
|
|
@@ -1460,7 +1534,7 @@ function printRippleNode(node, path, options, print, args) {
|
|
|
1460
1534
|
);
|
|
1461
1535
|
} else if (shouldNotIndent) {
|
|
1462
1536
|
// In assignment context, don't add indent - parent will handle it
|
|
1463
|
-
|
|
1537
|
+
result = group(
|
|
1464
1538
|
concat([
|
|
1465
1539
|
path.call(print, 'left'),
|
|
1466
1540
|
' ',
|
|
@@ -1469,7 +1543,7 @@ function printRippleNode(node, path, options, print, args) {
|
|
|
1469
1543
|
]),
|
|
1470
1544
|
);
|
|
1471
1545
|
} else {
|
|
1472
|
-
|
|
1546
|
+
result = group(
|
|
1473
1547
|
concat([
|
|
1474
1548
|
path.call(print, 'left'),
|
|
1475
1549
|
' ',
|
|
@@ -1478,6 +1552,13 @@ function printRippleNode(node, path, options, print, args) {
|
|
|
1478
1552
|
]),
|
|
1479
1553
|
);
|
|
1480
1554
|
}
|
|
1555
|
+
|
|
1556
|
+
// Wrap in parentheses only if semantically necessary
|
|
1557
|
+
if (binaryExpressionNeedsParens(node, parent)) {
|
|
1558
|
+
result = concat(['(', result, ')']);
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
nodeContent = result;
|
|
1481
1562
|
break;
|
|
1482
1563
|
}
|
|
1483
1564
|
case 'LogicalExpression':
|
|
@@ -2239,8 +2320,13 @@ function printArrowFunction(node, path, options, print) {
|
|
|
2239
2320
|
parts.push(path.call(print, 'body'));
|
|
2240
2321
|
} else {
|
|
2241
2322
|
// For expression bodies, check if we need to wrap in parens
|
|
2242
|
-
// Wrap ObjectExpression
|
|
2243
|
-
|
|
2323
|
+
// Wrap ObjectExpression, AssignmentExpression, and SequenceExpression in parens
|
|
2324
|
+
// to avoid ambiguity with block statements or to clarify intent
|
|
2325
|
+
if (
|
|
2326
|
+
node.body.type === 'ObjectExpression' ||
|
|
2327
|
+
node.body.type === 'AssignmentExpression' ||
|
|
2328
|
+
node.body.type === 'SequenceExpression'
|
|
2329
|
+
) {
|
|
2244
2330
|
parts.push('(');
|
|
2245
2331
|
parts.push(path.call(print, 'body'));
|
|
2246
2332
|
parts.push(')');
|
|
@@ -3064,7 +3150,13 @@ function printMemberExpression(node, path, options, print) {
|
|
|
3064
3150
|
|
|
3065
3151
|
let result;
|
|
3066
3152
|
if (node.computed) {
|
|
3067
|
-
|
|
3153
|
+
// Check if the MemberExpression itself is tracked to add @ symbol
|
|
3154
|
+
const trackedPrefix = node.tracked ? '@' : '';
|
|
3155
|
+
const openBracket = node.optional
|
|
3156
|
+
? '?.' + trackedPrefix + '['
|
|
3157
|
+
: trackedPrefix
|
|
3158
|
+
? '.' + trackedPrefix + '['
|
|
3159
|
+
: '[';
|
|
3068
3160
|
result = concat([objectPart, openBracket, propertyPart, ']']);
|
|
3069
3161
|
} else {
|
|
3070
3162
|
const separator = node.optional ? '?.' : '.';
|
|
@@ -3095,9 +3187,21 @@ function printUnaryExpression(node, path, options, print) {
|
|
|
3095
3187
|
if (needsSpace) {
|
|
3096
3188
|
parts.push(' ');
|
|
3097
3189
|
}
|
|
3098
|
-
|
|
3190
|
+
const argumentDoc = path.call(print, 'argument');
|
|
3191
|
+
// Preserve parentheses around the argument when present
|
|
3192
|
+
if (node.argument.metadata?.parenthesized) {
|
|
3193
|
+
parts.push('(', argumentDoc, ')');
|
|
3194
|
+
} else {
|
|
3195
|
+
parts.push(argumentDoc);
|
|
3196
|
+
}
|
|
3099
3197
|
} else {
|
|
3100
|
-
|
|
3198
|
+
const argumentDoc = path.call(print, 'argument');
|
|
3199
|
+
// Preserve parentheses around the argument when present
|
|
3200
|
+
if (node.argument.metadata?.parenthesized) {
|
|
3201
|
+
parts.push('(', argumentDoc, ')');
|
|
3202
|
+
} else {
|
|
3203
|
+
parts.push(argumentDoc);
|
|
3204
|
+
}
|
|
3101
3205
|
parts.push(node.operator);
|
|
3102
3206
|
}
|
|
3103
3207
|
|
|
@@ -4637,17 +4741,21 @@ function printJSXMemberExpression(node) {
|
|
|
4637
4741
|
|
|
4638
4742
|
function printMemberExpressionSimple(node, options, computed = false) {
|
|
4639
4743
|
if (node.type === 'Identifier') {
|
|
4640
|
-
|
|
4744
|
+
// When computed is true, it means we're inside brackets and tracked is already handled by .@[ or [
|
|
4745
|
+
// So we should NOT add @ prefix in that case
|
|
4746
|
+
return (computed ? '' : node.tracked ? '@' : '') + node.name;
|
|
4641
4747
|
}
|
|
4642
4748
|
|
|
4643
4749
|
if (node.type === 'MemberExpression') {
|
|
4644
4750
|
const obj = printMemberExpressionSimple(node.object, options);
|
|
4751
|
+
// For properties, we add the .@ or . prefix, and then pass true to indicate
|
|
4752
|
+
// that we're in a context where tracked has been handled
|
|
4645
4753
|
const prop = node.computed
|
|
4646
4754
|
? (node.property.tracked ? '.@[' : '[') +
|
|
4647
|
-
printMemberExpressionSimple(node.property, options,
|
|
4755
|
+
printMemberExpressionSimple(node.property, options, true) +
|
|
4648
4756
|
']'
|
|
4649
4757
|
: (node.property.tracked ? '.@' : '.') +
|
|
4650
|
-
printMemberExpressionSimple(node.property, options,
|
|
4758
|
+
printMemberExpressionSimple(node.property, options, true);
|
|
4651
4759
|
return obj + prop;
|
|
4652
4760
|
}
|
|
4653
4761
|
|
|
@@ -4658,7 +4766,7 @@ function printMemberExpressionSimple(node, options, computed = false) {
|
|
|
4658
4766
|
}
|
|
4659
4767
|
|
|
4660
4768
|
function printElement(node, path, options, print) {
|
|
4661
|
-
const tagName =
|
|
4769
|
+
const tagName = printMemberExpressionSimple(node.id, options);
|
|
4662
4770
|
|
|
4663
4771
|
const elementLeadingComments = getElementLeadingComments(node);
|
|
4664
4772
|
const metadataCommentParts =
|
package/src/index.test.js
CHANGED
|
@@ -902,6 +902,44 @@ export component Test({ a, b }: Props) {}`;
|
|
|
902
902
|
expect(result).toBeWithNewline(expected);
|
|
903
903
|
});
|
|
904
904
|
|
|
905
|
+
it('should not strip @ from dynamic self-closing components', async () => {
|
|
906
|
+
const expected = `component App() {
|
|
907
|
+
<@tracked_object.@tracked_basic />
|
|
908
|
+
}`;
|
|
909
|
+
|
|
910
|
+
const result = await format(expected, { singleQuote: true, printWidth: 100 });
|
|
911
|
+
expect(result).toBeWithNewline(expected);
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
it('should keep @ on dynamic object member array expressions', async () => {
|
|
915
|
+
const expected = `component App() {
|
|
916
|
+
const obj = {
|
|
917
|
+
[0]: track(0),
|
|
918
|
+
};
|
|
919
|
+
|
|
920
|
+
<div>{obj.@[0]}</div>
|
|
921
|
+
|
|
922
|
+
<button
|
|
923
|
+
onClick={() => {
|
|
924
|
+
obj.@[0]++;
|
|
925
|
+
}}
|
|
926
|
+
>
|
|
927
|
+
{'Increment'}
|
|
928
|
+
</button>
|
|
929
|
+
|
|
930
|
+
<button
|
|
931
|
+
onClick={() => {
|
|
932
|
+
obj.@[0] += 1;
|
|
933
|
+
}}
|
|
934
|
+
>
|
|
935
|
+
{'Increment'}
|
|
936
|
+
</button>
|
|
937
|
+
}`;
|
|
938
|
+
|
|
939
|
+
const result = await format(expected, { singleQuote: true, printWidth: 100 });
|
|
940
|
+
expect(result).toBeWithNewline(expected);
|
|
941
|
+
});
|
|
942
|
+
|
|
905
943
|
it('keeps a new line between comments above and code if one is present', async () => {
|
|
906
944
|
const expected = `// comment
|
|
907
945
|
|
|
@@ -1105,6 +1143,30 @@ const set = new #Set([1, 2, 3]);`;
|
|
|
1105
1143
|
expect(result).toBeWithNewline(expected);
|
|
1106
1144
|
});
|
|
1107
1145
|
|
|
1146
|
+
it('should keep TrackedSet parents with short syntax and no args intact', async () => {
|
|
1147
|
+
const expected = `component SetTest() {
|
|
1148
|
+
let items = new #Set();
|
|
1149
|
+
|
|
1150
|
+
<button onClick={() => items.add(1)}>{'add'}</button>
|
|
1151
|
+
<pre>{items.size}</pre>
|
|
1152
|
+
}`;
|
|
1153
|
+
|
|
1154
|
+
const result = await format(expected, { singleQuote: true, printWidth: 100 });
|
|
1155
|
+
expect(result).toBeWithNewline(expected);
|
|
1156
|
+
});
|
|
1157
|
+
|
|
1158
|
+
it('should keep TrackedMap parents with short syntax and no args intact', async () => {
|
|
1159
|
+
const expected = `component MapTest() {
|
|
1160
|
+
let items = new #Map();
|
|
1161
|
+
|
|
1162
|
+
<button onClick={() => items.set('key', 1)}>{'add'}</button>
|
|
1163
|
+
<pre>{items.size}</pre>
|
|
1164
|
+
}`;
|
|
1165
|
+
|
|
1166
|
+
const result = await format(expected, { singleQuote: true, printWidth: 100 });
|
|
1167
|
+
expect(result).toBeWithNewline(expected);
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1108
1170
|
it('should not remove blank lines between components and types if provided', async () => {
|
|
1109
1171
|
const expected = `export component App() {
|
|
1110
1172
|
console.log('test');
|
|
@@ -1496,6 +1558,59 @@ const program =
|
|
|
1496
1558
|
const result = await format(input, { singleQuote: true, printWidth: 30 });
|
|
1497
1559
|
expect(result).toBeWithNewline(expected);
|
|
1498
1560
|
});
|
|
1561
|
+
|
|
1562
|
+
it('should keep blank lines between commented out block and markup', async () => {
|
|
1563
|
+
const expected = `function CounterWrapper(props) {
|
|
1564
|
+
const more = {
|
|
1565
|
+
double: track(() => props.count * 2),
|
|
1566
|
+
another: track(0),
|
|
1567
|
+
onemore: 100,
|
|
1568
|
+
};
|
|
1569
|
+
|
|
1570
|
+
// if (props.@count > 1) {
|
|
1571
|
+
// delete more.another;
|
|
1572
|
+
// }
|
|
1573
|
+
|
|
1574
|
+
<div>
|
|
1575
|
+
<Counter {...props} {...more} />
|
|
1576
|
+
</div>
|
|
1577
|
+
}`;
|
|
1578
|
+
|
|
1579
|
+
const result = await format(expected, { singleQuote: true, printWidth: 100 });
|
|
1580
|
+
expect(result).toBeWithNewline(expected);
|
|
1581
|
+
});
|
|
1582
|
+
|
|
1583
|
+
it('should keep parens around negating key in object expression', async () => {
|
|
1584
|
+
const input = `effect(() => {
|
|
1585
|
+
props.count;
|
|
1586
|
+
if (props.count > 1 && 'another' in more) {
|
|
1587
|
+
untrack(() => delete more.another);
|
|
1588
|
+
} else if (props.count > 2 && !('another' in more)) {
|
|
1589
|
+
untrack(() => more.another = 0);
|
|
1590
|
+
}
|
|
1591
|
+
untrack(() => console.log(more));
|
|
1592
|
+
});`;
|
|
1593
|
+
|
|
1594
|
+
const expected = `effect(() => {
|
|
1595
|
+
props.count;
|
|
1596
|
+
if (props.count > 1 && 'another' in more) {
|
|
1597
|
+
untrack(() => delete more.another);
|
|
1598
|
+
} else if (props.count > 2 && !('another' in more)) {
|
|
1599
|
+
untrack(() => (more.another = 0));
|
|
1600
|
+
}
|
|
1601
|
+
untrack(() => console.log(more));
|
|
1602
|
+
});`;
|
|
1603
|
+
|
|
1604
|
+
const result = await format(input, { singleQuote: true, printWidth: 100 });
|
|
1605
|
+
expect(result).toBeWithNewline(expected);
|
|
1606
|
+
});
|
|
1607
|
+
|
|
1608
|
+
it('should keep parents in math subtraction and multiplication', async () => {
|
|
1609
|
+
const expected = `let offset = track(() => (@page - 1) * @limit);`;
|
|
1610
|
+
|
|
1611
|
+
const result = await format(expected, { singleQuote: true, printWidth: 100 });
|
|
1612
|
+
expect(result).toBeWithNewline(expected);
|
|
1613
|
+
});
|
|
1499
1614
|
});
|
|
1500
1615
|
|
|
1501
1616
|
describe('edge cases', () => {
|