Improve computational time complexity for printing fill() (#16595)
This commit is contained in:
1 parent
92253a3f40
commit
c921abee76
2 files changed
+40
-24
No files matched your search
+14
-23
@@ -32,7 +32,7 @@ const MODE_FLAT = Symbol("MODE_FLAT");
|
|||||||
|
|
||||||
const CURSOR_PLACEHOLDER = Symbol("cursor");
|
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() {
|
function rootIndent() {
|
||||||
return { value: "", length: 0, queue: [] };
|
return { value: "", length: 0, queue: [] };
|
||||||
@@ -463,12 +463,15 @@ function printDocToString(doc, options) {
|
|||||||
case DOC_TYPE_FILL: {
|
case DOC_TYPE_FILL: {
|
||||||
const rem = width - pos;
|
const rem = width - pos;
|
||||||
|
|
||||||
|
const offset = doc[DOC_FILL_PRINTED_LENGTH] ?? 0;
|
||||||
const { parts } = doc;
|
const { parts } = doc;
|
||||||
if (parts.length === 0) {
|
const length = parts.length - offset;
|
||||||
|
if (length === 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [content, whitespace] = parts;
|
const content = parts[offset + 0];
|
||||||
|
const whitespace = parts[offset + 1];
|
||||||
/** @type {Command} */
|
/** @type {Command} */
|
||||||
const contentFlatCmd = { ind, mode: MODE_FLAT, doc: content };
|
const contentFlatCmd = { ind, mode: MODE_FLAT, doc: content };
|
||||||
/** @type {Command} */
|
/** @type {Command} */
|
||||||
@@ -482,7 +485,7 @@ function printDocToString(doc, options) {
|
|||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (parts.length === 1) {
|
if (length === 1) {
|
||||||
if (contentFits) {
|
if (contentFits) {
|
||||||
cmds.push(contentFlatCmd);
|
cmds.push(contentFlatCmd);
|
||||||
} else {
|
} else {
|
||||||
@@ -496,7 +499,7 @@ function printDocToString(doc, options) {
|
|||||||
/** @type {Command} */
|
/** @type {Command} */
|
||||||
const whitespaceBreakCmd = { ind, mode: MODE_BREAK, doc: whitespace };
|
const whitespaceBreakCmd = { ind, mode: MODE_BREAK, doc: whitespace };
|
||||||
|
|
||||||
if (parts.length === 2) {
|
if (length === 2) {
|
||||||
if (contentFits) {
|
if (contentFits) {
|
||||||
cmds.push(whitespaceFlatCmd, contentFlatCmd);
|
cmds.push(whitespaceFlatCmd, contentFlatCmd);
|
||||||
} else {
|
} else {
|
||||||
@@ -505,26 +508,14 @@ function printDocToString(doc, options) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const secondContent = parts[2];
|
const secondContent = parts[offset + 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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @type {Command} */
|
/** @type {Command} */
|
||||||
const remainingCmd = { ind, mode, doc: remainingDoc };
|
const remainingCmd = {
|
||||||
|
ind,
|
||||||
|
mode,
|
||||||
|
doc: { ...doc, [DOC_FILL_PRINTED_LENGTH]: offset + 2 },
|
||||||
|
};
|
||||||
|
|
||||||
/** @type {Command} */
|
/** @type {Command} */
|
||||||
const firstAndSecondContentFlatCmd = {
|
const firstAndSecondContentFlatCmd = {
|
||||||
|
|||||||
@@ -40,6 +40,31 @@ test("`printDocToString` should not manipulate docs", () => {
|
|||||||
|
|
||||||
const lines = formatted.split("\n");
|
const lines = formatted.split("\n");
|
||||||
expect(lines.length).toBeGreaterThan(1000);
|
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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user