Correct invalid fill() in markdown paragraph print (#16158)

This commit is contained in:
Seiya authored and GitHub committed 2024-04-06 19:25:30 +09:00
1 parent 6215520007
commit 063978a592
8 files changed
+152 -65

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
#### Improve wrapping for markdown / mdx (#16158 by @seiyab)
```json
{ "proseWrap": "always" }
```
<!-- prettier-ignore -->
```md
<!-- Input -->
\
word very-very-very-very-very-very-very-very-very-very-long-word very-very-very-very-very-very-very-very-very-very-long-word
<!-- Prettier stable -->
\
word very-very-very-very-very-very-very-very-very-very-long-word very-very-very-very-very-very-very-very-very-very-long-word
<!-- Prettier main -->
\
word very-very-very-very-very-very-very-very-very-very-long-word
very-very-very-very-very-very-very-very-very-very-long-word
```
+5 -20
View File
@@ -337,10 +337,11 @@ function cleanDocFn(doc) {
return doc;
}
// A safer version of `normalizeDoc`
// - `normalizeDoc` concat strings and flat array in `fill`, while `cleanDoc` don't
// - On array, `normalizeDoc` always return object with `parts`, `cleanDoc` may return strings
// - `cleanDoc` also remove nested `group`s and empty `fill`/`align`/`indent`/`line-suffix`/`if-break` if possible
// - concat strings
// - flat arrays except for parts of `fill`
// - merge arrays of strings into single strings
// - remove nested `group`s and empty `fill`/`align`/`indent`/`line-suffix`/`if-break` if possible
function cleanDoc(doc) {
return mapDoc(doc, (currentDoc) => cleanDocFn(currentDoc));
}
@@ -376,21 +377,6 @@ function normalizeParts(parts) {
return newParts;
}
function normalizeDoc(doc) {
return mapDoc(doc, (currentDoc) => {
if (Array.isArray(currentDoc)) {
return normalizeParts(currentDoc);
}
if (!currentDoc.parts) {
return currentDoc;
}
return {
...currentDoc,
parts: normalizeParts(currentDoc.parts),
};
});
}
function replaceEndOfLine(doc, replacement = literalline) {
return mapDoc(doc, (currentDoc) =>
typeof currentDoc === "string"
@@ -422,7 +408,6 @@ export {
getDocType,
inheritLabel,
mapDoc,
normalizeDoc,
normalizeParts,
propagateBreaks,
removeLines,
+37 -2
View File
@@ -1,5 +1,6 @@
import { fill } from "../document/builders.js";
import { normalizeDoc } from "../document/utils.js";
import { DOC_TYPE_ARRAY, DOC_TYPE_FILL } from "../document/constants.js";
import { getDocType } from "../document/utils.js";
/**
* @typedef {import("../common/ast-path.js").default} AstPath
@@ -14,7 +15,41 @@ import { normalizeDoc } from "../document/utils.js";
*/
function printParagraph(path, options, print) {
const parts = path.map(print, "children");
return normalizeDoc(fill(parts));
return flattenFill(parts);
}
/**
* @param {Doc[]} docs
* @returns {Doc}
*/
function flattenFill(docs) {
/*
* We assume parts always meet following conditions:
* - parts.length is odd
* - odd elements are line-like doc that comes from odd element off inner fill
*/
/** @type {Doc[]} */
const parts = [""];
(function rec(/** @type {*} */ docArray) {
for (const doc of docArray) {
const docType = getDocType(doc);
if (docType === DOC_TYPE_ARRAY) {
rec(doc);
continue;
}
let head = doc;
let rest = [];
if (docType === DOC_TYPE_FILL) {
[head, ...rest] = doc.parts;
}
parts.push([parts.pop(), head], ...rest);
}
})(docs);
return fill(parts);
}
export { printParagraph };
+37
View File
@@ -0,0 +1,37 @@
/**
* @typedef {import("../common/ast-path.js").default} AstPath
* @typedef {import("../document/builders.js").Doc} Doc
*/
import { fill } from "../document/builders.js";
import { DOC_TYPE_STRING } from "../document/constants.js";
import { getDocType } from "../document/utils.js";
/**
* @param {AstPath} path
* @param {*} print
* @returns {Doc}
*/
function printSentence(path, print) {
/** @type {Doc[]} */
const parts = [""];
path.each(() => {
const { node } = path;
const doc = print();
switch (node.type) {
case "whitespace":
if (getDocType(doc) !== DOC_TYPE_STRING) {
parts.push(doc, "");
break;
}
// fallthrough
default:
parts.push([parts.pop(), doc]);
}
}, "children");
return fill(parts);
}
export { printSentence };
+21 -7
View File
@@ -2,6 +2,7 @@ import collapseWhiteSpace from "collapse-white-space";
import {
align,
fill,
group,
hardline,
indent,
@@ -10,7 +11,8 @@ import {
markAsRoot,
softline,
} from "../document/builders.js";
import { replaceEndOfLine } from "../document/utils.js";
import { DOC_TYPE_STRING } from "../document/constants.js";
import { getDocType, replaceEndOfLine } from "../document/utils.js";
import getMaxContinuousCount from "../utils/get-max-continuous-count.js";
import getMinNotPresentContinuousCount from "../utils/get-min-not-present-continuous-count.js";
import getPreferredQuote from "../utils/get-preferred-quote.js";
@@ -24,6 +26,7 @@ import { insertPragma } from "./pragma.js";
import { printTable } from "./print/table.js";
import { printParagraph } from "./print-paragraph.js";
import preprocess from "./print-preprocess.js";
import { printSentence } from "./print-sentence.js";
import { printWhitespace } from "./print-whitespace.js";
import {
getFencedCodeBlockValue,
@@ -48,16 +51,27 @@ function genericPrint(path, options, print) {
const { node } = path;
if (shouldRemainTheSameContent(path)) {
return splitText(
/** @type {Doc} */
const parts = [""];
const textsNodes = splitText(
options.originalText.slice(
node.position.start.offset,
node.position.end.offset,
),
).map((node) =>
node.type === "word"
? node.value
: printWhitespace(path, node.value, options.proseWrap, true),
);
for (const node of textsNodes) {
if (node.type === "word") {
parts.push([parts.pop(), node.value]);
continue;
}
const doc = printWhitespace(path, node.value, options.proseWrap, true);
if (getDocType(doc) === DOC_TYPE_STRING) {
parts.push([parts.pop(), doc]);
continue;
}
parts.push(doc);
}
return fill(parts);
}
switch (node.type) {
@@ -75,7 +89,7 @@ function genericPrint(path, options, print) {
case "paragraph":
return printParagraph(path, options, print);
case "sentence":
return printChildren(path, options, print);
return printSentence(path, print);
case "word": {
let escapedValue = node.value
.replaceAll("*", "\\*") // escape all `*`
@@ -28,3 +28,29 @@ proseWrap: "always"
================================================================================
`;
exports[`wrap.md - {"proseWrap":"always"} format 1`] = `
====================================options=====================================
parsers: ["markdown"]
printWidth: 80
proseWrap: "always"
| printWidth
=====================================input======================================
a
very-very-very-very-very-very-very-very-very-very-long-word very-very-very-very-very-very-very-very-very-very-long-word very-very-very-very-very-very-very-very-very-very-long-word
\\
word very-very-very-very-very-very-very-very-very-very-long-word very-very-very-very-very-very-very-very-very-very-long-word
=====================================output=====================================
a
very-very-very-very-very-very-very-very-very-very-long-word
very-very-very-very-very-very-very-very-very-very-long-word
very-very-very-very-very-very-very-very-very-very-long-word
\\
word very-very-very-very-very-very-very-very-very-very-long-word
very-very-very-very-very-very-very-very-very-very-long-word
================================================================================
`;
+5
View File
@@ -0,0 +1,5 @@
a
very-very-very-very-very-very-very-very-very-very-long-word very-very-very-very-very-very-very-very-very-very-long-word very-very-very-very-very-very-very-very-very-very-long-word
\
word very-very-very-very-very-very-very-very-very-very-long-word very-very-very-very-very-very-very-very-very-very-long-word
@@ -1,36 +0,0 @@
import { fill, group } from "../../../src/document/builders.js";
import { normalizeDoc } from "../../../src/document/utils.js";
describe("normalizeDoc", () => {
test.each([
[
"removes empty strings",
["", "foo", fill(["", "bar", ""]), ""],
["foo", fill(["bar"])],
],
[
"flattens nested concat",
["foo ", "", ["bar ", "", ["baz", ""]]],
["foo bar baz"],
],
[
"flattens nested concat in other docs",
group(["foo ", ["bar ", "", ["baz", ""]]]),
group(["foo bar baz"]),
],
[
"keeps groups",
[group("foo"), group("bar"), group("baz")],
[group("foo"), group("bar"), group("baz")],
],
[
"keeps fills",
fill(["foo", fill(["bar", fill(["baz"])])]),
fill(["foo", fill(["bar", fill(["baz"])])]),
],
])("%s", (_, doc, expected) => {
const result = normalizeDoc(doc);
expect(result).toEqual(expected);
});
});