prettierx
Version:
prettierX - a less opinionated fork of the Prettier code formatter
273 lines (239 loc) • 7.55 kB
JavaScript
;
const getLast = require("../../utils/get-last");
const { getStringWidth, getIndentSize } = require("../../common/util");
const {
builders: {
join,
hardline,
softline,
// [prettierx] --template-curly-spacing option support (...)
line,
group,
indent,
align,
lineSuffixBoundary,
addAlignmentToDoc,
},
printer: { printDocToString },
utils: { mapDoc },
} = require("../../document");
const {
isBinaryish,
isJestEachTemplateLiteral,
isSimpleTemplateLiteral,
hasComment,
isMemberExpression,
} = require("../utils");
function printTemplateLiteral(path, print, options) {
const node = path.getValue();
const isTemplateLiteral = node.type === "TemplateLiteral";
// [prettierx] --template-curly-spacing option support (...)
const templateCurlySpace = options.templateCurlySpacing ? " " : "";
const templateCurlyLine = options.templateCurlySpacing ? line : softline;
if (
isTemplateLiteral &&
isJestEachTemplateLiteral(node, path.getParentNode())
) {
const printed = printJestEachTemplateLiteral(path, options, print);
if (printed) {
return printed;
}
}
let expressionsKey = "expressions";
if (node.type === "TSTemplateLiteralType") {
expressionsKey = "types";
}
const parts = [];
let expressions = path.map(print, expressionsKey);
const isSimple = isSimpleTemplateLiteral(node);
if (isSimple) {
expressions = expressions.map(
(doc) =>
printDocToString(doc, {
...options,
printWidth: Number.POSITIVE_INFINITY,
}).formatted
);
}
parts.push(lineSuffixBoundary, "`");
path.each((childPath) => {
const i = childPath.getName();
parts.push(print());
if (i < expressions.length) {
// For a template literal of the following form:
// `someQuery {
// ${call({
// a,
// b,
// })}
// }`
// the expression is on its own line (there is a \n in the previous
// quasi literal), therefore we want to indent the JavaScript
// expression inside at the beginning of ${ instead of the beginning
// of the `.
const { tabWidth } = options;
const quasi = childPath.getValue();
const indentSize = getIndentSize(quasi.value.raw, tabWidth);
let printed = expressions[i];
if (!isSimple) {
const expression = node[expressionsKey][i];
// Breaks at the template element boundaries (${ and }) are preferred to breaking
// in the middle of a MemberExpression
if (
hasComment(expression) ||
isMemberExpression(expression) ||
expression.type === "ConditionalExpression" ||
expression.type === "SequenceExpression" ||
expression.type === "TSAsExpression" ||
isBinaryish(expression)
) {
// [prettierx] --template-curly-spacing option support (...)
printed = [indent([templateCurlyLine, printed]), templateCurlyLine];
} else {
// [prettierx] --template-curly-spacing option support (...)
printed = [templateCurlySpace, printed, templateCurlySpace];
}
}
const aligned =
indentSize === 0 && quasi.value.raw.endsWith("\n")
? align(Number.NEGATIVE_INFINITY, printed)
: addAlignmentToDoc(printed, indentSize, tabWidth);
parts.push(group(["${", aligned, lineSuffixBoundary, "}"]));
}
}, "quasis");
parts.push("`");
return parts;
}
function printJestEachTemplateLiteral(path, options, print) {
/**
* a | b | expected
* ${1} | ${1} | ${2}
* ${1} | ${2} | ${3}
* ${2} | ${1} | ${3}
*/
const node = path.getNode();
const headerNames = node.quasis[0].value.raw.trim().split(/\s*\|\s*/);
if (
headerNames.length > 1 ||
headerNames.some((headerName) => headerName.length > 0)
) {
options.__inJestEach = true;
const expressions = path.map(print, "expressions");
options.__inJestEach = false;
const parts = [];
// [prettierx] --template-curly-spacing option support (...)
const templateCurlySpace = options.templateCurlySpacing ? " " : "";
const stringifiedExpressions = expressions.map(
(doc) =>
// [prettierx] --template-curly-spacing option support (...)
"${" +
templateCurlySpace +
printDocToString(doc, {
...options,
printWidth: Number.POSITIVE_INFINITY,
endOfLine: "lf",
}).formatted +
templateCurlySpace +
"}"
);
const tableBody = [{ hasLineBreak: false, cells: [] }];
for (let i = 1; i < node.quasis.length; i++) {
const row = getLast(tableBody);
const correspondingExpression = stringifiedExpressions[i - 1];
row.cells.push(correspondingExpression);
if (correspondingExpression.includes("\n")) {
row.hasLineBreak = true;
}
if (node.quasis[i].value.raw.includes("\n")) {
tableBody.push({ hasLineBreak: false, cells: [] });
}
}
const maxColumnCount = Math.max(
headerNames.length,
...tableBody.map((row) => row.cells.length)
);
const maxColumnWidths = Array.from({ length: maxColumnCount }).fill(0);
const table = [
{ cells: headerNames },
...tableBody.filter((row) => row.cells.length > 0),
];
for (const { cells } of table.filter((row) => !row.hasLineBreak)) {
for (const [index, cell] of cells.entries()) {
maxColumnWidths[index] = Math.max(
maxColumnWidths[index],
getStringWidth(cell)
);
}
}
parts.push(
lineSuffixBoundary,
"`",
indent([
hardline,
join(
hardline,
table.map((row) =>
join(
" | ",
row.cells.map((cell, index) =>
row.hasLineBreak
? cell
: cell +
" ".repeat(maxColumnWidths[index] - getStringWidth(cell))
)
)
)
),
]),
hardline,
"`"
);
return parts;
}
}
// [prettierx] --template-curly-spacing option support (...)
function printTemplateExpression(path, print, options) {
const node = path.getValue();
let printed = print();
if (hasComment(node)) {
printed = group([indent([softline, printed]), softline]);
}
// [prettierx] --template-curly-spacing option support (...)
const templateCurlySpace = options.templateCurlySpacing ? " " : "";
// [prettierx] --template-curly-spacing option support (...)
return [
"${",
templateCurlySpace,
printed,
lineSuffixBoundary,
templateCurlySpace,
"}",
];
}
// [prettierx] --template-curly-spacing option support (...)
function printTemplateExpressions(path, print, options) {
return path.map(
// [prettierx] --template-curly-spacing option support (...)
(path) => printTemplateExpression(path, print, options),
"expressions"
);
}
function escapeTemplateCharacters(doc, raw) {
return mapDoc(doc, (currentDoc) => {
if (typeof currentDoc === "string") {
return raw
? currentDoc.replace(/(\\*)`/g, "$1$1\\`")
: uncookTemplateElementValue(currentDoc);
}
return currentDoc;
});
}
function uncookTemplateElementValue(cookedValue) {
return cookedValue.replace(/([\\`]|\${)/g, "\\$1");
}
module.exports = {
printTemplateLiteral,
printTemplateExpressions,
escapeTemplateCharacters,
uncookTemplateElementValue,
};