# Conflicts: # package.json # src/language-css/printer-postcss.js # src/language-handlebars/printer-glimmer.js # src/language-markdown/clean.js # src/language-markdown/printer-markdown.js # yarn.lock
857 lines
24 KiB
JavaScript
857 lines
24 KiB
JavaScript
|
|
import collapseWhiteSpace from "collapse-white-space";
|
|
import {
|
|
getMinNotPresentContinuousCount,
|
|
getMaxContinuousCount,
|
|
getStringWidth,
|
|
} from "../common/util.js";
|
|
import {
|
|
breakParent,
|
|
join,
|
|
line,
|
|
literalline,
|
|
markAsRoot,
|
|
hardline,
|
|
softline,
|
|
ifBreak,
|
|
fill,
|
|
align,
|
|
indent,
|
|
group,
|
|
hardlineWithoutBreakParent,
|
|
} from "../document/builders.js";
|
|
import { normalizeDoc, replaceEndOfLine } from "../document/utils.js";
|
|
import { printDocToString } from "../document/printer.js";
|
|
import createGetVisitorKeys from "../utils/create-get-visitor-keys.js";
|
|
import UnexpectedNodeError from "../utils/unexpected-node-error.js";
|
|
import embed from "./embed.js";
|
|
import { insertPragma } from "./pragma.js";
|
|
import { locStart, locEnd } from "./loc.js";
|
|
import preprocess from "./print-preprocess.js";
|
|
import clean from "./clean.js";
|
|
import {
|
|
getFencedCodeBlockValue,
|
|
hasGitDiffFriendlyOrderedList,
|
|
splitText,
|
|
punctuationPattern,
|
|
INLINE_NODE_TYPES,
|
|
INLINE_NODE_WRAPPER_TYPES,
|
|
isAutolink,
|
|
getAncestorNode,
|
|
getAncestorCounter,
|
|
} from "./utils.js";
|
|
import visitorKeys from "./visitor-keys.js";
|
|
import { printWhitespace } from "./print-whitespace.js";
|
|
|
|
const getVisitorKeys = createGetVisitorKeys(visitorKeys);
|
|
|
|
/**
|
|
* @typedef {import("../document/builders.js").Doc} Doc
|
|
*/
|
|
|
|
const SIBLING_NODE_TYPES = new Set([
|
|
"listItem",
|
|
"definition",
|
|
"footnoteDefinition",
|
|
]);
|
|
|
|
function genericPrint(path, options, print) {
|
|
const { node } = path;
|
|
|
|
if (shouldRemainTheSameContent(path)) {
|
|
return 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)
|
|
);
|
|
}
|
|
|
|
switch (node.type) {
|
|
case "front-matter":
|
|
return options.originalText.slice(
|
|
node.position.start.offset,
|
|
node.position.end.offset
|
|
);
|
|
case "root":
|
|
/* c8 ignore next 3 */
|
|
if (node.children.length === 0) {
|
|
return "";
|
|
}
|
|
return [normalizeDoc(printRoot(path, options, print)), hardline];
|
|
case "paragraph":
|
|
return printChildren(path, options, print, {
|
|
postprocessor: fill,
|
|
});
|
|
case "sentence":
|
|
return printChildren(path, options, print);
|
|
case "word": {
|
|
let escapedValue = node.value
|
|
.replaceAll("*", "\\*") // escape all `*`
|
|
.replaceAll(
|
|
new RegExp(
|
|
[
|
|
`(^|${punctuationPattern})(_+)`,
|
|
`(_+)(${punctuationPattern}|$)`,
|
|
].join("|"),
|
|
"g"
|
|
),
|
|
(_, text1, underscore1, underscore2, text2) =>
|
|
(underscore1
|
|
? `${text1}${underscore1}`
|
|
: `${underscore2}${text2}`
|
|
).replaceAll("_", "\\_")
|
|
); // escape all `_` except concating with non-punctuation, e.g. `1_2_3` is not considered emphasis
|
|
|
|
const isFirstSentence = (node, name, index) =>
|
|
node.type === "sentence" && index === 0;
|
|
const isLastChildAutolink = (node, name, index) =>
|
|
isAutolink(node.children[index - 1]);
|
|
|
|
if (
|
|
escapedValue !== node.value &&
|
|
(path.match(undefined, isFirstSentence, isLastChildAutolink) ||
|
|
path.match(
|
|
undefined,
|
|
isFirstSentence,
|
|
(node, name, index) => node.type === "emphasis" && index === 0,
|
|
isLastChildAutolink
|
|
))
|
|
) {
|
|
// backslash is parsed as part of autolinks, so we need to remove it
|
|
escapedValue = escapedValue.replace(/^(\\?[*_])+/, (prefix) =>
|
|
prefix.replaceAll("\\", "")
|
|
);
|
|
}
|
|
|
|
return escapedValue;
|
|
}
|
|
case "whitespace": {
|
|
const { next } = path;
|
|
|
|
const proseWrap =
|
|
// leading char that may cause different syntax
|
|
next && /^>|^(?:[*+-]|#{1,6}|\d+[).])$/.test(next.value)
|
|
? "never"
|
|
: options.proseWrap;
|
|
|
|
return printWhitespace(path, node.value, proseWrap);
|
|
}
|
|
case "emphasis": {
|
|
let style;
|
|
if (isAutolink(node.children[0])) {
|
|
style = options.originalText[node.position.start.offset];
|
|
} else {
|
|
const { previous, next } = path;
|
|
const hasPrevOrNextWord = // `1*2*3` is considered emphasis but `1_2_3` is not
|
|
(previous?.type === "sentence" &&
|
|
previous.children.at(-1)?.type === "word" &&
|
|
!previous.children.at(-1).hasTrailingPunctuation) ||
|
|
(next?.type === "sentence" &&
|
|
next.children[0]?.type === "word" &&
|
|
!next.children[0].hasLeadingPunctuation);
|
|
style =
|
|
hasPrevOrNextWord || getAncestorNode(path, "emphasis") ? "*" : "_";
|
|
}
|
|
return [style, printChildren(path, options, print), style];
|
|
}
|
|
case "strong":
|
|
return ["**", printChildren(path, options, print), "**"];
|
|
case "delete":
|
|
return ["~~", printChildren(path, options, print), "~~"];
|
|
case "inlineCode": {
|
|
const code =
|
|
options.proseWrap === "preserve"
|
|
? node.value
|
|
: node.value.replaceAll("\n", " ");
|
|
const backtickCount = getMinNotPresentContinuousCount(code, "`");
|
|
const backtickString = "`".repeat(backtickCount || 1);
|
|
const padding =
|
|
code.startsWith("`") ||
|
|
code.endsWith("`") ||
|
|
(/^[\n ]/.test(code) && /[\n ]$/.test(code) && /[^\n ]/.test(code))
|
|
? " "
|
|
: "";
|
|
return [backtickString, padding, code, padding, backtickString];
|
|
}
|
|
case "wikiLink": {
|
|
let contents = "";
|
|
if (options.proseWrap === "preserve") {
|
|
contents = node.value;
|
|
} else {
|
|
contents = node.value.replaceAll(/[\t\n]+/g, " ");
|
|
}
|
|
|
|
return ["[[", contents, "]]"];
|
|
}
|
|
case "link":
|
|
switch (options.originalText[node.position.start.offset]) {
|
|
case "<": {
|
|
const mailto = "mailto:";
|
|
const url =
|
|
// <hello@example.com> is parsed as { url: "mailto:hello@example.com" }
|
|
node.url.startsWith(mailto) &&
|
|
options.originalText.slice(
|
|
node.position.start.offset + 1,
|
|
node.position.start.offset + 1 + mailto.length
|
|
) !== mailto
|
|
? node.url.slice(mailto.length)
|
|
: node.url;
|
|
return ["<", url, ">"];
|
|
}
|
|
case "[":
|
|
return [
|
|
"[",
|
|
printChildren(path, options, print),
|
|
"](",
|
|
printUrl(node.url, ")"),
|
|
printTitle(node.title, options),
|
|
")",
|
|
];
|
|
default:
|
|
return options.originalText.slice(
|
|
node.position.start.offset,
|
|
node.position.end.offset
|
|
);
|
|
}
|
|
case "image":
|
|
return [
|
|
""),
|
|
printTitle(node.title, options),
|
|
")",
|
|
];
|
|
case "blockquote":
|
|
return ["> ", align("> ", printChildren(path, options, print))];
|
|
case "heading":
|
|
return [
|
|
"#".repeat(node.depth) + " ",
|
|
printChildren(path, options, print),
|
|
];
|
|
case "code": {
|
|
if (node.isIndented) {
|
|
// indented code block
|
|
const alignment = " ".repeat(4);
|
|
return align(alignment, [
|
|
alignment,
|
|
replaceEndOfLine(node.value, hardline),
|
|
]);
|
|
}
|
|
|
|
// fenced code block
|
|
const styleUnit = options.__inJsTemplate ? "~" : "`";
|
|
const style = styleUnit.repeat(
|
|
Math.max(3, getMaxContinuousCount(node.value, styleUnit) + 1)
|
|
);
|
|
return [
|
|
style,
|
|
node.lang || "",
|
|
node.meta ? " " + node.meta : "",
|
|
hardline,
|
|
replaceEndOfLine(
|
|
getFencedCodeBlockValue(node, options.originalText),
|
|
hardline
|
|
),
|
|
hardline,
|
|
style,
|
|
];
|
|
}
|
|
case "html": {
|
|
const { parent, isLast } = path;
|
|
const value =
|
|
parent.type === "root" && isLast ? node.value.trimEnd() : node.value;
|
|
const isHtmlComment = /^<!--.*-->$/s.test(value);
|
|
|
|
return replaceEndOfLine(
|
|
value,
|
|
// @ts-expect-error
|
|
isHtmlComment ? hardline : markAsRoot(literalline)
|
|
);
|
|
}
|
|
case "list": {
|
|
const nthSiblingIndex = getNthListSiblingIndex(node, path.parent);
|
|
|
|
const isGitDiffFriendlyOrderedList = hasGitDiffFriendlyOrderedList(
|
|
node,
|
|
options
|
|
);
|
|
|
|
return printChildren(path, options, print, {
|
|
processor(childPath) {
|
|
const prefix = getPrefix();
|
|
const childNode = childPath.node;
|
|
|
|
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(childPath, options, print, prefix)];
|
|
}
|
|
|
|
return [
|
|
prefix,
|
|
align(
|
|
" ".repeat(prefix.length),
|
|
printListItem(childPath, options, print, prefix)
|
|
),
|
|
];
|
|
|
|
function getPrefix() {
|
|
const rawPrefix = node.ordered
|
|
? (childPath.isFirst
|
|
? node.start
|
|
: isGitDiffFriendlyOrderedList
|
|
? 1
|
|
: node.start + childPath.index) +
|
|
(nthSiblingIndex % 2 === 0 ? ". " : ") ")
|
|
: nthSiblingIndex % 2 === 0
|
|
? "- "
|
|
: "* ";
|
|
|
|
return node.isAligned ||
|
|
/* workaround for https://github.com/remarkjs/remark/issues/315 */ node.hasIndentedCodeblock
|
|
? alignListPrefix(rawPrefix, options)
|
|
: rawPrefix;
|
|
}
|
|
},
|
|
});
|
|
}
|
|
case "thematicBreak": {
|
|
const counter = getAncestorCounter(path, "list");
|
|
if (counter === -1) {
|
|
return "---";
|
|
}
|
|
const nthSiblingIndex = getNthListSiblingIndex(
|
|
path.getParentNode(counter),
|
|
path.getParentNode(counter + 1)
|
|
);
|
|
return nthSiblingIndex % 2 === 0 ? "***" : "---";
|
|
}
|
|
case "linkReference":
|
|
return [
|
|
"[",
|
|
printChildren(path, options, print),
|
|
"]",
|
|
node.referenceType === "full"
|
|
? printLinkReference(node)
|
|
: node.referenceType === "collapsed"
|
|
? "[]"
|
|
: "",
|
|
];
|
|
case "imageReference":
|
|
switch (node.referenceType) {
|
|
case "full":
|
|
return ["![", node.alt || "", "]", printLinkReference(node)];
|
|
default:
|
|
return [
|
|
"![",
|
|
node.alt,
|
|
"]",
|
|
node.referenceType === "collapsed" ? "[]" : "",
|
|
];
|
|
}
|
|
case "definition": {
|
|
const lineOrSpace = options.proseWrap === "always" ? line : " ";
|
|
return group([
|
|
printLinkReference(node),
|
|
":",
|
|
indent([
|
|
lineOrSpace,
|
|
printUrl(node.url),
|
|
node.title === null
|
|
? ""
|
|
: [lineOrSpace, printTitle(node.title, options, false)],
|
|
]),
|
|
]);
|
|
}
|
|
// `footnote` requires `.use(footnotes, {inlineNotes: true})`, we are not using this option
|
|
// https://github.com/remarkjs/remark-footnotes#optionsinlinenotes
|
|
/* c8 ignore next 2 */
|
|
case "footnote":
|
|
return ["[^", printChildren(path, options, print), "]"];
|
|
case "footnoteReference":
|
|
return printFootnoteReference(node);
|
|
case "footnoteDefinition": {
|
|
const shouldInlineFootnote =
|
|
node.children.length === 1 &&
|
|
node.children[0].type === "paragraph" &&
|
|
(options.proseWrap === "never" ||
|
|
(options.proseWrap === "preserve" &&
|
|
node.children[0].position.start.line ===
|
|
node.children[0].position.end.line));
|
|
return [
|
|
printFootnoteReference(node),
|
|
": ",
|
|
shouldInlineFootnote
|
|
? printChildren(path, options, print)
|
|
: group([
|
|
align(
|
|
" ".repeat(4),
|
|
printChildren(path, options, print, {
|
|
processor: ({ isFirst }) =>
|
|
isFirst ? group([softline, print()]) : print(),
|
|
})
|
|
),
|
|
path.next?.type === "footnoteDefinition" ? softline : "",
|
|
]),
|
|
];
|
|
}
|
|
case "table":
|
|
return printTable(path, options, print);
|
|
case "tableCell":
|
|
return printChildren(path, options, print);
|
|
case "break":
|
|
return /\s/.test(options.originalText[node.position.start.offset])
|
|
? [" ", markAsRoot(literalline)]
|
|
: ["\\", hardline];
|
|
case "liquidNode":
|
|
return replaceEndOfLine(node.value, hardline);
|
|
// MDX
|
|
// fallback to the original text if multiparser failed
|
|
// or `embeddedLanguageFormatting: "off"`
|
|
case "import":
|
|
case "export":
|
|
case "jsx":
|
|
return node.value;
|
|
case "esComment":
|
|
return ["{/* ", node.value, " */}"];
|
|
case "math":
|
|
return [
|
|
"$$",
|
|
hardline,
|
|
node.value ? [replaceEndOfLine(node.value, hardline), hardline] : "",
|
|
"$$",
|
|
];
|
|
case "inlineMath":
|
|
// remark-math trims content but we don't want to remove whitespaces
|
|
// since it's very possible that it's recognized as math accidentally
|
|
return options.originalText.slice(locStart(node), locEnd(node));
|
|
|
|
case "tableRow": // handled in "table"
|
|
case "listItem": // handled in "list"
|
|
case "text": // handled in other types
|
|
default:
|
|
/* c8 ignore next */
|
|
throw new UnexpectedNodeError(node, "Markdown");
|
|
}
|
|
}
|
|
|
|
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 printTable(path, options, print) {
|
|
const { node } = path;
|
|
|
|
const columnMaxWidths = [];
|
|
// { [rowIndex: number]: { [columnIndex: number]: {text: string, width: number} } }
|
|
const contents = path.map(
|
|
() =>
|
|
path.map(({ index: columnIndex }) => {
|
|
const text = printDocToString(print(), options).formatted;
|
|
const width = getStringWidth(text);
|
|
columnMaxWidths[columnIndex] = Math.max(
|
|
columnMaxWidths[columnIndex] || 3, // minimum width = 3 (---, :--, :-:, --:)
|
|
width
|
|
);
|
|
return { text, width };
|
|
}, "children"),
|
|
"children"
|
|
);
|
|
|
|
const alignedTable = printTableContents(/* isCompact */ false);
|
|
if (options.proseWrap !== "never") {
|
|
return [breakParent, alignedTable];
|
|
}
|
|
|
|
// Only if the --prose-wrap never is set and it exceeds the print width.
|
|
const compactTable = printTableContents(/* isCompact */ true);
|
|
return [breakParent, group(ifBreak(compactTable, alignedTable))];
|
|
|
|
function printTableContents(isCompact) {
|
|
/** @type{Doc[]} */
|
|
const parts = [printRow(contents[0], isCompact), printAlign(isCompact)];
|
|
if (contents.length > 1) {
|
|
parts.push(
|
|
join(
|
|
hardlineWithoutBreakParent,
|
|
contents
|
|
.slice(1)
|
|
.map((rowContents) => printRow(rowContents, isCompact))
|
|
)
|
|
);
|
|
}
|
|
return join(hardlineWithoutBreakParent, parts);
|
|
}
|
|
|
|
function printAlign(isCompact) {
|
|
const align = columnMaxWidths.map((width, index) => {
|
|
const align = node.align[index];
|
|
const first = align === "center" || align === "left" ? ":" : "-";
|
|
const last = align === "center" || align === "right" ? ":" : "-";
|
|
const middle = isCompact ? "-" : "-".repeat(width - 2);
|
|
return `${first}${middle}${last}`;
|
|
});
|
|
|
|
return `| ${align.join(" | ")} |`;
|
|
}
|
|
|
|
function printRow(rowContents, isCompact) {
|
|
const columns = rowContents.map(({ text, width }, columnIndex) => {
|
|
if (isCompact) {
|
|
return text;
|
|
}
|
|
const spaces = columnMaxWidths[columnIndex] - width;
|
|
const align = node.align[columnIndex];
|
|
let before = 0;
|
|
if (align === "right") {
|
|
before = spaces;
|
|
} else if (align === "center") {
|
|
before = Math.floor(spaces / 2);
|
|
}
|
|
const after = spaces - before;
|
|
return `${" ".repeat(before)}${text}${" ".repeat(after)}`;
|
|
});
|
|
|
|
return `| ${columns.join(" | ")} |`;
|
|
}
|
|
}
|
|
|
|
function printRoot(path, options, print) {
|
|
/** @typedef {{ index: number, offset: number }} IgnorePosition */
|
|
/** @type {Array<{start: IgnorePosition, end: IgnorePosition}>} */
|
|
const ignoreRanges = [];
|
|
|
|
/** @type {IgnorePosition | null} */
|
|
let ignoreStart = null;
|
|
|
|
const { children } = path.node;
|
|
for (const [index, childNode] of children.entries()) {
|
|
switch (isPrettierIgnore(childNode)) {
|
|
case "start":
|
|
if (ignoreStart === null) {
|
|
ignoreStart = { index, offset: childNode.position.end.offset };
|
|
}
|
|
break;
|
|
case "end":
|
|
if (ignoreStart !== null) {
|
|
ignoreRanges.push({
|
|
start: ignoreStart,
|
|
end: { index, offset: childNode.position.start.offset },
|
|
});
|
|
ignoreStart = null;
|
|
}
|
|
break;
|
|
default:
|
|
// do nothing
|
|
break;
|
|
}
|
|
}
|
|
|
|
return printChildren(path, options, print, {
|
|
processor({ index }) {
|
|
if (ignoreRanges.length > 0) {
|
|
const ignoreRange = ignoreRanges[0];
|
|
|
|
if (index === ignoreRange.start.index) {
|
|
return [
|
|
printIgnoreComment(children[ignoreRange.start.index]),
|
|
options.originalText.slice(
|
|
ignoreRange.start.offset,
|
|
ignoreRange.end.offset
|
|
),
|
|
printIgnoreComment(children[ignoreRange.end.index]),
|
|
];
|
|
}
|
|
|
|
if (ignoreRange.start.index < index && index < ignoreRange.end.index) {
|
|
return false;
|
|
}
|
|
|
|
if (index === ignoreRange.end.index) {
|
|
ignoreRanges.shift();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return print();
|
|
},
|
|
});
|
|
}
|
|
|
|
function printChildren(path, options, print, events = {}) {
|
|
const { postprocessor = (parts) => parts, 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) ||
|
|
shouldPrePrintTripleHardline(path)
|
|
) {
|
|
parts.push(hardline);
|
|
}
|
|
|
|
if (shouldPrePrintTripleHardline(path)) {
|
|
parts.push(hardline);
|
|
}
|
|
}
|
|
|
|
parts.push(result);
|
|
}
|
|
}, "children");
|
|
|
|
return postprocessor(parts);
|
|
}
|
|
|
|
function printIgnoreComment(node) {
|
|
if (node.type === "html") {
|
|
return node.value;
|
|
}
|
|
|
|
if (
|
|
node.type === "paragraph" &&
|
|
Array.isArray(node.children) &&
|
|
node.children.length === 1 &&
|
|
node.children[0].type === "esComment"
|
|
) {
|
|
return ["{/* ", node.children[0].value, " */}"];
|
|
}
|
|
}
|
|
|
|
/** @return {false | 'next' | 'start' | 'end'} */
|
|
function isPrettierIgnore(node) {
|
|
let match;
|
|
|
|
if (node.type === "html") {
|
|
match = node.value.match(/^<!--\s*prettier-ignore(?:-(start|end))?\s*-->$/);
|
|
} 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))?$/);
|
|
}
|
|
}
|
|
|
|
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 shouldPrePrintDoubleHardline({ node, previous, parent }) {
|
|
const isSequence = previous.type === node.type;
|
|
const isSiblingNode = isSequence && SIBLING_NODE_TYPES.has(node.type);
|
|
|
|
const isInTightListItem = parent.type === "listItem" && !parent.loose;
|
|
|
|
const isPrevNodeLooseListItem =
|
|
previous.type === "listItem" && previous.loose;
|
|
|
|
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 (
|
|
isPrevNodeLooseListItem ||
|
|
!(
|
|
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 ancestorNode = getAncestorNode(path, [
|
|
"linkReference",
|
|
"imageReference",
|
|
]);
|
|
|
|
return (
|
|
ancestorNode &&
|
|
(ancestorNode.type !== "linkReference" ||
|
|
ancestorNode.referenceType !== "full")
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param {string} url
|
|
* @param {string[] | string} [dangerousCharOrChars]
|
|
* @returns {string}
|
|
*/
|
|
function printUrl(url, dangerousCharOrChars = []) {
|
|
const dangerousChars = [
|
|
" ",
|
|
...(Array.isArray(dangerousCharOrChars)
|
|
? dangerousCharOrChars
|
|
: [dangerousCharOrChars]),
|
|
];
|
|
return new RegExp(dangerousChars.map((x) => `\\${x}`).join("|")).test(url)
|
|
? `<${url}>`
|
|
: url;
|
|
}
|
|
|
|
function printTitle(title, options, printSpace = true) {
|
|
if (!title) {
|
|
return "";
|
|
}
|
|
if (printSpace) {
|
|
return " " + printTitle(title, options, false);
|
|
}
|
|
|
|
// title is escaped after `remark-parse` v7
|
|
title = title.replaceAll(/\\(?=["')])/g, "");
|
|
|
|
if (title.includes('"') && title.includes("'") && !title.includes(")")) {
|
|
return `(${title})`; // avoid escaped quotes
|
|
}
|
|
// faster than using RegExps: https://jsperf.com/performance-of-match-vs-split
|
|
const singleCount = title.split("'").length - 1;
|
|
const doubleCount = title.split('"').length - 1;
|
|
const quote =
|
|
singleCount > doubleCount
|
|
? '"'
|
|
: doubleCount > singleCount
|
|
? "'"
|
|
: options.singleQuote
|
|
? "'"
|
|
: '"';
|
|
title = title.replaceAll("\\", "\\\\");
|
|
title = title.replaceAll(new RegExp(`(${quote})`, "g"), "\\$1");
|
|
return `${quote}${title}${quote}`;
|
|
}
|
|
|
|
function clamp(value, min, max) {
|
|
return value < min ? min : value > max ? max : value;
|
|
}
|
|
|
|
function hasPrettierIgnore(path) {
|
|
return path.index > 0 && isPrettierIgnore(path.previous) === "next";
|
|
}
|
|
|
|
// `remark-parse` lowercase the `label` as `identifier`, we don't want do that
|
|
// https://github.com/remarkjs/remark/blob/daddcb463af2d5b2115496c395d0571c0ff87d15/packages/remark-parse/lib/tokenize/reference.js
|
|
function printLinkReference(node) {
|
|
return `[${collapseWhiteSpace(node.label)}]`;
|
|
}
|
|
|
|
function printFootnoteReference(node) {
|
|
return `[^${node.label}]`;
|
|
}
|
|
|
|
const printer = {
|
|
preprocess,
|
|
print: genericPrint,
|
|
embed,
|
|
massageAstNode: clean,
|
|
hasPrettierIgnore,
|
|
insertPragma,
|
|
getVisitorKeys,
|
|
};
|
|
|
|
export default printer;
|