prettier/src/language-markdown/preprocess.js

280 lines
6.4 KiB
JavaScript

"use strict";
const { getOrderedListItemInfo, mapAst, splitText } = require("./utils");
// 0x0 ~ 0x10ffff
// eslint-disable-next-line no-control-regex
const isSingleCharRegex = /^([\u0000-\uffff]|[\ud800-\udbff][\udc00-\udfff])$/;
function preprocess(ast, options) {
ast = restoreUnescapedCharacter(ast, options);
ast = mergeContinuousTexts(ast);
ast = transformInlineCode(ast);
ast = transformIndentedCodeblockAndMarkItsParentList(ast, options);
ast = markAlignedList(ast, options);
ast = splitTextIntoSentences(ast, options);
ast = transformImportExport(ast);
ast = mergeContinuousImportExport(ast);
return ast;
}
function transformImportExport(ast) {
return mapAst(ast, node => {
if (node.type !== "import" && node.type !== "export") {
return node;
}
return Object.assign({}, node, { type: "importExport" });
});
}
function transformInlineCode(ast) {
return mapAst(ast, node => {
if (node.type !== "inlineCode") {
return node;
}
return Object.assign({}, node, {
value: node.value.replace(/\s+/g, " ")
});
});
}
function restoreUnescapedCharacter(ast, options) {
return mapAst(ast, node => {
return node.type !== "text"
? node
: Object.assign({}, node, {
value:
node.value !== "*" &&
node.value !== "_" &&
node.value !== "$" && // handle these cases in printer
isSingleCharRegex.test(node.value) &&
node.position.end.offset - node.position.start.offset !==
node.value.length
? options.originalText.slice(
node.position.start.offset,
node.position.end.offset
)
: node.value
});
});
}
function mergeContinuousImportExport(ast) {
return mergeChildren(
ast,
(prevNode, node) =>
prevNode.type === "importExport" && node.type === "importExport",
(prevNode, node) => ({
type: "importExport",
value: prevNode.value + "\n\n" + node.value,
position: {
start: prevNode.position.start,
end: node.position.end
}
})
);
}
function mergeChildren(ast, shouldMerge, mergeNode) {
return mapAst(ast, node => {
if (!node.children) {
return node;
}
const children = node.children.reduce((current, child) => {
const lastChild = current[current.length - 1];
if (lastChild && shouldMerge(lastChild, child)) {
current.splice(-1, 1, mergeNode(lastChild, child));
} else {
current.push(child);
}
return current;
}, []);
return Object.assign({}, node, { children });
});
}
function mergeContinuousTexts(ast) {
return mergeChildren(
ast,
(prevNode, node) => prevNode.type === "text" && node.type === "text",
(prevNode, node) => ({
type: "text",
value: prevNode.value + node.value,
position: {
start: prevNode.position.start,
end: node.position.end
}
})
);
}
function splitTextIntoSentences(ast, options) {
return mapAst(ast, (node, index, [parentNode]) => {
if (node.type !== "text") {
return node;
}
let value = node.value;
if (parentNode.type === "paragraph") {
if (index === 0) {
value = value.trimLeft();
}
if (index === parentNode.children.length - 1) {
value = value.trimRight();
}
}
return {
type: "sentence",
position: node.position,
children: splitText(value, options)
};
});
}
function transformIndentedCodeblockAndMarkItsParentList(ast, options) {
return mapAst(ast, (node, index, parentStack) => {
if (node.type === "code") {
// the first char may point to `\n`, e.g. `\n\t\tbar`, just ignore it
const isIndented = /^\n?( {4,}|\t)/.test(
options.originalText.slice(
node.position.start.offset,
node.position.end.offset
)
);
node.isIndented = isIndented;
if (isIndented) {
for (let i = 0; i < parentStack.length; i++) {
const parent = parentStack[i];
// no need to check checked items
if (parent.hasIndentedCodeblock) {
break;
}
if (parent.type === "list") {
parent.hasIndentedCodeblock = true;
}
}
}
}
return node;
});
}
function markAlignedList(ast, options) {
return mapAst(ast, (node, index, parentStack) => {
if (node.type === "list" && node.children.length !== 0) {
// if one of its parents is not aligned, it's not possible to be aligned in sub-lists
for (let i = 0; i < parentStack.length; i++) {
const parent = parentStack[i];
if (parent.type === "list" && !parent.isAligned) {
node.isAligned = false;
return node;
}
}
node.isAligned = isAligned(node);
}
return node;
});
function getListItemStart(listItem) {
return listItem.children.length === 0
? -1
: listItem.children[0].position.start.column - 1;
}
function isAligned(list) {
if (!list.ordered) {
/**
* - 123
* - 123
*/
return true;
}
const [firstItem, secondItem] = list.children;
const firstInfo = getOrderedListItemInfo(firstItem, options.originalText);
if (firstInfo.leadingSpaces.length > 1) {
/**
* 1. 123
*
* 1. 123
* 1. 123
*/
return true;
}
const firstStart = getListItemStart(firstItem);
if (firstStart === -1) {
/**
* 1.
*
* 1.
* 1.
*/
return false;
}
if (list.children.length === 1) {
/**
* aligned:
*
* 11. 123
*
* not aligned:
*
* 1. 123
*/
return firstStart % options.tabWidth === 0;
}
const secondStart = getListItemStart(secondItem);
if (firstStart !== secondStart) {
/**
* 11. 123
* 1. 123
*
* 1. 123
* 11. 123
*/
return false;
}
if (firstStart % options.tabWidth === 0) {
/**
* 11. 123
* 12. 123
*/
return true;
}
/**
* aligned:
*
* 11. 123
* 1. 123
*
* not aligned:
*
* 1. 123
* 2. 123
*/
const secondInfo = getOrderedListItemInfo(secondItem, options.originalText);
return secondInfo.leadingSpaces.length > 1;
}
}
module.exports = preprocess;