Extract some print functions in markdown (#18321)

Co-authored-by: fisker Cheung <lionkay@gmail.com>
This commit is contained in:
Seiyaandfisker Cheung authored and GitHub committed 2025-11-26 13:49:48 +08:00
1 parent 864bb7c042
commit e2ed914e02
4 files changed
+285 -239

No files matched your search

+113
View File
@@ -0,0 +1,113 @@
import { hardline } from "../../document/index.js";
import {
INLINE_NODE_TYPES,
INLINE_NODE_WRAPPER_TYPES,
isPrettierIgnore,
} from "../utils.js";
/**
* @import AstPath from "../../common/ast-path.js"
* @import {Doc} from "../../document/index.js"
*/
/**
* @param {AstPath} path
* @param {*} options
* @param {() => Doc} print
* @param {*} [events]
* @return {Doc}
*/
function printChildren(path, options, print, events = {}) {
const { processor = print } = events;
const parts = [];
path.each(() => {
const result = processor(path);
if (result !== false) {
if (parts.length > 0 && shouldPrePrintHardline(path)) {
parts.push(hardline);
if (
shouldPrePrintDoubleHardline(path, options) ||
shouldPrePrintTripleHardline(path)
) {
parts.push(hardline);
}
if (shouldPrePrintTripleHardline(path)) {
parts.push(hardline);
}
}
parts.push(result);
}
}, "children");
return parts;
}
function shouldPrePrintHardline({ node, parent }) {
const isInlineNode = INLINE_NODE_TYPES.has(node.type);
const isInlineHTML =
node.type === "html" && INLINE_NODE_WRAPPER_TYPES.has(parent.type);
return !isInlineNode && !isInlineHTML;
}
const SIBLING_NODE_TYPES = new Set(["listItem", "definition"]);
function shouldPrePrintDoubleHardline({ node, previous, parent }, options) {
if (
isLooseListItem(previous, options) ||
(node.type === "list" &&
parent.type === "listItem" &&
previous.type === "code")
) {
return true;
}
const isSequence = previous.type === node.type;
const isSiblingNode = isSequence && SIBLING_NODE_TYPES.has(node.type);
const isInTightListItem =
parent.type === "listItem" &&
(node.type === "list" || !isLooseListItem(parent, options));
const isPrevNodePrettierIgnore = isPrettierIgnore(previous) === "next";
const isBlockHtmlWithoutBlankLineBetweenPrevHtml =
node.type === "html" &&
previous.type === "html" &&
previous.position.end.line + 1 === node.position.start.line;
const isHtmlDirectAfterListItem =
node.type === "html" &&
parent.type === "listItem" &&
previous.type === "paragraph" &&
previous.position.end.line + 1 === node.position.start.line;
return !(
isSiblingNode ||
isInTightListItem ||
isPrevNodePrettierIgnore ||
isBlockHtmlWithoutBlankLineBetweenPrevHtml ||
isHtmlDirectAfterListItem
);
}
function shouldPrePrintTripleHardline({ node, previous }) {
const isPrevNodeList = previous.type === "list";
const isIndentedCode = node.type === "code" && node.isIndented;
return isPrevNodeList && isIndentedCode;
}
function isLooseListItem(node, options) {
return (
node.type === "listItem" &&
(node.spread ||
// Check if `listItem` ends with `\n`
// since it can't be empty, so we only need check the last character
options.originalText.charAt(node.position.end.offset - 1) === "\n")
);
}
export { printChildren };
+111
View File
@@ -0,0 +1,111 @@
import { align } from "../../document/index.js";
import {
getNthListSiblingIndex,
hasGitDiffFriendlyOrderedList,
} from "../utils.js";
import { printChildren } from "./children.js";
/**
* @import AstPath from "../../common/ast-path.js"
* @import {Doc} from "../../document/index.js"
*/
/**
* @param {AstPath} path
* @param {*} options
* @param {() => Doc} print
* @return {Doc}
*/
function printList(path, options, print) {
const { node } = path;
const nthSiblingIndex = getNthListSiblingIndex(node, path.parent);
const isGitDiffFriendlyOrderedList = hasGitDiffFriendlyOrderedList(
node,
options,
);
return printChildren(path, options, print, {
processor() {
const prefix = getPrefix();
const { node: childNode } = path;
if (
childNode.children.length === 2 &&
childNode.children[1].type === "html" &&
childNode.children[0].position.start.column !==
childNode.children[1].position.start.column
) {
return [prefix, printListItem(path, options, print, prefix)];
}
return [
prefix,
align(
" ".repeat(prefix.length),
printListItem(path, options, print, prefix),
),
];
function getPrefix() {
const rawPrefix = node.ordered
? (path.isFirst
? node.start
: isGitDiffFriendlyOrderedList
? 1
: node.start + path.index) +
(nthSiblingIndex % 2 === 0 ? ". " : ") ")
: nthSiblingIndex % 2 === 0
? "- "
: "* ";
return (node.isAligned ||
/* workaround for https://github.com/remarkjs/remark/issues/315 */ node.hasIndentedCodeblock) &&
node.ordered
? alignListPrefix(rawPrefix, options)
: rawPrefix;
}
},
});
}
function printListItem(path, options, print, listPrefix) {
const { node } = path;
const prefix = node.checked === null ? "" : node.checked ? "[x] " : "[ ] ";
return [
prefix,
printChildren(path, options, print, {
processor({ node, isFirst }) {
if (isFirst && node.type !== "list") {
return align(" ".repeat(prefix.length), print());
}
const alignment = " ".repeat(
clamp(options.tabWidth - listPrefix.length, 0, 3), // 4+ will cause indented code block
);
return [alignment, align(alignment, print())];
},
}),
];
}
function alignListPrefix(prefix, options) {
const additionalSpaces = getAdditionalSpaces();
return (
prefix +
" ".repeat(
additionalSpaces >= 4 ? 0 : additionalSpaces, // 4+ will cause indented code block
)
);
function getAdditionalSpaces() {
const restSpaces = prefix.length % options.tabWidth;
return restSpaces === 0 ? 0 : options.tabWidth - restSpaces;
}
}
function clamp(value, min, max) {
return Math.max(min, Math.min(value, max));
}
export { printList };
+6 -239
View File
@@ -24,6 +24,8 @@ import embed from "./embed.js";
import getVisitorKeys from "./get-visitor-keys.js";
import { locEnd, locStart } from "./loc.js";
import { insertPragma } from "./pragma.js";
import { printChildren } from "./print/children.js";
import { printList } from "./print/list.js";
import { printTable } from "./print/table.js";
import { printParagraph } from "./print-paragraph.js";
import preprocess from "./print-preprocess.js";
@@ -31,10 +33,9 @@ import { printSentence } from "./print-sentence.js";
import { printWhitespace } from "./print-whitespace.js";
import {
getFencedCodeBlockValue,
hasGitDiffFriendlyOrderedList,
INLINE_NODE_TYPES,
INLINE_NODE_WRAPPER_TYPES,
getNthListSiblingIndex,
isAutolink,
isPrettierIgnore,
splitText,
} from "./utils.js";
@@ -42,8 +43,6 @@ import {
* @import {Doc} from "../document/index.js"
*/
const SIBLING_NODE_TYPES = new Set(["listItem", "definition"]);
function prevOrNextWord(path) {
const { previous, next } = path;
const hasPrevOrNextWord =
@@ -285,57 +284,8 @@ function genericPrint(path, options, print) {
isHtmlComment ? hardline : markAsRoot(literalline),
);
}
case "list": {
const nthSiblingIndex = getNthListSiblingIndex(node, path.parent);
const isGitDiffFriendlyOrderedList = hasGitDiffFriendlyOrderedList(
node,
options,
);
return printChildren(path, options, print, {
processor() {
const prefix = getPrefix();
const { node: childNode } = path;
if (
childNode.children.length === 2 &&
childNode.children[1].type === "html" &&
childNode.children[0].position.start.column !==
childNode.children[1].position.start.column
) {
return [prefix, printListItem(path, options, print, prefix)];
}
return [
prefix,
align(
" ".repeat(prefix.length),
printListItem(path, options, print, prefix),
),
];
function getPrefix() {
const rawPrefix = node.ordered
? (path.isFirst
? node.start
: isGitDiffFriendlyOrderedList
? 1
: node.start + path.index) +
(nthSiblingIndex % 2 === 0 ? ". " : ") ")
: nthSiblingIndex % 2 === 0
? "- "
: "* ";
return (node.isAligned ||
/* workaround for https://github.com/remarkjs/remark/issues/315 */ node.hasIndentedCodeblock) &&
node.ordered
? alignListPrefix(rawPrefix, options)
: rawPrefix;
}
},
});
}
case "list":
return printList(path, options, print);
case "thematicBreak": {
const { ancestors } = path;
const counter = ancestors.findIndex((node) => node.type === "list");
@@ -457,65 +407,6 @@ function genericPrint(path, options, print) {
}
}
function printListItem(path, options, print, listPrefix) {
const { node } = path;
const prefix = node.checked === null ? "" : node.checked ? "[x] " : "[ ] ";
return [
prefix,
printChildren(path, options, print, {
processor({ node, isFirst }) {
if (isFirst && node.type !== "list") {
return align(" ".repeat(prefix.length), print());
}
const alignment = " ".repeat(
clamp(options.tabWidth - listPrefix.length, 0, 3), // 4+ will cause indented code block
);
return [alignment, align(alignment, print())];
},
}),
];
}
function alignListPrefix(prefix, options) {
const additionalSpaces = getAdditionalSpaces();
return (
prefix +
" ".repeat(
additionalSpaces >= 4 ? 0 : additionalSpaces, // 4+ will cause indented code block
)
);
function getAdditionalSpaces() {
const restSpaces = prefix.length % options.tabWidth;
return restSpaces === 0 ? 0 : options.tabWidth - restSpaces;
}
}
function getNthListSiblingIndex(node, parentNode) {
return getNthSiblingIndex(
node,
parentNode,
(siblingNode) => siblingNode.ordered === node.ordered,
);
}
function getNthSiblingIndex(node, parentNode, condition) {
let index = -1;
for (const childNode of parentNode.children) {
if (childNode.type === node.type && condition(childNode)) {
index++;
} else {
index = -1;
}
if (childNode === node) {
return index;
}
}
}
function printRoot(path, options, print) {
/** @typedef {{ index: number, offset: number }} IgnorePosition */
/** @type {Array<{start: IgnorePosition, end: IgnorePosition}>} */
@@ -578,36 +469,6 @@ function printRoot(path, options, print) {
});
}
function printChildren(path, options, print, events = {}) {
const { processor = print } = events;
const parts = [];
path.each(() => {
const result = processor(path);
if (result !== false) {
if (parts.length > 0 && shouldPrePrintHardline(path)) {
parts.push(hardline);
if (
shouldPrePrintDoubleHardline(path, options) ||
shouldPrePrintTripleHardline(path)
) {
parts.push(hardline);
}
if (shouldPrePrintTripleHardline(path)) {
parts.push(hardline);
}
}
parts.push(result);
}
}, "children");
return parts;
}
function printIgnoreComment(node) {
if (node.type === "html") {
return node.value;
@@ -623,96 +484,6 @@ function printIgnoreComment(node) {
}
}
/** @return {false | 'next' | 'start' | 'end'} */
function isPrettierIgnore(node) {
let match;
if (node.type === "html") {
match = node.value.match(
/^<!--\s*prettier-ignore(?:-(start|end))?\s*-->$/u,
);
} else {
let comment;
if (node.type === "esComment") {
comment = node;
} else if (
node.type === "paragraph" &&
node.children.length === 1 &&
node.children[0].type === "esComment"
) {
comment = node.children[0];
}
if (comment) {
match = comment.value.match(/^prettier-ignore(?:-(start|end))?$/u);
}
}
return match ? match[1] || "next" : false;
}
function shouldPrePrintHardline({ node, parent }) {
const isInlineNode = INLINE_NODE_TYPES.has(node.type);
const isInlineHTML =
node.type === "html" && INLINE_NODE_WRAPPER_TYPES.has(parent.type);
return !isInlineNode && !isInlineHTML;
}
function isLooseListItem(node, options) {
return (
node.type === "listItem" &&
(node.spread ||
// Check if `listItem` ends with `\n`
// since it can't be empty, so we only need check the last character
options.originalText.charAt(node.position.end.offset - 1) === "\n")
);
}
function shouldPrePrintDoubleHardline({ node, previous, parent }, options) {
if (
isLooseListItem(previous, options) ||
(node.type === "list" &&
parent.type === "listItem" &&
previous.type === "code")
) {
return true;
}
const isSequence = previous.type === node.type;
const isSiblingNode = isSequence && SIBLING_NODE_TYPES.has(node.type);
const isInTightListItem =
parent.type === "listItem" &&
(node.type === "list" || !isLooseListItem(parent, options));
const isPrevNodePrettierIgnore = isPrettierIgnore(previous) === "next";
const isBlockHtmlWithoutBlankLineBetweenPrevHtml =
node.type === "html" &&
previous.type === "html" &&
previous.position.end.line + 1 === node.position.start.line;
const isHtmlDirectAfterListItem =
node.type === "html" &&
parent.type === "listItem" &&
previous.type === "paragraph" &&
previous.position.end.line + 1 === node.position.start.line;
return !(
isSiblingNode ||
isInTightListItem ||
isPrevNodePrettierIgnore ||
isBlockHtmlWithoutBlankLineBetweenPrevHtml ||
isHtmlDirectAfterListItem
);
}
function shouldPrePrintTripleHardline({ node, previous }) {
const isPrevNodeList = previous.type === "list";
const isIndentedCode = node.type === "code" && node.isIndented;
return isPrevNodeList && isIndentedCode;
}
function shouldRemainTheSameContent(path) {
const node = path.findAncestor(
(node) => node.type === "linkReference" || node.type === "imageReference",
@@ -770,10 +541,6 @@ function printTitle(title, options, printSpace = true) {
return `${quote}${title}${quote}`;
}
function clamp(value, min, max) {
return Math.max(min, Math.min(value, max));
}
function hasPrettierIgnore(path) {
return path.index > 0 && isPrettierIgnore(path.previous) === "next";
}
+55
View File
@@ -252,13 +252,68 @@ function isAutolink(node) {
return locStart(node) === locStart(child) && locEnd(node) === locEnd(child);
}
/** @return {false | 'next' | 'start' | 'end'} */
function isPrettierIgnore(node) {
let match;
if (node.type === "html") {
match = node.value.match(
/^<!--\s*prettier-ignore(?:-(start|end))?\s*-->$/u,
);
} else {
let comment;
if (node.type === "esComment") {
comment = node;
} else if (
node.type === "paragraph" &&
node.children.length === 1 &&
node.children[0].type === "esComment"
) {
comment = node.children[0];
}
if (comment) {
match = comment.value.match(/^prettier-ignore(?:-(start|end))?$/u);
}
}
return match ? match[1] || "next" : false;
}
function getNthListSiblingIndex(node, parentNode) {
return getNthSiblingIndex(
node,
parentNode,
(siblingNode) => siblingNode.ordered === node.ordered,
);
function getNthSiblingIndex(node, parentNode, condition) {
let index = -1;
for (const childNode of parentNode.children) {
if (childNode.type === node.type && condition(childNode)) {
index++;
} else {
index = -1;
}
if (childNode === node) {
return index;
}
}
}
}
export {
getFencedCodeBlockValue,
getNthListSiblingIndex,
getOrderedListItemInfo,
hasGitDiffFriendlyOrderedList,
INLINE_NODE_TYPES,
INLINE_NODE_WRAPPER_TYPES,
isAutolink,
isPrettierIgnore,
KIND_CJ_LETTER,
KIND_CJK_PUNCTUATION,
KIND_K_LETTER,