Improve computational time complexity for printing fill() (#16595)

This commit is contained in:
Seiya authored and GitHub committed 2024-08-24 08:23:59 +09:00
1 parent 92253a3f40
commit c921abee76
2 files changed
+40 -24

No files matched your search

+14 -23
View File
@@ -32,7 +32,7 @@ const MODE_FLAT = Symbol("MODE_FLAT");
const CURSOR_PLACEHOLDER = Symbol("cursor");
const IS_MUTABLE_FILL = Symbol("IS_MUTABLE_FILL");
const DOC_FILL_PRINTED_LENGTH = Symbol("DOC_FILL_PRINTED_LENGTH");
function rootIndent() {
return { value: "", length: 0, queue: [] };
@@ -463,12 +463,15 @@ function printDocToString(doc, options) {
case DOC_TYPE_FILL: {
const rem = width - pos;
const offset = doc[DOC_FILL_PRINTED_LENGTH] ?? 0;
const { parts } = doc;
if (parts.length === 0) {
const length = parts.length - offset;
if (length === 0) {
break;
}
const [content, whitespace] = parts;
const content = parts[offset + 0];
const whitespace = parts[offset + 1];
/** @type {Command} */
const contentFlatCmd = { ind, mode: MODE_FLAT, doc: content };
/** @type {Command} */
@@ -482,7 +485,7 @@ function printDocToString(doc, options) {
true,
);
if (parts.length === 1) {
if (length === 1) {
if (contentFits) {
cmds.push(contentFlatCmd);
} else {
@@ -496,7 +499,7 @@ function printDocToString(doc, options) {
/** @type {Command} */
const whitespaceBreakCmd = { ind, mode: MODE_BREAK, doc: whitespace };
if (parts.length === 2) {
if (length === 2) {
if (contentFits) {
cmds.push(whitespaceFlatCmd, contentFlatCmd);
} else {
@@ -505,26 +508,14 @@ function printDocToString(doc, options) {
break;
}
const secondContent = parts[2];
// 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,
};
}
const secondContent = parts[offset + 2];
/** @type {Command} */
const remainingCmd = { ind, mode, doc: remainingDoc };
const remainingCmd = {
ind,
mode,
doc: { ...doc, [DOC_FILL_PRINTED_LENGTH]: offset + 2 },
};
/** @type {Command} */
const firstAndSecondContentFlatCmd = {
+26 -1
View File
@@ -40,6 +40,31 @@ test("`printDocToString` should not manipulate docs", () => {
const lines = formatted.split("\n");
expect(lines.length).toBeGreaterThan(1000);
expect(endTime - startTime).toBeLessThan(1000);
expect(endTime - startTime).toBeLessThan(100);
}
});
describe("`printDocToString` has linear time complexity at most to print fill()", () => {
const baseSize = 3_000;
const relativeMargin = 0.3;
const baseTime = time(makeFill(baseSize));
test.each([10_000, 20_000, 40_000])("numWords=%d", (numWords) => {
const doc = makeFill(numWords);
const ellapsed = time(doc);
const ratio = numWords / baseSize;
expect(ellapsed).toBeLessThan(baseTime * ratio * (1 + relativeMargin));
});
function makeFill(numWords) {
const WORD = "word";
const parts = Array.from({ length: numWords }, () => WORD);
return fill(join(line, parts));
}
function time(doc) {
const printOptions = { printWidth: 40, tabWidth: 2 };
const start = performance.now();
printDocToString(doc, printOptions);
return performance.now() - start;
}
});