@tsrx/prettier-plugin 0.3.93 → 0.3.95
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 +73 -4
- package/src/index.test.js +75 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tsrx/prettier-plugin",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.95",
|
|
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.4"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@tsrx/core": "0.1.
|
|
30
|
+
"@tsrx/core": "0.1.38"
|
|
31
31
|
},
|
|
32
32
|
"files": [
|
|
33
33
|
"src/"
|
package/src/index.js
CHANGED
|
@@ -37,7 +37,7 @@ const {
|
|
|
37
37
|
lineSuffix,
|
|
38
38
|
align,
|
|
39
39
|
} = builders;
|
|
40
|
-
const { replaceEndOfLine, willBreak } = utils;
|
|
40
|
+
const { replaceEndOfLine, stripTrailingHardline, willBreak } = utils;
|
|
41
41
|
|
|
42
42
|
/** @type {import('prettier').Plugin['languages']} */
|
|
43
43
|
export const languages = [
|
|
@@ -121,14 +121,38 @@ export const printers = {
|
|
|
121
121
|
// Return the formatted CSS
|
|
122
122
|
// Note: printElement will wrap this in indent(), so we don't add indent here
|
|
123
123
|
return body;
|
|
124
|
-
} catch
|
|
125
|
-
//
|
|
126
|
-
|
|
124
|
+
} catch {
|
|
125
|
+
// A stylesheet that doesn't parse (e.g. mid-edit code) is an expected
|
|
126
|
+
// state, not an error: keep it verbatim and stay quiet.
|
|
127
127
|
return node.source;
|
|
128
128
|
}
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
// Raw-text `<script>` bodies: the parser mirrors the element's `content` as
|
|
133
|
+
// a single JSXText child. Format it with Prettier's TypeScript parser (a
|
|
134
|
+
// superset of JS, so plain bodies format identically) the same way <style>
|
|
135
|
+
// bodies are formatted as CSS above.
|
|
136
|
+
if (node.type === 'JSXText') {
|
|
137
|
+
const parent = /** @type {AST.TSRXJSXElement | null} */ (path.getParentNode());
|
|
138
|
+
if (isRawScriptElement(parent)) {
|
|
139
|
+
return async (textToDoc) => {
|
|
140
|
+
try {
|
|
141
|
+
const body = await textToDoc(node.value, {
|
|
142
|
+
parser: 'typescript',
|
|
143
|
+
});
|
|
144
|
+
// Drop the program's trailing hardline; printElement places the
|
|
145
|
+
// closing tag on its own line already.
|
|
146
|
+
return stripTrailingHardline(body);
|
|
147
|
+
} catch {
|
|
148
|
+
// A body that doesn't parse (e.g. mid-edit code) is an expected
|
|
149
|
+
// state, not an error: keep it verbatim and stay quiet.
|
|
150
|
+
return replaceEndOfLine(node.value);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
132
156
|
return null;
|
|
133
157
|
},
|
|
134
158
|
/**
|
|
@@ -161,6 +185,23 @@ export const printers = {
|
|
|
161
185
|
},
|
|
162
186
|
};
|
|
163
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Raw-text `<script>` element: the parser stores the verbatim JS/TS body on
|
|
190
|
+
* `content` and mirrors it as a single JSXText child. Checking the tag name
|
|
191
|
+
* alongside `content` matches the other raw-aware consumers (the Ripple
|
|
192
|
+
* transforms, the compiler's script regions).
|
|
193
|
+
* @param {AST.TSRXJSXElement | AST.JSXStyleElement | null | undefined} node
|
|
194
|
+
* @returns {boolean}
|
|
195
|
+
*/
|
|
196
|
+
function isRawScriptElement(node) {
|
|
197
|
+
return (
|
|
198
|
+
node?.type === 'JSXElement' &&
|
|
199
|
+
node.openingElement?.name?.type === 'JSXIdentifier' &&
|
|
200
|
+
node.openingElement.name.name === 'script' &&
|
|
201
|
+
typeof node.content === 'string'
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
164
205
|
/**
|
|
165
206
|
* Format a string literal according to Prettier options
|
|
166
207
|
* @param {string | number | bigint | boolean | RegExp | null | undefined} value - value to format
|
|
@@ -362,6 +403,16 @@ function binaryExpressionNeedsParens(node, parent) {
|
|
|
362
403
|
if (nodePrecedence === parentPrecedence && node.operator !== parent.operator) {
|
|
363
404
|
return true;
|
|
364
405
|
}
|
|
406
|
+
if (nodePrecedence === parentPrecedence) {
|
|
407
|
+
// Same precedence, same operator: dropping the parens regroups a
|
|
408
|
+
// left-associative chain (`a - (b - c)` !== `a - b - c`; even `+` is
|
|
409
|
+
// non-associative once strings are involved), so the RIGHT operand
|
|
410
|
+
// keeps its parens. `**` is right-associative — there it's the LEFT
|
|
411
|
+
// operand that must keep them.
|
|
412
|
+
if (parent.operator === '**' ? parent.left === node : parent.right === node) {
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
365
416
|
}
|
|
366
417
|
|
|
367
418
|
return false;
|
|
@@ -5861,6 +5912,24 @@ function printJSXElement(node, path, options, print) {
|
|
|
5861
5912
|
{ shouldBreak: shouldForceBreak },
|
|
5862
5913
|
);
|
|
5863
5914
|
|
|
5915
|
+
// Raw-text `<script>` element: the body lives on `node.content`, mirrored as a
|
|
5916
|
+
// single JSXText child (see the parser's `#parseScriptElement`). Print that
|
|
5917
|
+
// child — embed() formats it as TypeScript — in a block layout, bypassing the
|
|
5918
|
+
// generic children path so the body is never whitespace-merged as markup text.
|
|
5919
|
+
if (isRawScriptElement(node)) {
|
|
5920
|
+
if (!hasChildren) {
|
|
5921
|
+
return [openingTag, '</', tagName, '>'];
|
|
5922
|
+
}
|
|
5923
|
+
return group([
|
|
5924
|
+
openingTag,
|
|
5925
|
+
indent([hardline, path.call(print, 'children', 0)]),
|
|
5926
|
+
hardline,
|
|
5927
|
+
'</',
|
|
5928
|
+
tagName,
|
|
5929
|
+
'>',
|
|
5930
|
+
]);
|
|
5931
|
+
}
|
|
5932
|
+
|
|
5864
5933
|
// Comments before `</tag>` and the comments of a comment-only element.
|
|
5865
5934
|
const { closingCommentDocs, innerCommentDocs } = collectElementBodyCommentDocs(
|
|
5866
5935
|
/** @type {AST.TSRXJSXElement} */ (node),
|
package/src/index.test.js
CHANGED
|
@@ -2285,6 +2285,42 @@ const program =
|
|
|
2285
2285
|
expect(result).toBeWithNewline(expected);
|
|
2286
2286
|
});
|
|
2287
2287
|
|
|
2288
|
+
it('should keep parens around the right operand of a same-operator subtraction', async () => {
|
|
2289
|
+
const expected = `const d = a - (b - c);`;
|
|
2290
|
+
|
|
2291
|
+
const result = await format(expected, { singleQuote: true, printWidth: 100 });
|
|
2292
|
+
expect(result).toBeWithNewline(expected);
|
|
2293
|
+
});
|
|
2294
|
+
|
|
2295
|
+
it('should keep parens around the right operand of a same-operator division', async () => {
|
|
2296
|
+
const expected = `const d = a / (b / c);`;
|
|
2297
|
+
|
|
2298
|
+
const result = await format(expected, { singleQuote: true, printWidth: 100 });
|
|
2299
|
+
expect(result).toBeWithNewline(expected);
|
|
2300
|
+
});
|
|
2301
|
+
|
|
2302
|
+
it('should keep parens around a right-side addition under string concatenation', async () => {
|
|
2303
|
+
const expected = `const s = 'x' + (n + 1);`;
|
|
2304
|
+
|
|
2305
|
+
const result = await format(expected, { singleQuote: true, printWidth: 100 });
|
|
2306
|
+
expect(result).toBeWithNewline(expected);
|
|
2307
|
+
});
|
|
2308
|
+
|
|
2309
|
+
it('should drop redundant parens around the left operand of a same-operator addition', async () => {
|
|
2310
|
+
const input = `const s = (a + b) + c;`;
|
|
2311
|
+
const expected = `const s = a + b + c;`;
|
|
2312
|
+
|
|
2313
|
+
const result = await format(input, { singleQuote: true, printWidth: 100 });
|
|
2314
|
+
expect(result).toBeWithNewline(expected);
|
|
2315
|
+
});
|
|
2316
|
+
|
|
2317
|
+
it('should keep parens around the left operand of exponentiation', async () => {
|
|
2318
|
+
const expected = `const p = (a ** b) ** c;`;
|
|
2319
|
+
|
|
2320
|
+
const result = await format(expected, { singleQuote: true, printWidth: 100 });
|
|
2321
|
+
expect(result).toBeWithNewline(expected);
|
|
2322
|
+
});
|
|
2323
|
+
|
|
2288
2324
|
it('should have parents around low-precedence logical expression', async () => {
|
|
2289
2325
|
const input = `files = [...files ?? [], ...dt.files];
|
|
2290
2326
|
files = [...(files ?? []), ...dt.files];`;
|
|
@@ -5583,13 +5619,49 @@ render(App);`;
|
|
|
5583
5619
|
expect(result).toBeWithNewline(expected);
|
|
5584
5620
|
});
|
|
5585
5621
|
|
|
5586
|
-
it('should
|
|
5587
|
-
const expected = `<script>
|
|
5622
|
+
it('should format <script> bodies as JS in a block layout', async () => {
|
|
5623
|
+
const expected = `<script>
|
|
5624
|
+
const i = 2;
|
|
5625
|
+
</script>`;
|
|
5588
5626
|
|
|
5589
|
-
const result = await format(
|
|
5627
|
+
const result = await format(`<script>const i = 2;</script>`, {
|
|
5628
|
+
singleQuote: true,
|
|
5629
|
+
printWidth: 100,
|
|
5630
|
+
});
|
|
5631
|
+
expect(result).toBeWithNewline(expected);
|
|
5632
|
+
});
|
|
5633
|
+
|
|
5634
|
+
it('should format a TypeScript <script> body with prettier options applied', async () => {
|
|
5635
|
+
const expected = `<script type="text/typescript">
|
|
5636
|
+
const n: number = 1 < 2 ? 3 : 4;
|
|
5637
|
+
if (n < 2) {
|
|
5638
|
+
go('now');
|
|
5639
|
+
}
|
|
5640
|
+
</script>`;
|
|
5641
|
+
|
|
5642
|
+
const source = `<script type="text/typescript">const n:number=1<2?3:4;
|
|
5643
|
+
if(n<2){go("now")}</script>`;
|
|
5644
|
+
|
|
5645
|
+
const result = await format(source, { singleQuote: true, printWidth: 100 });
|
|
5590
5646
|
expect(result).toBeWithNewline(expected);
|
|
5591
5647
|
});
|
|
5592
5648
|
|
|
5649
|
+
it('should be idempotent when reformatting a formatted <script> body', async () => {
|
|
5650
|
+
const once = await format(`<script>const i = 2;</script>`, {
|
|
5651
|
+
singleQuote: true,
|
|
5652
|
+
printWidth: 100,
|
|
5653
|
+
});
|
|
5654
|
+
const twice = await format(once, { singleQuote: true, printWidth: 100 });
|
|
5655
|
+
expect(twice).toBe(once);
|
|
5656
|
+
});
|
|
5657
|
+
|
|
5658
|
+
it('should keep an unparseable <script> body verbatim', async () => {
|
|
5659
|
+
const expected = `<script>const broken = ;</script>`;
|
|
5660
|
+
|
|
5661
|
+
const result = await format(expected, { singleQuote: true, printWidth: 100 });
|
|
5662
|
+
expect(result).toContain('const broken = ;');
|
|
5663
|
+
});
|
|
5664
|
+
|
|
5593
5665
|
it('should preserve the blank line between a function and text literal sibling inside element', async () => {
|
|
5594
5666
|
const expected = `function Something({ children }) {
|
|
5595
5667
|
const test = 'yo';
|