Round-trippable --debug-print-doc (#10169)
This commit is contained in:
1 parent
a7ea77e0ef
commit
78b38d2282
10 files changed
+171
-94
No files matched your search
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -370,6 +370,7 @@
|
||||
"Tomasek",
|
||||
"Tradeshift",
|
||||
"Transloadit",
|
||||
"trippable",
|
||||
"TSAs",
|
||||
"tsep",
|
||||
"TSESTree",
|
||||
|
||||
@@ -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
@@ -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 };
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
"
|
||||
`;
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user