Round-trippable --debug-print-doc (#10169)

This commit is contained in:
Georgii Dolzhykov authored and GitHub committed 2021-01-29 21:48:34 +02:00
1 parent a7ea77e0ef
commit 78b38d2282
10 files changed
+171 -94

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
#### Round-trippable `--debug-print-doc` (#10169 by @thorn0)
The idea is to make the output of `--debug-print-doc` closer to actual code for generating docs (Prettier's intermediate representation). Ideally, it should be possible for it to work without modification after copy-pasting into a JS file. That ideal hasn't probably been reached by this PR, but it's pretty close. This is going to make `--debug-print-doc` and the corresponding part of the Playground a bit more useful.
+9
View File
@@ -237,6 +237,15 @@ declare var trim: Doc;
This will trim any whitespace or tab character on the current line. This is used for preprocessor directives.
### hardlineWithoutBreakParent and literallineWithoutBreakParent
These are used very rarely, for advanced formatting tricks.
Examples:
- `hardlineWithoutBreakParent` is used for printing tables in Prettier's Markdown printer. With `proseWrap` set to `never`, the columns are aligned only if none of the rows exceeds `printWidth`.
- `literallineWithoutBreakParent` (called `literalLineNoBreak` [there](https://github.com/prettier/plugin-ruby/blob/9e658db13d3f0524d816528e060b1381e0431559/src/ruby/nodes/heredocs.js)) is used in the [Ruby plugin](https://github.com/prettier/plugin-ruby) for printing heredoc syntax.
### cursor
```ts
+1
View File
@@ -370,6 +370,7 @@
"Tomasek",
"Tradeshift",
"Transloadit",
"trippable",
"TSAs",
"tsep",
"TSESTree",
+13 -5
View File
@@ -186,13 +186,19 @@ function lineSuffix(contents) {
const lineSuffixBoundary = { type: "line-suffix-boundary" };
const breakParent = { type: "break-parent" };
const trim = { type: "trim" };
const hardlineWithoutBreakParent = { type: "line", hard: true };
const literallineWithoutBreakParent = {
type: "line",
hard: true,
literal: true,
};
const line = { type: "line" };
const softline = { type: "line", soft: true };
const hardline = concat([{ type: "line", hard: true }, breakParent]);
const literalline = concat([
{ type: "line", hard: true, literal: true },
breakParent,
]);
const hardline = concat([hardlineWithoutBreakParent, breakParent]);
const literalline = concat([literallineWithoutBreakParent, breakParent]);
const cursor = { type: "cursor", placeholder: Symbol("cursor") };
/**
@@ -257,4 +263,6 @@ module.exports = {
markAsRoot,
dedentToRoot,
dedent,
hardlineWithoutBreakParent,
literallineWithoutBreakParent,
};
+124 -77
View File
@@ -5,13 +5,11 @@ const { isConcat, getDocParts } = require("./doc-utils");
function flattenDoc(doc) {
if (isConcat(doc)) {
const res = [];
const parts = getDocParts(doc);
for (let i = 0; i < parts.length; ++i) {
const doc2 = parts[i];
if (typeof doc2 !== "string" && isConcat(doc2)) {
res.push(...flattenDoc(doc2).parts);
for (const part of getDocParts(doc)) {
if (isConcat(part)) {
res.push(...flattenDoc(part).parts);
} else {
const flattened = flattenDoc(doc2);
const flattened = flattenDoc(part);
if (flattened !== "") {
res.push(flattened);
}
@@ -35,100 +33,149 @@ function flattenDoc(doc) {
? doc.expandedStates.map(flattenDoc)
: doc.expandedStates,
};
} else if (doc.type === "fill") {
return { type: "fill", parts: doc.parts.map(flattenDoc) };
} else if (doc.contents) {
return { ...doc, contents: flattenDoc(doc.contents) };
}
return doc;
}
function printDoc(doc) {
if (typeof doc === "string") {
return JSON.stringify(doc);
}
function printDocToDebug(doc) {
/** @type Record<symbol, string> */
const printedSymbols = Object.create(null);
/** @type Set<string> */
const usedKeysForSymbols = new Set();
return printDoc(flattenDoc(doc));
if (isConcat(doc)) {
return "[" + getDocParts(doc).map(printDoc).join(", ") + "]";
}
if (doc.type === "line") {
if (doc.literal) {
return "literalline";
function printDoc(doc, index, parentParts) {
if (typeof doc === "string") {
return JSON.stringify(doc);
}
if (doc.hard) {
return "hardline";
if (isConcat(doc)) {
return `[${getDocParts(doc).map(printDoc).filter(Boolean).join(", ")}]`;
}
if (doc.soft) {
return "softline";
if (doc.type === "line") {
const withBreakParent =
Array.isArray(parentParts) &&
parentParts[index + 1] &&
parentParts[index + 1].type === "break-parent";
if (doc.literal) {
return withBreakParent
? "literalline"
: "literallineWithoutBreakParent";
}
if (doc.hard) {
return withBreakParent ? "hardline" : "hardlineWithoutBreakParent";
}
if (doc.soft) {
return "softline";
}
return "line";
}
return "line";
}
if (doc.type === "break-parent") {
return "breakParent";
}
if (doc.type === "break-parent") {
const afterHardline =
Array.isArray(parentParts) &&
parentParts[index - 1] &&
parentParts[index - 1].type === "line" &&
parentParts[index - 1].hard;
return afterHardline ? undefined : "breakParent";
}
if (doc.type === "trim") {
return "trim";
}
if (doc.type === "trim") {
return "trim";
}
if (doc.type === "indent") {
return "indent(" + printDoc(doc.contents) + ")";
}
if (doc.type === "indent") {
return "indent(" + printDoc(doc.contents) + ")";
}
if (doc.type === "align") {
return doc.n === Number.NEGATIVE_INFINITY
? "dedentToRoot(" + printDoc(doc.contents) + ")"
: doc.n < 0
? "dedent(" + printDoc(doc.contents) + ")"
: doc.n.type === "root"
? "markAsRoot(" + printDoc(doc.contents) + ")"
: "align(" + JSON.stringify(doc.n) + ", " + printDoc(doc.contents) + ")";
}
if (doc.type === "align") {
return doc.n === Number.NEGATIVE_INFINITY
? "dedentToRoot(" + printDoc(doc.contents) + ")"
: doc.n < 0
? "dedent(" + printDoc(doc.contents) + ")"
: doc.n.type === "root"
? "markAsRoot(" + printDoc(doc.contents) + ")"
: "align(" +
JSON.stringify(doc.n) +
", " +
printDoc(doc.contents) +
")";
}
if (doc.type === "if-break") {
return (
"ifBreak(" +
printDoc(doc.breakContents) +
(doc.flatContents ? ", " + printDoc(doc.flatContents) : "") +
")"
);
}
if (doc.type === "group") {
if (doc.expandedStates) {
if (doc.type === "if-break") {
return (
"conditionalGroup(" +
"[" +
doc.expandedStates.map(printDoc).join(",") +
"])"
"ifBreak(" +
printDoc(doc.breakContents) +
(doc.flatContents ? ", " + printDoc(doc.flatContents) : "") +
(doc.groupId
? (!doc.flatContents ? ', ""' : "") +
`, { groupId: ${printGroupId(doc.groupId)} }`
: "") +
")"
);
}
return (
(doc.break ? "wrappedGroup" : "group") +
"(" +
printDoc(doc.contents) +
")"
);
if (doc.type === "group") {
const optionsParts = [];
if (doc.break && doc.break !== "propagated") {
optionsParts.push("break: true");
}
if (doc.id) {
optionsParts.push(`id: ${printGroupId(doc.id)}`);
}
const options =
optionsParts.length > 0 ? `, { ${optionsParts.join(", ")} }` : "";
if (doc.expandedStates) {
return `conditionalGroup([${doc.expandedStates
.map(printDoc)
.join(",")}]${options})`;
}
return `group(${printDoc(doc.contents)}${options})`;
}
if (doc.type === "fill") {
return `fill([${doc.parts.map(printDoc).join(", ")}])`;
}
if (doc.type === "line-suffix") {
return "lineSuffix(" + printDoc(doc.contents) + ")";
}
if (doc.type === "line-suffix-boundary") {
return "lineSuffixBoundary";
}
throw new Error("Unknown doc type " + doc.type);
}
if (doc.type === "fill") {
return "fill" + "(" + doc.parts.map(printDoc).join(", ") + ")";
}
function printGroupId(id) {
if (typeof id !== "symbol") {
return JSON.stringify(String(id));
}
if (doc.type === "line-suffix") {
return "lineSuffix(" + printDoc(doc.contents) + ")";
}
if (id in printedSymbols) {
return printedSymbols[id];
}
if (doc.type === "line-suffix-boundary") {
return "lineSuffixBoundary";
const prefix = id.description || "symbol";
for (let counter = 0; ; counter++) {
const key = prefix + (counter > 0 ? ` #${counter}` : "");
if (!usedKeysForSymbols.has(key)) {
usedKeysForSymbols.add(key);
return (printedSymbols[id] = `Symbol.for(${JSON.stringify(key)})`);
}
}
}
throw new Error("Unknown doc type " + doc.type);
}
module.exports = {
printDocToDebug(doc) {
return printDoc(flattenDoc(doc));
},
};
module.exports = { printDocToDebug };
+4 -2
View File
@@ -131,8 +131,10 @@ function breakParentGroup(groupStack) {
const parentGroup = groupStack[groupStack.length - 1];
// Breaks are not propagated through conditional groups because
// the user is expected to manually handle what breaks.
if (!parentGroup.expandedStates) {
parentGroup.break = true;
if (!parentGroup.expandedStates && !parentGroup.break) {
// An alternative truthy value allows to distinguish propagated group breaks
// and not to print them as `group(..., { break: true })` in `--debug-print-doc`.
parentGroup.break = "propagated";
}
}
return null;
+2 -2
View File
@@ -21,8 +21,9 @@ const {
align,
indent,
group,
hardlineWithoutBreakParent,
},
utils: { normalizeDoc, getDocParts },
utils: { normalizeDoc },
printer: { printDocToString },
} = require("../document");
const { replaceEndOfLineWith } = require("../common/util");
@@ -557,7 +558,6 @@ function printLine(path, value, options) {
}
function printTable(path, options, print) {
const hardlineWithoutBreakParent = getDocParts(hardline)[0];
const node = path.getValue();
const columnMaxWidths = [];
@@ -10,7 +10,6 @@ exports[`API prettier.formatDoc 1`] = `
\\";\\",
]),
hardline,
breakParent,
];
"
`;
+14 -6
View File
@@ -1,12 +1,9 @@
"use strict";
const {
parse,
formatAST,
formatDoc,
printToDoc,
printDocToString,
} = require("prettier-local").__debug;
__debug: { parse, formatAST, formatDoc, printToDoc, printDocToString },
doc: { builders },
} = require("prettier-local");
const { outdent } = require("outdent");
const code = outdent`
@@ -45,4 +42,15 @@ describe("API", () => {
test("prettier.printDocToString", () => {
expect(stringFromDoc).toBe(formatted);
});
const doc2 = new Function(
`{ ${Object.keys(builders)} }`,
`return ${formatResultFromDoc}`
)(builders);
const { formatted: stringFromDoc2 } = printDocToString(doc2, options);
const formatResultFromDoc2 = formatDoc(doc2, options);
test("output of prettier.formatDoc can be reused as code", () => {
expect(stringFromDoc2).toBe(formatted);
expect(formatResultFromDoc2).toBe(formatResultFromDoc);
});
});
@@ -6,7 +6,7 @@ describe("prints doc with --debug-print-doc", () => {
runPrettier("cli/with-shebang", ["--debug-print-doc", "--parser", "babel"], {
input: "0",
}).test({
stdout: '["0", ";", hardline, breakParent];\n',
stdout: '["0", ";", hardline];\n',
stderr: "",
status: 0,
});