@tsrx/prettier-plugin 0.3.59 → 0.3.61
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 +37 -4
- package/src/index.test.js +168 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tsrx/prettier-plugin",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.61",
|
|
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.
|
|
30
|
+
"@tsrx/core": "0.1.11"
|
|
31
31
|
},
|
|
32
32
|
"files": [
|
|
33
33
|
"src/"
|
package/src/index.js
CHANGED
|
@@ -36,7 +36,7 @@ const {
|
|
|
36
36
|
indentIfBreak,
|
|
37
37
|
lineSuffix,
|
|
38
38
|
} = builders;
|
|
39
|
-
const { willBreak } = utils;
|
|
39
|
+
const { replaceEndOfLine, willBreak } = utils;
|
|
40
40
|
|
|
41
41
|
/** @type {import('prettier').Plugin['languages']} */
|
|
42
42
|
export const languages = [
|
|
@@ -2236,7 +2236,7 @@ function printRippleNode(node, path, options, print, args) {
|
|
|
2236
2236
|
|
|
2237
2237
|
case 'Text': {
|
|
2238
2238
|
if (typeof node.raw === 'string') {
|
|
2239
|
-
nodeContent = node.raw;
|
|
2239
|
+
nodeContent = printRawText(node.raw);
|
|
2240
2240
|
break;
|
|
2241
2241
|
}
|
|
2242
2242
|
|
|
@@ -5501,6 +5501,27 @@ function printTSIndexedAccessType(node, path, options, print) {
|
|
|
5501
5501
|
return [path.call(print, 'objectType'), '[', path.call(print, 'indexType'), ']'];
|
|
5502
5502
|
}
|
|
5503
5503
|
|
|
5504
|
+
/**
|
|
5505
|
+
* Print direct TSRX text so it can wrap like JSX text when an element body breaks.
|
|
5506
|
+
* @param {string} raw
|
|
5507
|
+
* @returns {Doc}
|
|
5508
|
+
*/
|
|
5509
|
+
function printRawText(raw) {
|
|
5510
|
+
const text = raw.trim().replace(/(?:\r\n|\r|\n)[^\S\r\n]+/gu, ' ');
|
|
5511
|
+
if (!text) {
|
|
5512
|
+
return '';
|
|
5513
|
+
}
|
|
5514
|
+
|
|
5515
|
+
return fill(
|
|
5516
|
+
text
|
|
5517
|
+
.split(/([^\S\r\n]+)/u)
|
|
5518
|
+
.filter(Boolean)
|
|
5519
|
+
.map((part) => {
|
|
5520
|
+
return /^[^\S\r\n]+$/u.test(part) ? line : replaceEndOfLine(part);
|
|
5521
|
+
}),
|
|
5522
|
+
);
|
|
5523
|
+
}
|
|
5524
|
+
|
|
5504
5525
|
/**
|
|
5505
5526
|
* @param {AST.Node} parentNode
|
|
5506
5527
|
* @param {AST.Node} firstChild
|
|
@@ -6270,6 +6291,8 @@ function printElement(element, path, options, print) {
|
|
|
6270
6291
|
}, 'attributes')
|
|
6271
6292
|
: [];
|
|
6272
6293
|
const shouldForceBreak = hasOpeningTagComments || hasBreakingAttribute;
|
|
6294
|
+
const openingTagAlwaysBreaks =
|
|
6295
|
+
(hasAttributes && options.singleAttributePerLine) || shouldForceBreak;
|
|
6273
6296
|
const openingTag = group([
|
|
6274
6297
|
'<',
|
|
6275
6298
|
tagName,
|
|
@@ -6551,9 +6574,19 @@ function printElement(element, path, options, print) {
|
|
|
6551
6574
|
const isNonSelfClosingElement =
|
|
6552
6575
|
firstChild && firstChild.type === 'Element' && !firstChild.selfClosing;
|
|
6553
6576
|
const isElementChild = firstChild && firstChild.type === 'Element';
|
|
6577
|
+
const isRawTextChild =
|
|
6578
|
+
firstChild && firstChild.type === 'Text' && typeof firstChild.raw === 'string';
|
|
6554
6579
|
|
|
6555
|
-
if (
|
|
6556
|
-
|
|
6580
|
+
if (
|
|
6581
|
+
(typeof child === 'string' || isRawTextChild) &&
|
|
6582
|
+
shouldInlineSingleChild(node, firstChild, child)
|
|
6583
|
+
) {
|
|
6584
|
+
elementOutput = openingTagAlwaysBreaks
|
|
6585
|
+
? [openingTag, indent([hardline, child]), hardline, closingTag]
|
|
6586
|
+
: conditionalGroup([
|
|
6587
|
+
group([openingTag, child, closingTag]),
|
|
6588
|
+
[openingTag, indent([hardline, child]), hardline, closingTag],
|
|
6589
|
+
]);
|
|
6557
6590
|
} else if (
|
|
6558
6591
|
child &&
|
|
6559
6592
|
typeof child === 'object' &&
|
package/src/index.test.js
CHANGED
|
@@ -4,6 +4,14 @@ import { fileURLToPath } from 'url';
|
|
|
4
4
|
import { dirname, join } from 'path';
|
|
5
5
|
import { languages } from './index.js';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {typeof prettier & {
|
|
9
|
+
* __debug: {
|
|
10
|
+
* printToDoc: (code: string, options: import('prettier').Options) => Promise<import('prettier').Doc>;
|
|
11
|
+
* };
|
|
12
|
+
* }} PrettierWithDebug
|
|
13
|
+
*/
|
|
14
|
+
|
|
7
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
16
|
const __dirname = dirname(__filename);
|
|
9
17
|
|
|
@@ -62,6 +70,41 @@ describe('prettier-plugin', () => {
|
|
|
62
70
|
});
|
|
63
71
|
};
|
|
64
72
|
|
|
73
|
+
/**
|
|
74
|
+
* @param {string} code
|
|
75
|
+
* @param {import('prettier').Options} [options]
|
|
76
|
+
*/
|
|
77
|
+
const printToDoc = async (code, options = {}) => {
|
|
78
|
+
const prettierWithDebug = /** @type {PrettierWithDebug} */ (prettier);
|
|
79
|
+
return await prettierWithDebug.__debug.printToDoc(code, {
|
|
80
|
+
parser: 'ripple',
|
|
81
|
+
plugins: [join(__dirname, 'index.js')],
|
|
82
|
+
...options,
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {unknown} doc
|
|
88
|
+
* @param {(doc: Record<string, unknown>) => boolean} predicate
|
|
89
|
+
* @returns {boolean}
|
|
90
|
+
*/
|
|
91
|
+
const docContains = (doc, predicate) => {
|
|
92
|
+
if (!doc || typeof doc !== 'object') {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (Array.isArray(doc)) {
|
|
97
|
+
return doc.some((part) => docContains(part, predicate));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const objectDoc = /** @type {Record<string, unknown>} */ (doc);
|
|
101
|
+
if (predicate(objectDoc)) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return Object.values(objectDoc).some((value) => docContains(value, predicate));
|
|
106
|
+
};
|
|
107
|
+
|
|
65
108
|
/**
|
|
66
109
|
* @param {string} code
|
|
67
110
|
* @param {Partial<import('prettier').CursorOptions>} options
|
|
@@ -1055,6 +1098,23 @@ export component Test({ a, b }: Props) {}`;
|
|
|
1055
1098
|
expect(result).toBeWithNewline(expected);
|
|
1056
1099
|
});
|
|
1057
1100
|
|
|
1101
|
+
it('should not force attribute-less elements to break with singleAttributePerLine', async () => {
|
|
1102
|
+
const input = `component One() {
|
|
1103
|
+
<div>"Hello"</div>
|
|
1104
|
+
}`;
|
|
1105
|
+
|
|
1106
|
+
const expected = `component One() {
|
|
1107
|
+
<div>"Hello"</div>
|
|
1108
|
+
}`;
|
|
1109
|
+
|
|
1110
|
+
const result = await format(input, {
|
|
1111
|
+
singleQuote: true,
|
|
1112
|
+
printWidth: 100,
|
|
1113
|
+
singleAttributePerLine: true,
|
|
1114
|
+
});
|
|
1115
|
+
expect(result).toBeWithNewline(expected);
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1058
1118
|
it('should respect singleAttributePerLine set to false setting', async () => {
|
|
1059
1119
|
const input = `component One() {
|
|
1060
1120
|
<button
|
|
@@ -3239,6 +3299,114 @@ const items = [] as unknown[];`;
|
|
|
3239
3299
|
expect(result).toBeWithNewline(expected);
|
|
3240
3300
|
});
|
|
3241
3301
|
|
|
3302
|
+
it('should preserve literal newlines in direct double-quoted text children', async () => {
|
|
3303
|
+
const input = `component App() {
|
|
3304
|
+
<pre>"first
|
|
3305
|
+
second"</pre>
|
|
3306
|
+
}`;
|
|
3307
|
+
|
|
3308
|
+
const expected = `component App() {
|
|
3309
|
+
<pre>"first
|
|
3310
|
+
second"</pre>
|
|
3311
|
+
}`;
|
|
3312
|
+
|
|
3313
|
+
const result = await format(input);
|
|
3314
|
+
expect(result).toBeWithNewline(expected);
|
|
3315
|
+
});
|
|
3316
|
+
|
|
3317
|
+
it('should wrap direct double-quoted text children idempotently', async () => {
|
|
3318
|
+
const input = `component App() {
|
|
3319
|
+
<p class="lede">
|
|
3320
|
+
"Set up TSRX with React, Preact, Solid, Vue, or Ripple and then wire in the editor tooling that makes "
|
|
3321
|
+
<code class="inline-code">".tsrx"</code>
|
|
3322
|
+
" files feel native in the rest of your repo."
|
|
3323
|
+
</p>
|
|
3324
|
+
}`;
|
|
3325
|
+
|
|
3326
|
+
const expected = `component App() {
|
|
3327
|
+
<p class="lede">
|
|
3328
|
+
"Set up TSRX with React, Preact, Solid, Vue, or Ripple and then wire in the editor tooling that
|
|
3329
|
+
makes "
|
|
3330
|
+
<code class="inline-code">".tsrx"</code>
|
|
3331
|
+
" files feel native in the rest of your repo."
|
|
3332
|
+
</p>
|
|
3333
|
+
}`;
|
|
3334
|
+
|
|
3335
|
+
const result = await format(input, { printWidth: 100 });
|
|
3336
|
+
const secondResult = await format(result, { printWidth: 100 });
|
|
3337
|
+
expect(result).toBeWithNewline(expected);
|
|
3338
|
+
expect(secondResult).toBeWithNewline(expected);
|
|
3339
|
+
});
|
|
3340
|
+
|
|
3341
|
+
it('should break long direct text children after inline attributes', async () => {
|
|
3342
|
+
const input = `component App() {
|
|
3343
|
+
<span
|
|
3344
|
+
class={styles.notificationMessage}
|
|
3345
|
+
>"The report is ready. Review the summary before sharing it with the team."</span>
|
|
3346
|
+
}`;
|
|
3347
|
+
|
|
3348
|
+
const expected = `component App() {
|
|
3349
|
+
<span class={styles.notificationMessage}>
|
|
3350
|
+
"The report is ready. Review the summary before sharing it with the team."
|
|
3351
|
+
</span>
|
|
3352
|
+
}`;
|
|
3353
|
+
|
|
3354
|
+
const result = await format(input, { printWidth: 80 });
|
|
3355
|
+
expect(result).toBeWithNewline(expected);
|
|
3356
|
+
});
|
|
3357
|
+
|
|
3358
|
+
it('should wrap long direct text children when elements break', async () => {
|
|
3359
|
+
const input = `component App() {
|
|
3360
|
+
<span class={styles.notificationMessage}>"The report is ready. Review the summary before sharing it with the team."</span>
|
|
3361
|
+
}`;
|
|
3362
|
+
|
|
3363
|
+
const expectedPrintWidth70 = `component App() {
|
|
3364
|
+
<span class={styles.notificationMessage}>
|
|
3365
|
+
"The report is ready. Review the summary before sharing it with
|
|
3366
|
+
the team."
|
|
3367
|
+
</span>
|
|
3368
|
+
}`;
|
|
3369
|
+
const expectedPrintWidth40 = `component App() {
|
|
3370
|
+
<span
|
|
3371
|
+
class={styles.notificationMessage}
|
|
3372
|
+
>
|
|
3373
|
+
"The report is ready. Review the
|
|
3374
|
+
summary before sharing it with the
|
|
3375
|
+
team."
|
|
3376
|
+
</span>
|
|
3377
|
+
}`;
|
|
3378
|
+
|
|
3379
|
+
const resultPrintWidth70 = await format(input, { printWidth: 70 });
|
|
3380
|
+
expect(resultPrintWidth70).toBeWithNewline(expectedPrintWidth70);
|
|
3381
|
+
|
|
3382
|
+
const resultPrintWidth40 = await format(input, { printWidth: 40 });
|
|
3383
|
+
expect(resultPrintWidth40).toBeWithNewline(expectedPrintWidth40);
|
|
3384
|
+
});
|
|
3385
|
+
|
|
3386
|
+
it('should keep direct text children on the text-specific conditional layout path', async () => {
|
|
3387
|
+
const input = `component App() {
|
|
3388
|
+
<div>"The report is ready. Review the summary before sharing it with the team."</div>
|
|
3389
|
+
}`;
|
|
3390
|
+
|
|
3391
|
+
const doc = await printToDoc(input, { printWidth: 70 });
|
|
3392
|
+
const hasConditionalTextGroup = docContains(doc, (part) => {
|
|
3393
|
+
return (
|
|
3394
|
+
part.type === 'group' &&
|
|
3395
|
+
Array.isArray(part.expandedStates) &&
|
|
3396
|
+
docContains(part.contents, (childPart) => {
|
|
3397
|
+
return (
|
|
3398
|
+
childPart.type === 'fill' &&
|
|
3399
|
+
Array.isArray(childPart.parts) &&
|
|
3400
|
+
childPart.parts.some((part) => Array.isArray(part) && part.includes('"The')) &&
|
|
3401
|
+
childPart.parts.some((part) => Array.isArray(part) && part.includes('team."'))
|
|
3402
|
+
);
|
|
3403
|
+
})
|
|
3404
|
+
);
|
|
3405
|
+
});
|
|
3406
|
+
|
|
3407
|
+
expect(hasConditionalTextGroup).toBe(true);
|
|
3408
|
+
});
|
|
3409
|
+
|
|
3242
3410
|
it('should not insert a new line between js and jsx if not provided', async () => {
|
|
3243
3411
|
const expected = `export component App() {
|
|
3244
3412
|
let text = 'something';
|