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 { convertEndOfLineToChars } from "../common/end-of-line.js";
|
||||||
import getStringWidth from "../utils/get-string-width.js";
|
import getStringWidth from "../utils/get-string-width.js";
|
||||||
import { fill, hardlineWithoutBreakParent, indent } from "./builders.js";
|
import { hardlineWithoutBreakParent, indent } from "./builders.js";
|
||||||
import {
|
import {
|
||||||
DOC_TYPE_ALIGN,
|
DOC_TYPE_ALIGN,
|
||||||
DOC_TYPE_ARRAY,
|
DOC_TYPE_ARRAY,
|
||||||
@@ -32,6 +32,8 @@ const MODE_FLAT = Symbol("MODE_FLAT");
|
|||||||
|
|
||||||
const CURSOR_PLACEHOLDER = Symbol("cursor");
|
const CURSOR_PLACEHOLDER = Symbol("cursor");
|
||||||
|
|
||||||
|
const IS_MUTABLE_FILL = Symbol("IS_MUTABLE_FILL");
|
||||||
|
|
||||||
function rootIndent() {
|
function rootIndent() {
|
||||||
return { value: "", length: 0, queue: [] };
|
return { value: "", length: 0, queue: [] };
|
||||||
}
|
}
|
||||||
@@ -503,16 +505,26 @@ function printDocToString(doc, options) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point we've handled the first pair (context, separator)
|
const secondContent = parts[2];
|
||||||
// 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[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} */
|
/** @type {Command} */
|
||||||
const firstAndSecondContentFlatCmd = {
|
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.\`,)
|
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=====================================
|
=====================================output=====================================
|
||||||
const long1 = \`long \${
|
const long1 = \`long \${
|
||||||
a.b //comment
|
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.\`,
|
\`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.`,)
|
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