JS: Refactor embed (#9653)
This commit is contained in:
1 parent
f9f66711b9
commit
dc541cc9b9
7 files changed
+432
-385
No files matched your search
+16
-383
@@ -1,31 +1,20 @@
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
builders: {
|
||||
indent,
|
||||
join,
|
||||
line,
|
||||
hardline,
|
||||
softline,
|
||||
literalline,
|
||||
concat,
|
||||
group,
|
||||
dedentToRoot,
|
||||
lineSuffixBoundary,
|
||||
},
|
||||
utils: { mapDoc, replaceNewlinesWithLiterallines },
|
||||
} = require("../document");
|
||||
const { isBlockComment } = require("./utils");
|
||||
const { hasLeadingComment } = require("./comments");
|
||||
const formatMarkdown = require("./embed/markdown");
|
||||
const formatCss = require("./embed/css");
|
||||
const formatGraphql = require("./embed/graphql");
|
||||
const formatHtml = require("./embed/html");
|
||||
|
||||
function getParser(path) {
|
||||
function getLanguage(path) {
|
||||
if (
|
||||
isStyledJsx(path) ||
|
||||
isStyledComponents(path) ||
|
||||
isCssProp(path) ||
|
||||
isAngularComponentStyles(path)
|
||||
) {
|
||||
return "scss";
|
||||
return "css";
|
||||
}
|
||||
|
||||
if (isGraphQL(path)) {
|
||||
@@ -57,130 +46,25 @@ function embed(path, print, textToDoc, options) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parser = getParser(path);
|
||||
if (!parser) {
|
||||
const language = getLanguage(path);
|
||||
if (!language) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (parser === "markdown") {
|
||||
let text = node.quasis[0].value.raw.replace(
|
||||
/((?:\\\\)*)\\`/g,
|
||||
(_, backslashes) => "\\".repeat(backslashes.length / 2) + "`"
|
||||
);
|
||||
const indentation = getIndentation(text);
|
||||
const hasIndent = indentation !== "";
|
||||
if (hasIndent) {
|
||||
text = text.replace(new RegExp(`^${indentation}`, "gm"), "");
|
||||
}
|
||||
const doc = printMarkdown(text, textToDoc);
|
||||
return concat([
|
||||
"`",
|
||||
hasIndent
|
||||
? indent(concat([softline, doc]))
|
||||
: concat([literalline, dedentToRoot(doc)]),
|
||||
softline,
|
||||
"`",
|
||||
]);
|
||||
if (language === "markdown") {
|
||||
return formatMarkdown(path, print, textToDoc);
|
||||
}
|
||||
|
||||
const expressionDocs = path.map(
|
||||
(path) => printTemplateExpression(path, print),
|
||||
"expressions"
|
||||
);
|
||||
|
||||
if (parser === "scss") {
|
||||
// Get full template literal with expressions replaced by placeholders
|
||||
const rawQuasis = node.quasis.map((q) => q.value.raw);
|
||||
let placeholderID = 0;
|
||||
const text = rawQuasis.reduce((prevVal, currVal, idx) => {
|
||||
return idx === 0
|
||||
? currVal
|
||||
: prevVal +
|
||||
"@prettier-placeholder-" +
|
||||
placeholderID++ +
|
||||
"-id" +
|
||||
currVal;
|
||||
}, "");
|
||||
const doc = textToDoc(text, { parser }, { stripTrailingHardline: true });
|
||||
return transformCssDoc(doc, node, expressionDocs);
|
||||
if (language === "css") {
|
||||
return formatCss(path, print, textToDoc);
|
||||
}
|
||||
|
||||
if (parser === "graphql") {
|
||||
const numQuasis = node.quasis.length;
|
||||
if (numQuasis === 1 && node.quasis[0].value.raw.trim() === "") {
|
||||
return "``";
|
||||
}
|
||||
|
||||
const parts = [];
|
||||
|
||||
for (let i = 0; i < numQuasis; i++) {
|
||||
const templateElement = node.quasis[i];
|
||||
const isFirst = i === 0;
|
||||
const isLast = i === numQuasis - 1;
|
||||
const text = templateElement.value.cooked;
|
||||
|
||||
const lines = text.split("\n");
|
||||
const numLines = lines.length;
|
||||
const expressionDoc = expressionDocs[i];
|
||||
|
||||
const startsWithBlankLine =
|
||||
numLines > 2 && lines[0].trim() === "" && lines[1].trim() === "";
|
||||
const endsWithBlankLine =
|
||||
numLines > 2 &&
|
||||
lines[numLines - 1].trim() === "" &&
|
||||
lines[numLines - 2].trim() === "";
|
||||
|
||||
const commentsAndWhitespaceOnly = lines.every((line) =>
|
||||
/^\s*(?:#[^\n\r]*)?$/.test(line)
|
||||
);
|
||||
|
||||
// Bail out if an interpolation occurs within a comment.
|
||||
if (!isLast && /#[^\n\r]*$/.test(lines[numLines - 1])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let doc = null;
|
||||
|
||||
if (commentsAndWhitespaceOnly) {
|
||||
doc = printGraphqlComments(lines);
|
||||
} else {
|
||||
doc = textToDoc(text, { parser }, { stripTrailingHardline: true });
|
||||
}
|
||||
|
||||
if (doc) {
|
||||
doc = escapeTemplateCharacters(doc, false);
|
||||
if (!isFirst && startsWithBlankLine) {
|
||||
parts.push("");
|
||||
}
|
||||
parts.push(doc);
|
||||
if (!isLast && endsWithBlankLine) {
|
||||
parts.push("");
|
||||
}
|
||||
} else if (!isFirst && !isLast && startsWithBlankLine) {
|
||||
parts.push("");
|
||||
}
|
||||
|
||||
if (expressionDoc) {
|
||||
parts.push(expressionDoc);
|
||||
}
|
||||
}
|
||||
|
||||
return concat([
|
||||
"`",
|
||||
indent(concat([hardline, join(hardline, parts)])),
|
||||
hardline,
|
||||
"`",
|
||||
]);
|
||||
if (language === "graphql") {
|
||||
return formatGraphql(path, print, textToDoc);
|
||||
}
|
||||
|
||||
if (parser === "html" || parser === "angular") {
|
||||
return printHtmlTemplateLiteral(
|
||||
node,
|
||||
expressionDocs,
|
||||
textToDoc,
|
||||
parser,
|
||||
options
|
||||
);
|
||||
if (language === "html" || language === "angular") {
|
||||
return formatHtml(path, print, textToDoc, options, { parser: language });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,153 +84,6 @@ function isMarkdown(path) {
|
||||
);
|
||||
}
|
||||
|
||||
function printTemplateExpression(path, print) {
|
||||
const node = path.getValue();
|
||||
let printed = print(path);
|
||||
if (node.comments && node.comments.length) {
|
||||
printed = group(concat([indent(concat([softline, printed])), softline]));
|
||||
}
|
||||
return concat(["${", printed, lineSuffixBoundary, "}"]);
|
||||
}
|
||||
|
||||
function printMarkdown(text, textToDoc) {
|
||||
const doc = textToDoc(
|
||||
text,
|
||||
{ parser: "markdown", __inJsTemplate: true },
|
||||
{ stripTrailingHardline: true }
|
||||
);
|
||||
return escapeTemplateCharacters(doc, true);
|
||||
}
|
||||
|
||||
function getIndentation(str) {
|
||||
const firstMatchedIndent = str.match(/^([^\S\n]*)\S/m);
|
||||
return firstMatchedIndent === null ? "" : firstMatchedIndent[1];
|
||||
}
|
||||
|
||||
function uncook(cookedValue) {
|
||||
return cookedValue.replace(/([\\`]|\${)/g, "\\$1");
|
||||
}
|
||||
|
||||
function escapeTemplateCharacters(doc, raw) {
|
||||
return mapDoc(doc, (currentDoc) => {
|
||||
if (!currentDoc.parts) {
|
||||
return currentDoc;
|
||||
}
|
||||
|
||||
const parts = currentDoc.parts.map((part) => {
|
||||
if (typeof part === "string") {
|
||||
return raw ? part.replace(/(\\*)`/g, "$1$1\\`") : uncook(part);
|
||||
}
|
||||
|
||||
return part;
|
||||
});
|
||||
|
||||
return { ...currentDoc, parts };
|
||||
});
|
||||
}
|
||||
|
||||
function transformCssDoc(quasisDoc, parentNode, expressionDocs) {
|
||||
const isEmpty =
|
||||
parentNode.quasis.length === 1 && !parentNode.quasis[0].value.raw.trim();
|
||||
if (isEmpty) {
|
||||
return "``";
|
||||
}
|
||||
|
||||
const newDoc = replacePlaceholders(quasisDoc, expressionDocs);
|
||||
/* istanbul ignore if */
|
||||
if (!newDoc) {
|
||||
throw new Error("Couldn't insert all the expressions");
|
||||
}
|
||||
return concat(["`", indent(concat([hardline, newDoc])), softline, "`"]);
|
||||
}
|
||||
|
||||
// Search all the placeholders in the quasisDoc tree
|
||||
// and replace them with the expression docs one by one
|
||||
// returns a new doc with all the placeholders replaced,
|
||||
// or null if it couldn't replace any expression
|
||||
function replacePlaceholders(quasisDoc, expressionDocs) {
|
||||
if (!expressionDocs || !expressionDocs.length) {
|
||||
return quasisDoc;
|
||||
}
|
||||
|
||||
let replaceCounter = 0;
|
||||
const newDoc = mapDoc(quasisDoc, (doc) => {
|
||||
if (!doc || !doc.parts || !doc.parts.length) {
|
||||
return doc;
|
||||
}
|
||||
|
||||
let { parts } = doc;
|
||||
const atIndex = parts.indexOf("@");
|
||||
const placeholderIndex = atIndex + 1;
|
||||
if (
|
||||
atIndex > -1 &&
|
||||
typeof parts[placeholderIndex] === "string" &&
|
||||
parts[placeholderIndex].startsWith("prettier-placeholder")
|
||||
) {
|
||||
// If placeholder is split, join it
|
||||
const at = parts[atIndex];
|
||||
const placeholder = parts[placeholderIndex];
|
||||
const rest = parts.slice(placeholderIndex + 1);
|
||||
parts = parts
|
||||
.slice(0, atIndex)
|
||||
.concat([at + placeholder])
|
||||
.concat(rest);
|
||||
}
|
||||
|
||||
const replacedParts = [];
|
||||
parts.forEach((part) => {
|
||||
if (typeof part !== "string" || !part.includes("@prettier-placeholder")) {
|
||||
replacedParts.push(part);
|
||||
return;
|
||||
}
|
||||
|
||||
// When we have multiple placeholders in one line, like:
|
||||
// ${Child}${Child2}:not(:first-child)
|
||||
part.split(/@prettier-placeholder-(\d+)-id/).forEach((component, idx) => {
|
||||
// The placeholder is always at odd indices
|
||||
if (idx % 2 === 0) {
|
||||
replacedParts.push(replaceNewlinesWithLiterallines(component));
|
||||
return;
|
||||
}
|
||||
|
||||
// The component will always be a number at odd index
|
||||
replacedParts.push(expressionDocs[component]);
|
||||
replaceCounter++;
|
||||
});
|
||||
});
|
||||
return { ...doc, parts: replacedParts };
|
||||
});
|
||||
return expressionDocs.length === replaceCounter ? newDoc : null;
|
||||
}
|
||||
|
||||
function printGraphqlComments(lines) {
|
||||
const parts = [];
|
||||
let seenComment = false;
|
||||
|
||||
lines
|
||||
.map((textLine) => textLine.trim())
|
||||
.forEach((textLine, i, array) => {
|
||||
// Lines are either whitespace only, or a comment (with potential whitespace
|
||||
// around it). Drop whitespace-only lines.
|
||||
if (textLine === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (array[i - 1] === "" && seenComment) {
|
||||
// If a non-first comment is preceded by a blank (whitespace only) line,
|
||||
// add in a blank line.
|
||||
parts.push(concat([hardline, textLine]));
|
||||
} else {
|
||||
parts.push(textLine);
|
||||
}
|
||||
|
||||
seenComment = true;
|
||||
});
|
||||
|
||||
// If `lines` was whitespace only, return `null`.
|
||||
return parts.length === 0 ? null : join(hardline, parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Template literal in these contexts:
|
||||
* <style jsx>{`div{color:red}`}</style>
|
||||
@@ -555,110 +292,6 @@ function isHtml(path) {
|
||||
);
|
||||
}
|
||||
|
||||
// The counter is needed to distinguish nested embeds.
|
||||
let htmlTemplateLiteralCounter = 0;
|
||||
|
||||
function printHtmlTemplateLiteral(
|
||||
node,
|
||||
expressionDocs,
|
||||
textToDoc,
|
||||
parser,
|
||||
options
|
||||
) {
|
||||
const counter = htmlTemplateLiteralCounter;
|
||||
htmlTemplateLiteralCounter = (htmlTemplateLiteralCounter + 1) >>> 0;
|
||||
|
||||
const composePlaceholder = (index) =>
|
||||
`PRETTIER_HTML_PLACEHOLDER_${index}_${counter}_IN_JS`;
|
||||
|
||||
const text = node.quasis
|
||||
.map((quasi, index, quasis) =>
|
||||
index === quasis.length - 1
|
||||
? quasi.value.cooked
|
||||
: quasi.value.cooked + composePlaceholder(index)
|
||||
)
|
||||
.join("");
|
||||
|
||||
if (expressionDocs.length === 0 && text.trim().length === 0) {
|
||||
return "``";
|
||||
}
|
||||
|
||||
const placeholderRegex = new RegExp(composePlaceholder("(\\d+)"), "g");
|
||||
let topLevelCount = 0;
|
||||
|
||||
const contentDoc = mapDoc(
|
||||
textToDoc(
|
||||
text,
|
||||
{
|
||||
parser,
|
||||
__onHtmlRoot(root) {
|
||||
topLevelCount = root.children.length;
|
||||
},
|
||||
},
|
||||
{ stripTrailingHardline: true }
|
||||
),
|
||||
(doc) => {
|
||||
if (typeof doc !== "string") {
|
||||
return doc;
|
||||
}
|
||||
|
||||
const parts = [];
|
||||
|
||||
const components = doc.split(placeholderRegex);
|
||||
for (let i = 0; i < components.length; i++) {
|
||||
let component = components[i];
|
||||
|
||||
if (i % 2 === 0) {
|
||||
if (component) {
|
||||
component = uncook(component);
|
||||
if (options.embeddedInHtml) {
|
||||
component = component.replace(/<\/(script)\b/gi, "<\\/$1");
|
||||
}
|
||||
parts.push(component);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const placeholderIndex = +component;
|
||||
parts.push(expressionDocs[placeholderIndex]);
|
||||
}
|
||||
|
||||
return concat(parts);
|
||||
}
|
||||
);
|
||||
|
||||
const leadingWhitespace = /^\s/.test(text) ? " " : "";
|
||||
const trailingWhitespace = /\s$/.test(text) ? " " : "";
|
||||
|
||||
const linebreak =
|
||||
options.htmlWhitespaceSensitivity === "ignore"
|
||||
? hardline
|
||||
: leadingWhitespace && trailingWhitespace
|
||||
? line
|
||||
: null;
|
||||
|
||||
if (linebreak) {
|
||||
return group(
|
||||
concat([
|
||||
"`",
|
||||
indent(concat([linebreak, group(contentDoc)])),
|
||||
linebreak,
|
||||
"`",
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
return group(
|
||||
concat([
|
||||
"`",
|
||||
leadingWhitespace,
|
||||
topLevelCount > 1 ? indent(group(contentDoc)) : group(contentDoc),
|
||||
trailingWhitespace,
|
||||
"`",
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
function hasInvalidCookedValue({ quasis }) {
|
||||
return quasis.some(({ value: { cooked } }) => cooked === null);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
builders: { indent, hardline, softline, concat },
|
||||
utils: { mapDoc, replaceNewlinesWithLiterallines },
|
||||
} = require("../../document");
|
||||
const { printTemplateExpressions } = require("../print/template-literal");
|
||||
|
||||
function format(path, print, textToDoc) {
|
||||
const node = path.getValue();
|
||||
|
||||
// Get full template literal with expressions replaced by placeholders
|
||||
const rawQuasis = node.quasis.map((q) => q.value.raw);
|
||||
let placeholderID = 0;
|
||||
const text = rawQuasis.reduce((prevVal, currVal, idx) => {
|
||||
return idx === 0
|
||||
? currVal
|
||||
: prevVal + "@prettier-placeholder-" + placeholderID++ + "-id" + currVal;
|
||||
}, "");
|
||||
const doc = textToDoc(
|
||||
text,
|
||||
{ parser: "scss" },
|
||||
{ stripTrailingHardline: true }
|
||||
);
|
||||
const expressionDocs = printTemplateExpressions(path, print);
|
||||
return transformCssDoc(doc, node, expressionDocs);
|
||||
}
|
||||
|
||||
function transformCssDoc(quasisDoc, parentNode, expressionDocs) {
|
||||
const isEmpty =
|
||||
parentNode.quasis.length === 1 && !parentNode.quasis[0].value.raw.trim();
|
||||
if (isEmpty) {
|
||||
return "``";
|
||||
}
|
||||
|
||||
const newDoc = replacePlaceholders(quasisDoc, expressionDocs);
|
||||
/* istanbul ignore if */
|
||||
if (!newDoc) {
|
||||
throw new Error("Couldn't insert all the expressions");
|
||||
}
|
||||
return concat(["`", indent(concat([hardline, newDoc])), softline, "`"]);
|
||||
}
|
||||
|
||||
// Search all the placeholders in the quasisDoc tree
|
||||
// and replace them with the expression docs one by one
|
||||
// returns a new doc with all the placeholders replaced,
|
||||
// or null if it couldn't replace any expression
|
||||
function replacePlaceholders(quasisDoc, expressionDocs) {
|
||||
if (!expressionDocs || !expressionDocs.length) {
|
||||
return quasisDoc;
|
||||
}
|
||||
|
||||
let replaceCounter = 0;
|
||||
const newDoc = mapDoc(quasisDoc, (doc) => {
|
||||
if (!doc || !doc.parts || !doc.parts.length) {
|
||||
return doc;
|
||||
}
|
||||
|
||||
let { parts } = doc;
|
||||
const atIndex = parts.indexOf("@");
|
||||
const placeholderIndex = atIndex + 1;
|
||||
if (
|
||||
atIndex > -1 &&
|
||||
typeof parts[placeholderIndex] === "string" &&
|
||||
parts[placeholderIndex].startsWith("prettier-placeholder")
|
||||
) {
|
||||
// If placeholder is split, join it
|
||||
const at = parts[atIndex];
|
||||
const placeholder = parts[placeholderIndex];
|
||||
const rest = parts.slice(placeholderIndex + 1);
|
||||
parts = parts
|
||||
.slice(0, atIndex)
|
||||
.concat([at + placeholder])
|
||||
.concat(rest);
|
||||
}
|
||||
|
||||
const replacedParts = [];
|
||||
parts.forEach((part) => {
|
||||
if (typeof part !== "string" || !part.includes("@prettier-placeholder")) {
|
||||
replacedParts.push(part);
|
||||
return;
|
||||
}
|
||||
|
||||
// When we have multiple placeholders in one line, like:
|
||||
// ${Child}${Child2}:not(:first-child)
|
||||
part.split(/@prettier-placeholder-(\d+)-id/).forEach((component, idx) => {
|
||||
// The placeholder is always at odd indices
|
||||
if (idx % 2 === 0) {
|
||||
replacedParts.push(replaceNewlinesWithLiterallines(component));
|
||||
return;
|
||||
}
|
||||
|
||||
// The component will always be a number at odd index
|
||||
replacedParts.push(expressionDocs[component]);
|
||||
replaceCounter++;
|
||||
});
|
||||
});
|
||||
return { ...doc, parts: replacedParts };
|
||||
});
|
||||
return expressionDocs.length === replaceCounter ? newDoc : null;
|
||||
}
|
||||
|
||||
module.exports = format;
|
||||
@@ -0,0 +1,114 @@
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
builders: { indent, join, hardline, concat },
|
||||
} = require("../../document");
|
||||
const {
|
||||
escapeTemplateCharacters,
|
||||
printTemplateExpressions,
|
||||
} = require("../print/template-literal");
|
||||
|
||||
function format(path, print, textToDoc) {
|
||||
const node = path.getValue();
|
||||
|
||||
const numQuasis = node.quasis.length;
|
||||
if (numQuasis === 1 && node.quasis[0].value.raw.trim() === "") {
|
||||
return "``";
|
||||
}
|
||||
|
||||
const expressionDocs = printTemplateExpressions(path, print);
|
||||
const parts = [];
|
||||
|
||||
for (let i = 0; i < numQuasis; i++) {
|
||||
const templateElement = node.quasis[i];
|
||||
const isFirst = i === 0;
|
||||
const isLast = i === numQuasis - 1;
|
||||
const text = templateElement.value.cooked;
|
||||
|
||||
const lines = text.split("\n");
|
||||
const numLines = lines.length;
|
||||
const expressionDoc = expressionDocs[i];
|
||||
|
||||
const startsWithBlankLine =
|
||||
numLines > 2 && lines[0].trim() === "" && lines[1].trim() === "";
|
||||
const endsWithBlankLine =
|
||||
numLines > 2 &&
|
||||
lines[numLines - 1].trim() === "" &&
|
||||
lines[numLines - 2].trim() === "";
|
||||
|
||||
const commentsAndWhitespaceOnly = lines.every((line) =>
|
||||
/^\s*(?:#[^\n\r]*)?$/.test(line)
|
||||
);
|
||||
|
||||
// Bail out if an interpolation occurs within a comment.
|
||||
if (!isLast && /#[^\n\r]*$/.test(lines[numLines - 1])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let doc = null;
|
||||
|
||||
if (commentsAndWhitespaceOnly) {
|
||||
doc = printGraphqlComments(lines);
|
||||
} else {
|
||||
doc = textToDoc(
|
||||
text,
|
||||
{ parser: "graphql" },
|
||||
{ stripTrailingHardline: true }
|
||||
);
|
||||
}
|
||||
|
||||
if (doc) {
|
||||
doc = escapeTemplateCharacters(doc, false);
|
||||
if (!isFirst && startsWithBlankLine) {
|
||||
parts.push("");
|
||||
}
|
||||
parts.push(doc);
|
||||
if (!isLast && endsWithBlankLine) {
|
||||
parts.push("");
|
||||
}
|
||||
} else if (!isFirst && !isLast && startsWithBlankLine) {
|
||||
parts.push("");
|
||||
}
|
||||
|
||||
if (expressionDoc) {
|
||||
parts.push(expressionDoc);
|
||||
}
|
||||
}
|
||||
|
||||
return concat([
|
||||
"`",
|
||||
indent(concat([hardline, join(hardline, parts)])),
|
||||
hardline,
|
||||
"`",
|
||||
]);
|
||||
}
|
||||
|
||||
function printGraphqlComments(lines) {
|
||||
const parts = [];
|
||||
let seenComment = false;
|
||||
|
||||
lines
|
||||
.map((textLine) => textLine.trim())
|
||||
.forEach((textLine, i, array) => {
|
||||
// Lines are either whitespace only, or a comment (with potential whitespace
|
||||
// around it). Drop whitespace-only lines.
|
||||
if (textLine === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (array[i - 1] === "" && seenComment) {
|
||||
// If a non-first comment is preceded by a blank (whitespace only) line,
|
||||
// add in a blank line.
|
||||
parts.push(concat([hardline, textLine]));
|
||||
} else {
|
||||
parts.push(textLine);
|
||||
}
|
||||
|
||||
seenComment = true;
|
||||
});
|
||||
|
||||
// If `lines` was whitespace only, return `null`.
|
||||
return parts.length === 0 ? null : join(hardline, parts);
|
||||
}
|
||||
|
||||
module.exports = format;
|
||||
@@ -0,0 +1,109 @@
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
builders: { indent, line, hardline, concat, group },
|
||||
utils: { mapDoc },
|
||||
} = require("../../document");
|
||||
const {
|
||||
printTemplateExpressions,
|
||||
uncookTemplateElementValue,
|
||||
} = require("../print/template-literal");
|
||||
|
||||
// The counter is needed to distinguish nested embeds.
|
||||
let htmlTemplateLiteralCounter = 0;
|
||||
function format(path, print, textToDoc, options, { parser }) {
|
||||
const node = path.getValue();
|
||||
const counter = htmlTemplateLiteralCounter;
|
||||
htmlTemplateLiteralCounter = (htmlTemplateLiteralCounter + 1) >>> 0;
|
||||
|
||||
const composePlaceholder = (index) =>
|
||||
`PRETTIER_HTML_PLACEHOLDER_${index}_${counter}_IN_JS`;
|
||||
|
||||
const text = node.quasis
|
||||
.map((quasi, index, quasis) =>
|
||||
index === quasis.length - 1
|
||||
? quasi.value.cooked
|
||||
: quasi.value.cooked + composePlaceholder(index)
|
||||
)
|
||||
.join("");
|
||||
|
||||
const expressionDocs = printTemplateExpressions(path, print);
|
||||
if (expressionDocs.length === 0 && text.trim().length === 0) {
|
||||
return "``";
|
||||
}
|
||||
|
||||
const placeholderRegex = new RegExp(composePlaceholder("(\\d+)"), "g");
|
||||
let topLevelCount = 0;
|
||||
const doc = textToDoc(
|
||||
text,
|
||||
{
|
||||
parser,
|
||||
__onHtmlRoot(root) {
|
||||
topLevelCount = root.children.length;
|
||||
},
|
||||
},
|
||||
{ stripTrailingHardline: true }
|
||||
);
|
||||
|
||||
const contentDoc = mapDoc(doc, (doc) => {
|
||||
if (typeof doc !== "string") {
|
||||
return doc;
|
||||
}
|
||||
|
||||
const parts = [];
|
||||
|
||||
const components = doc.split(placeholderRegex);
|
||||
for (let i = 0; i < components.length; i++) {
|
||||
let component = components[i];
|
||||
|
||||
if (i % 2 === 0) {
|
||||
if (component) {
|
||||
component = uncookTemplateElementValue(component);
|
||||
if (options.embeddedInHtml) {
|
||||
component = component.replace(/<\/(script)\b/gi, "<\\/$1");
|
||||
}
|
||||
parts.push(component);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const placeholderIndex = +component;
|
||||
parts.push(expressionDocs[placeholderIndex]);
|
||||
}
|
||||
|
||||
return concat(parts);
|
||||
});
|
||||
|
||||
const leadingWhitespace = /^\s/.test(text) ? " " : "";
|
||||
const trailingWhitespace = /\s$/.test(text) ? " " : "";
|
||||
|
||||
const linebreak =
|
||||
options.htmlWhitespaceSensitivity === "ignore"
|
||||
? hardline
|
||||
: leadingWhitespace && trailingWhitespace
|
||||
? line
|
||||
: null;
|
||||
|
||||
if (linebreak) {
|
||||
return group(
|
||||
concat([
|
||||
"`",
|
||||
indent(concat([linebreak, group(contentDoc)])),
|
||||
linebreak,
|
||||
"`",
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
return group(
|
||||
concat([
|
||||
"`",
|
||||
leadingWhitespace,
|
||||
topLevelCount > 1 ? indent(group(contentDoc)) : group(contentDoc),
|
||||
trailingWhitespace,
|
||||
"`",
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = format;
|
||||
@@ -0,0 +1,42 @@
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
builders: { indent, softline, literalline, concat, dedentToRoot },
|
||||
} = require("../../document");
|
||||
const { escapeTemplateCharacters } = require("../print/template-literal");
|
||||
|
||||
function format(path, print, textToDoc) {
|
||||
const node = path.getValue();
|
||||
let text = node.quasis[0].value.raw.replace(
|
||||
/((?:\\\\)*)\\`/g,
|
||||
(_, backslashes) => "\\".repeat(backslashes.length / 2) + "`"
|
||||
);
|
||||
const indentation = getIndentation(text);
|
||||
const hasIndent = indentation !== "";
|
||||
if (hasIndent) {
|
||||
text = text.replace(new RegExp(`^${indentation}`, "gm"), "");
|
||||
}
|
||||
const doc = escapeTemplateCharacters(
|
||||
textToDoc(
|
||||
text,
|
||||
{ parser: "markdown", __inJsTemplate: true },
|
||||
{ stripTrailingHardline: true }
|
||||
),
|
||||
true
|
||||
);
|
||||
return concat([
|
||||
"`",
|
||||
hasIndent
|
||||
? indent(concat([softline, doc]))
|
||||
: concat([literalline, dedentToRoot(doc)]),
|
||||
softline,
|
||||
"`",
|
||||
]);
|
||||
}
|
||||
|
||||
function getIndentation(str) {
|
||||
const firstMatchedIndent = str.match(/^([^\S\n]*)\S/m);
|
||||
return firstMatchedIndent === null ? "" : firstMatchedIndent[1];
|
||||
}
|
||||
|
||||
module.exports = format;
|
||||
@@ -14,6 +14,7 @@ const {
|
||||
addAlignmentToDoc,
|
||||
},
|
||||
printer: { printDocToString },
|
||||
utils: { mapDoc },
|
||||
} = require("../../document");
|
||||
const {
|
||||
isBinaryish,
|
||||
@@ -190,4 +191,49 @@ function printJestEachTemplateLiteral(path, options, print) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = printTemplateLiteral;
|
||||
function printTemplateExpression(path, print) {
|
||||
const node = path.getValue();
|
||||
let printed = print(path);
|
||||
if (node.comments && node.comments.length) {
|
||||
printed = group(concat([indent(concat([softline, printed])), softline]));
|
||||
}
|
||||
return concat(["${", printed, lineSuffixBoundary, "}"]);
|
||||
}
|
||||
|
||||
function printTemplateExpressions(path, print) {
|
||||
return path.map(
|
||||
(path) => printTemplateExpression(path, print),
|
||||
"expressions"
|
||||
);
|
||||
}
|
||||
|
||||
function escapeTemplateCharacters(doc, raw) {
|
||||
return mapDoc(doc, (currentDoc) => {
|
||||
if (!currentDoc.parts) {
|
||||
return currentDoc;
|
||||
}
|
||||
|
||||
const parts = currentDoc.parts.map((part) => {
|
||||
if (typeof part === "string") {
|
||||
return raw
|
||||
? part.replace(/(\\*)`/g, "$1$1\\`")
|
||||
: uncookTemplateElementValue(part);
|
||||
}
|
||||
|
||||
return part;
|
||||
});
|
||||
|
||||
return { ...currentDoc, parts };
|
||||
});
|
||||
}
|
||||
|
||||
function uncookTemplateElementValue(cookedValue) {
|
||||
return cookedValue.replace(/([\\`]|\${)/g, "\\$1");
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
printTemplateLiteral,
|
||||
printTemplateExpressions,
|
||||
escapeTemplateCharacters,
|
||||
uncookTemplateElementValue,
|
||||
};
|
||||
@@ -123,7 +123,7 @@ const {
|
||||
printFunctionParameters,
|
||||
shouldHugFunctionParameters,
|
||||
} = require("./print/function-parameters");
|
||||
const printTemplateLiteral = require("./print/template-literal");
|
||||
const { printTemplateLiteral } = require("./print/template-literal");
|
||||
|
||||
const needsQuoteProps = new WeakMap();
|
||||
|
||||
|
||||
Reference in new issue
Block a user