Stop mutate doc.parts when printing fill (#13315)
Co-authored-by: Alexander Kachkaev <alexander@kachkaev.ru>
This commit is contained in:
1 parent
407f1a0056
commit
c60eb83d59
6 files changed
+98
-10
No files matched your search
@@ -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
@@ -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]]}`
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user