Add PrintResult for doc printer (#18372)

This commit is contained in:
fisker Cheung authored and GitHub committed 2026-01-02 13:41:28 +08:00
1 parent c4fb922a0f
commit 58ff4642dd
4 files changed
+110 -70

No files matched your search

+31 -21
View File
@@ -1,5 +1,6 @@
import assert from "node:assert/strict";
import fs from "node:fs/promises";
import prettyBytes from "pretty-bytes";
import * as prettierProduction from "../node_modules/prettier/index.mjs";
import * as prettierDevelopment from "../src/index.js";
import { runBenchmark } from "./utilities.js";
@@ -7,27 +8,36 @@ import { runBenchmark } from "./utilities.js";
assert.notEqual(prettierProduction.version, prettierDevelopment.version);
const text = await fs.readFile(new URL(import.meta.url), "utf8");
// @ts-expect-error -- No types
const doc = await prettierDevelopment.__debug.printToDoc(text, {
parser: "babel",
cursorOffset: text.indexOf("cursorOffset"),
});
const run = (prettier) =>
prettier.doc.printer.printDocToString(doc, { printWidth: 80, tabWidth: 2 });
for (const size of [1, 1e1, 1e2, 1e3]) {
const doc = await Promise.all(
Array.from({ length: size }, (_, index) =>
// @ts-expect-error -- No types
prettierDevelopment.__debug.printToDoc(text, {
parser: "babel",
cursorOffset: index === 0 ? text.indexOf("cursorOffset") : undefined,
}),
),
);
const expected = await run(prettierProduction);
const run = (prettier) =>
prettier.doc.printer.printDocToString(doc, { printWidth: 80, tabWidth: 2 });
await runBenchmark(
{
name: "printDocToString",
assert: (result) => assert.deepEqual(result, expected),
},
[
{ name: "Development", prettier: prettierDevelopment },
{ name: "Production", prettier: prettierProduction },
].map(({ name, prettier }) => ({
name,
implementation: () => run(prettier),
})),
);
const expected = await run(prettierProduction);
await runBenchmark(
{
name: `printDocToString (${size}, ${prettyBytes(text.length * size)})`,
assert: (result) => assert.deepEqual(result, expected),
warmupIterations: 1,
iterations: size > 5e3 ? 1 : size > 1e2 ? 2 : undefined,
},
[
{ name: "Development", prettier: prettierDevelopment },
{ name: "Production", prettier: prettierProduction },
].map(({ name, prettier }) => ({
name,
implementation: () => run(prettier),
})),
);
}
+1 -1
View File
@@ -28,7 +28,7 @@ async function runBenchmark(options, implementations) {
const { assert, ...benchOptions } = options;
const bench = new Bench(benchOptions);
const bench = new Bench({ throws: true, ...benchOptions });
let error;
+64
View File
@@ -0,0 +1,64 @@
import { trimIndentation } from "./trim-indentation.js";
class printResult {
/** @type {string[]} */
#settledTexts = [];
#unsettledText = "";
#settledTextLength = 0;
/** @type {number[]} */
#settledPositions = [];
/** @type {number[]} */
#unsettledPositions = [];
#settle() {
const text = this.#unsettledText;
if (text !== "") {
this.#settledTexts.push(text);
this.#settledTextLength += text.length;
this.#unsettledText = "";
}
for (const position of this.#unsettledPositions) {
this.#settledPositions.push(Math.min(position, this.#settledTextLength));
}
this.#unsettledPositions.length = 0;
}
markPosition() {
if (this.#settledPositions.length + this.#unsettledPositions.length >= 2) {
throw new Error("There are too many 'cursor' in doc.");
}
this.#unsettledPositions.push(
this.#settledTextLength + this.#unsettledText.length,
);
}
/**
@param {string} text
*/
write(text) {
this.#unsettledText += text;
}
trim() {
const { text: trimmed, count } = trimIndentation(this.#unsettledText);
this.#unsettledText = trimmed;
this.#settle();
return count;
}
finish() {
this.#settle();
return {
text: this.#settledTexts.join(""),
positions: this.#settledPositions,
};
}
}
export default printResult;
+14 -48
View File
@@ -22,6 +22,7 @@ import {
import { getDocType, propagateBreaks } from "../utilities/index.js";
import InvalidDocError from "../utilities/invalid-doc-error.js";
import { makeAlign, makeIndent, ROOT_INDENT } from "./indent.js";
import PrintResult from "./print-result.js";
import { trimIndentation } from "./trim-indentation.js";
/**
@@ -187,16 +188,11 @@ function printDocToString(doc, options) {
// commands to the array instead of recursively calling `print`.
/** @type Command[] */
const commands = [{ indent: ROOT_INDENT, mode: MODE_BREAK, doc }];
let output = "";
let shouldRemeasure = false;
/** @type Command[] */
const lineSuffix = [];
const cursorPositions = [];
/** @type string[] */
const settledOutput = [];
const settledCursorPositions = [];
let settledTextLength = 0;
const result = new PrintResult();
propagateBreaks(doc);
@@ -208,7 +204,7 @@ function printDocToString(doc, options) {
newLine !== "\n" ? doc.replaceAll("\n", newLine) : doc;
// Plugins may print single string, should skip measure the width
if (formatted) {
output += formatted;
result.write(formatted);
if (commands.length > 0) {
position += getStringWidth(formatted);
}
@@ -223,10 +219,7 @@ function printDocToString(doc, options) {
break;
case DOC_TYPE_CURSOR:
if (cursorPositions.length >= 2) {
throw new Error("There are too many 'cursor' in doc.");
}
cursorPositions.push(settledTextLength + output.length);
result.markPosition();
break;
case DOC_TYPE_INDENT:
@@ -246,7 +239,7 @@ function printDocToString(doc, options) {
break;
case DOC_TYPE_TRIM:
trim();
position -= result.trim();
break;
case DOC_TYPE_GROUP:
@@ -481,8 +474,7 @@ function printDocToString(doc, options) {
case MODE_FLAT:
if (!doc.hard) {
if (!doc.soft) {
output += " ";
result.write(" ");
position += 1;
}
@@ -506,17 +498,17 @@ function printDocToString(doc, options) {
}
if (doc.literal) {
output += newLine;
result.write(newLine);
position = 0;
if (indent.root) {
if (indent.root.value) {
output += indent.root.value;
result.write(indent.root.value);
}
position = indent.root.length;
}
} else {
trim();
output += newLine + indent.value;
result.trim();
result.write(newLine + indent.value);
position = indent.length;
}
break;
@@ -543,10 +535,9 @@ function printDocToString(doc, options) {
}
}
const formatted = settledOutput.join("") + output;
const finalCursorPositions = [...settledCursorPositions, ...cursorPositions];
const { text: formatted, positions: cursorPositions } = result.finish();
if (finalCursorPositions.length !== 2) {
if (cursorPositions.length !== 2) {
// If the doc contained ONE cursor command,
// instead of the expected zero or two. If the doc being printed was
// returned by printAstToDoc, then the only ways this can have happened
@@ -575,37 +566,12 @@ function printDocToString(doc, options) {
return { formatted };
}
const cursorNodeStart = finalCursorPositions[0];
const [cursorNodeStart, cursorNodeEnd] = cursorPositions;
return {
formatted,
cursorNodeStart,
cursorNodeText: formatted.slice(
cursorNodeStart,
finalCursorPositions.at(-1),
),
cursorNodeText: formatted.slice(cursorNodeStart, cursorNodeEnd),
};
function trim() {
const { text: trimmed, count } = trimIndentation(output);
if (trimmed) {
settledOutput.push(trimmed);
settledTextLength += trimmed.length;
}
output = "";
position -= count;
if (cursorPositions.length > 0) {
settledCursorPositions.push(
...cursorPositions.map((position) =>
Math.min(position, settledTextLength),
),
);
cursorPositions.length = 0;
}
}
}
export { printDocToString };