Correct invalid fill() in markdown paragraph print (#16158)
This commit is contained in:
1 parent
6215520007
commit
063978a592
8 files changed
+152
-65
No files matched your search
@@ -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
@@ -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,
|
||||
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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
|
||||
|
||||
================================================================================
|
||||
`;
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user