Stop mutate doc.parts when printing fill (#13315)

Co-authored-by: Alexander Kachkaev <alexander@kachkaev.ru>
This commit is contained in:
fisker CheungandAlexander Kachkaev authored and GitHub committed 2024-07-26 19:51:00 +08:00
1 parent 407f1a0056
commit c60eb83d59
6 files changed
+98 -10

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
#### Stop doc mutation in `prettier.doc.printDocToString` (#13315 by @fisker)
For performance reason, `prettier.doc.printDocToString` used to mutate `.parts` of the [`fill`](https://github.com/prettier/prettier/blob/main/commands.md#fill) command during print. It was converted to a [pure function](https://en.wikipedia.org/wiki/Pure_function) to ensure output correctness.
@@ -0,0 +1,19 @@
#### Fix template literal print with array (#13315 by @fisker, @syi0808)
<!-- prettier-ignore -->
```jsx
// Input
const string = `${[[1, 2], [3, 4]]}`
// Prettier stable
const string = `${[
[1, 2],
[3, 4],
]}`;
// Prettier main
const string = `${[
[1, 2],
[3, 4],
]}`;
```
+22 -10
View File
@@ -1,6 +1,6 @@
import { convertEndOfLineToChars } from "../common/end-of-line.js";
import getStringWidth from "../utils/get-string-width.js";
import { fill, hardlineWithoutBreakParent, indent } from "./builders.js";
import { hardlineWithoutBreakParent, indent } from "./builders.js";
import {
DOC_TYPE_ALIGN,
DOC_TYPE_ARRAY,
@@ -32,6 +32,8 @@ const MODE_FLAT = Symbol("MODE_FLAT");
const CURSOR_PLACEHOLDER = Symbol("cursor");
const IS_MUTABLE_FILL = Symbol("IS_MUTABLE_FILL");
function rootIndent() {
return { value: "", length: 0, queue: [] };
}
@@ -503,16 +505,26 @@ function printDocToString(doc, options) {
break;
}
// At this point we've handled the first pair (context, separator)
// and will create a new fill doc for the rest of the content.
// Ideally we wouldn't mutate the array here but copying all the
// elements to a new array would make this algorithm quadratic,
// which is unusable for large arrays (e.g. large texts in JSX).
parts.splice(0, 2);
/** @type {Command} */
const remainingCmd = { ind, mode, doc: fill(parts) };
const secondContent = parts[2];
const secondContent = parts[0];
// At this point we've handled the first pair (context, separator)
// and will create a new *mutable* fill doc for the rest of the content.
// Copying all the elements to a new array would make this algorithm quadratic,
// which is unusable for large arrays (e.g. large texts in JSX).
// https://github.com/prettier/prettier/issues/3263#issuecomment-344275152
let remainingDoc = doc;
if (doc[IS_MUTABLE_FILL]) {
parts.splice(0, 2);
} else {
remainingDoc = {
...doc,
parts: parts.slice(2),
[IS_MUTABLE_FILL]: true,
};
}
/** @type {Command} */
const remainingCmd = { ind, mode, doc: remainingDoc };
/** @type {Command} */
const firstAndSecondContentFlatCmd = {
@@ -164,6 +164,8 @@ descirbe('something', () => {
throw new Error(\`pretty-format: Option "theme" has a key "\${key}" whose value "\${value}" is undefined in ansi-styles.\`,)
a = \`\${[[1, 2, 3], [4, 5, 6]]}\`
=====================================output=====================================
const long1 = \`long \${
a.b //comment
@@ -224,6 +226,11 @@ throw new Error(
\`pretty-format: Option "theme" has a key "\${key}" whose value "\${value}" is undefined in ansi-styles.\`,
);
a = \`\${[
[1, 2, 3],
[4, 5, 6],
]}\`;
================================================================================
`;
@@ -44,3 +44,5 @@ descirbe('something', () => {
})
throw new Error(`pretty-format: Option "theme" has a key "${key}" whose value "${value}" is undefined in ansi-styles.`,)
a = `${[[1, 2, 3], [4, 5, 6]]}`
+45
View File
@@ -0,0 +1,45 @@
// TODO: Find a better way to test the performance
import { fill, join, line } from "../../src/document/builders.js";
import { printDocToString } from "../../src/document/printer.js";
test("`printDocToString` should not manipulate docs", () => {
const printOptions = { printWidth: 40, tabWidth: 2 };
const doc = fill(
join(
line,
Array.from({ length: 255 }, (_, index) => String(index + 1)),
),
);
expect(doc.parts.length).toBe(255 + 254);
const { formatted: firstPrint } = printDocToString(doc, printOptions);
expect(doc.parts.length).toBe(255 + 254);
const { formatted: secondPrint } = printDocToString(doc, printOptions);
expect(firstPrint).toBe(secondPrint);
{
// About 1000 lines, #3263
const WORD = "word";
const hugeParts = join(
line,
Array.from(
{ length: 1000 * Math.ceil(printOptions.printWidth / WORD.length) },
() => WORD,
),
);
const orignalLength = hugeParts.length;
const startTime = performance.now();
const { formatted } = printDocToString(fill(hugeParts), printOptions);
const endTime = performance.now();
expect(hugeParts.length).toBe(orignalLength);
const lines = formatted.split("\n");
expect(lines.length).toBeGreaterThan(1000);
expect(endTime - startTime).toBeLessThan(1000);
}
});