prettierx
Version:
prettierX - a less opinionated fork of the Prettier code formatter
453 lines (421 loc) • 11.5 kB
JavaScript
;
/** @typedef {import("../document").Doc} Doc */
const {
builders: {
breakParent,
fill,
group,
hardline,
join,
line,
lineSuffix,
literalline,
},
utils: { getDocParts, replaceEndOfLineWith },
} = require("../document");
const { isPreviousLineEmpty } = require("../common/util");
const { insertPragma, isPragma } = require("./pragma");
const { locStart } = require("./loc");
const embed = require("./embed");
const {
getFlowScalarLineContents,
getLastDescendantNode,
hasLeadingComments,
hasMiddleComments,
hasTrailingComment,
hasEndComments,
hasPrettierIgnore,
isLastDescendantNode,
isNode,
isInlineNode,
} = require("./utils");
const preprocess = require("./print-preprocess");
const {
alignWithSpaces,
printNextEmptyLine,
shouldPrintEndComments,
} = require("./print/misc");
const {
printFlowMapping,
printFlowSequence,
} = require("./print/flow-mapping-sequence");
const printMappingItem = require("./print/mapping-item");
const printBlock = require("./print/block");
function genericPrint(path, options, print) {
const node = path.getValue();
/** @type {Doc[]} */
const parts = [];
if (node.type !== "mappingValue" && hasLeadingComments(node)) {
parts.push([join(hardline, path.map(print, "leadingComments")), hardline]);
}
const { tag, anchor } = node;
if (tag) {
parts.push(print("tag"));
}
if (tag && anchor) {
parts.push(" ");
}
if (anchor) {
parts.push(print("anchor"));
}
/** @type {Doc} */
let nextEmptyLine = "";
if (
isNode(node, [
"mapping",
"sequence",
"comment",
"directive",
"mappingItem",
"sequenceItem",
]) &&
!isLastDescendantNode(path)
) {
nextEmptyLine = printNextEmptyLine(path, options.originalText);
}
if (tag || anchor) {
if (isNode(node, ["sequence", "mapping"]) && !hasMiddleComments(node)) {
parts.push(hardline);
} else {
parts.push(" ");
}
}
if (hasMiddleComments(node)) {
parts.push([
node.middleComments.length === 1 ? "" : hardline,
join(hardline, path.map(print, "middleComments")),
hardline,
]);
}
const parentNode = path.getParentNode();
if (hasPrettierIgnore(path)) {
parts.push(
replaceEndOfLineWith(
options.originalText
.slice(node.position.start.offset, node.position.end.offset)
.trimEnd(),
literalline
)
);
} else {
parts.push(group(printNode(node, parentNode, path, options, print)));
}
if (hasTrailingComment(node) && !isNode(node, ["document", "documentHead"])) {
parts.push(
lineSuffix([
node.type === "mappingValue" && !node.content ? "" : " ",
parentNode.type === "mappingKey" &&
path.getParentNode(2).type === "mapping" &&
isInlineNode(node)
? ""
: breakParent,
print("trailingComment"),
])
);
}
if (shouldPrintEndComments(node)) {
parts.push(
alignWithSpaces(node.type === "sequenceItem" ? 2 : 0, [
hardline,
join(
hardline,
path.map(
(path) => [
isPreviousLineEmpty(
options.originalText,
path.getValue(),
locStart
)
? hardline
: "",
print(),
],
"endComments"
)
),
])
);
}
parts.push(nextEmptyLine);
return parts;
}
function printNode(node, parentNode, path, options, print) {
switch (node.type) {
case "root": {
const { children } = node;
const parts = [];
path.each((childPath, index) => {
const document = children[index];
const nextDocument = children[index + 1];
if (index !== 0) {
parts.push(hardline);
}
parts.push(print());
if (shouldPrintDocumentEndMarker(document, nextDocument)) {
parts.push(hardline, "...");
if (hasTrailingComment(document)) {
parts.push(" ", print("trailingComment"));
}
} else if (nextDocument && !hasTrailingComment(nextDocument.head)) {
parts.push(hardline, "---");
}
}, "children");
const lastDescendantNode = getLastDescendantNode(node);
if (
!isNode(lastDescendantNode, ["blockLiteral", "blockFolded"]) ||
lastDescendantNode.chomping !== "keep"
) {
parts.push(hardline);
}
return parts;
}
case "document": {
const nextDocument = parentNode.children[path.getName() + 1];
const parts = [];
if (
shouldPrintDocumentHeadEndMarker(
node,
nextDocument,
parentNode,
options
) === "head"
) {
if (node.head.children.length > 0 || node.head.endComments.length > 0) {
parts.push(print("head"));
}
if (hasTrailingComment(node.head)) {
parts.push(["---", " ", print(["head", "trailingComment"])]);
} else {
parts.push("---");
}
}
if (shouldPrintDocumentBody(node)) {
parts.push(print("body"));
}
return join(hardline, parts);
}
case "documentHead":
return join(hardline, [
...path.map(print, "children"),
...path.map(print, "endComments"),
]);
case "documentBody": {
const { children, endComments } = node;
/** @type {Doc} */
let separator = "";
if (children.length > 0 && endComments.length > 0) {
const lastDescendantNode = getLastDescendantNode(node);
// there's already a newline printed at the end of blockValue (chomping=keep, lastDescendant=true)
if (isNode(lastDescendantNode, ["blockFolded", "blockLiteral"])) {
// an extra newline for better readability
if (lastDescendantNode.chomping !== "keep") {
separator = [hardline, hardline];
}
} else {
separator = hardline;
}
}
return [
join(hardline, path.map(print, "children")),
separator,
join(hardline, path.map(print, "endComments")),
];
}
case "directive":
return ["%", join(" ", [node.name, ...node.parameters])];
case "comment":
return ["#", node.value];
case "alias":
return ["*", node.value];
case "tag":
return options.originalText.slice(
node.position.start.offset,
node.position.end.offset
);
case "anchor":
return ["&", node.value];
case "plain":
return printFlowScalarContent(
node.type,
options.originalText.slice(
node.position.start.offset,
node.position.end.offset
),
options
);
case "quoteDouble":
case "quoteSingle": {
const singleQuote = "'";
const doubleQuote = '"';
const raw = options.originalText.slice(
node.position.start.offset + 1,
node.position.end.offset - 1
);
if (
(node.type === "quoteSingle" && raw.includes("\\")) ||
(node.type === "quoteDouble" && /\\[^"]/.test(raw))
) {
// only quoteDouble can use escape chars
// and quoteSingle do not need to escape backslashes
const originalQuote =
node.type === "quoteDouble" ? doubleQuote : singleQuote;
return [
originalQuote,
printFlowScalarContent(node.type, raw, options),
originalQuote,
];
}
if (raw.includes(doubleQuote)) {
return [
singleQuote,
printFlowScalarContent(
node.type,
node.type === "quoteDouble"
? raw
// double quote needs to be escaped by backslash in quoteDouble
.replace(/\\"/g, doubleQuote)
.replace(/'/g, singleQuote.repeat(2))
: raw,
options
),
singleQuote,
];
}
if (raw.includes(singleQuote)) {
return [
doubleQuote,
printFlowScalarContent(
node.type,
node.type === "quoteSingle"
? // single quote needs to be escaped by 2 single quotes in quoteSingle
raw.replace(/''/g, singleQuote)
: raw,
options
),
doubleQuote,
];
}
const quote = options.singleQuote ? singleQuote : doubleQuote;
return [quote, printFlowScalarContent(node.type, raw, options), quote];
}
case "blockFolded":
case "blockLiteral": {
return printBlock(path, print, options);
}
case "mapping":
case "sequence":
return join(hardline, path.map(print, "children"));
case "sequenceItem":
return ["- ", alignWithSpaces(2, !node.content ? "" : print("content"))];
case "mappingKey":
case "mappingValue":
return !node.content ? "" : print("content");
case "mappingItem":
case "flowMappingItem": {
return printMappingItem(node, parentNode, path, print, options);
}
case "flowMapping":
return printFlowMapping(path, print, options);
case "flowSequence":
return printFlowSequence(path, print, options);
case "flowSequenceItem":
return print("content");
// istanbul ignore next
default:
throw new Error(`Unexpected node type ${node.type}`);
}
}
function shouldPrintDocumentBody(document) {
return document.body.children.length > 0 || hasEndComments(document.body);
}
function shouldPrintDocumentEndMarker(document, nextDocument) {
return (
/**
*... # trailingComment
*/
hasTrailingComment(document) ||
(nextDocument &&
/**
* ...
* %DIRECTIVE
* ---
*/
(nextDocument.head.children.length > 0 ||
/**
* ...
* # endComment
* ---
*/
hasEndComments(nextDocument.head)))
);
}
function shouldPrintDocumentHeadEndMarker(
document,
nextDocument,
root,
options
) {
if (
/**
* ---
* preserve the first document head end marker
*/
(root.children[0] === document &&
/---(\s|$)/.test(
options.originalText.slice(locStart(document), locStart(document) + 4)
)) ||
/**
* %DIRECTIVE
* ---
*/
document.head.children.length > 0 ||
/**
* # end comment
* ---
*/
hasEndComments(document.head) ||
/**
* --- # trailing comment
*/
hasTrailingComment(document.head)
) {
return "head";
}
if (shouldPrintDocumentEndMarker(document, nextDocument)) {
return false;
}
return nextDocument ? "root" : false;
}
function printFlowScalarContent(nodeType, content, options) {
const lineContents = getFlowScalarLineContents(nodeType, content, options);
return join(
hardline,
lineContents.map((lineContentWords) =>
fill(getDocParts(join(line, lineContentWords)))
)
);
}
function clean(node, newNode /*, parent */) {
if (isNode(newNode)) {
delete newNode.position;
switch (newNode.type) {
case "comment":
// insert pragma
if (isPragma(newNode.value)) {
return null;
}
break;
case "quoteDouble":
case "quoteSingle":
newNode.type = "quote";
break;
}
}
}
module.exports = {
preprocess,
embed,
print: genericPrint,
massageAstNode: clean,
insertPragma,
};