From 04faada98b7484e8892959ac12c23f4dd8c51f57 Mon Sep 17 00:00:00 2001 From: Georgii Dolzhykov Date: Sun, 25 Sep 2022 15:26:14 +0300 Subject: [PATCH] Refactor handling or whitespace-only template literals in JS embed (#13517) * Refactor handling or whitespace-only template literals in JS embed * Rename "format" to "embed" --- src/language-js/embed.js | 69 ++++++++++--------- src/language-js/embed/css.js | 16 +---- src/language-js/embed/graphql.js | 7 +- src/language-js/embed/html.js | 11 +-- src/language-js/embed/markdown.js | 4 +- .../__snapshots__/jsfmt.spec.js.snap | 4 +- 6 files changed, 46 insertions(+), 65 deletions(-) diff --git a/src/language-js/embed.js b/src/language-js/embed.js index 546885f4a..5cda83507 100644 --- a/src/language-js/embed.js +++ b/src/language-js/embed.js @@ -3,10 +3,10 @@ import { CommentCheckFlags, isObjectProperty, } from "./utils/index.js"; -import formatMarkdown from "./embed/markdown.js"; -import formatCss from "./embed/css.js"; -import formatGraphql from "./embed/graphql.js"; -import { formatHtml, formatAngular } from "./embed/html.js"; +import embedMarkdown from "./embed/markdown.js"; +import embedCss from "./embed/css.js"; +import embedGraphQL from "./embed/graphql.js"; +import { embedHtml, embedAngular } from "./embed/html.js"; function embed(path) { const { node } = path; @@ -20,29 +20,42 @@ function embed(path) { return; } + const embedder = getEmbedder(path); + + if (embedder) { + // Special case: whitespace-only template literals + if (node.quasis.length === 1 && node.quasis[0].value.raw.trim() === "") { + return "``"; + } + + return embedder; + } +} + +function getEmbedder(path) { if ( isStyledJsx(path) || isStyledComponents(path) || isCssProp(path) || isAngularComponentStyles(path) ) { - return formatCss; + return embedCss; } if (isGraphQL(path)) { - return formatGraphql; + return embedGraphQL; } if (isHtml(path)) { - return formatHtml; + return embedHtml; } if (isAngularComponentTemplate(path)) { - return formatAngular; + return embedAngular; } if (isMarkdown(path)) { - return formatMarkdown; + return embedMarkdown; } } @@ -50,9 +63,7 @@ function embed(path) { * md`...` * markdown`...` */ -function isMarkdown(path) { - const { node } = path; - const parent = path.getParentNode(); +function isMarkdown({ node, parent }) { return ( parent && parent.type === "TaggedTemplateExpression" && @@ -69,17 +80,14 @@ function isMarkdown(path) { * css.global`` * css.resolve`` */ -function isStyledJsx(path) { - const { node } = path; - const parent = path.getParentNode(); - const parentParent = path.getParentNode(1); +function isStyledJsx({ node, parent, grandparent }) { return ( - (parentParent && + (grandparent && node.quasis && parent.type === "JSXExpressionContainer" && - parentParent.type === "JSXElement" && - parentParent.openingElement.name.name === "style" && - parentParent.openingElement.attributes.some( + grandparent.type === "JSXElement" && + grandparent.openingElement.name.name === "style" && + grandparent.openingElement.attributes.some( (attribute) => attribute.name.name === "jsx" )) || (parent && @@ -145,9 +153,7 @@ const angularComponentObjectExpressionPredicates = [ /** * styled-components template literals */ -function isStyledComponents(path) { - const parent = path.getParentNode(); - +function isStyledComponents({ parent }) { if (!parent || parent.type !== "TaggedTemplateExpression") { return false; } @@ -193,15 +199,13 @@ function isStyledComponents(path) { /** * JSX element with CSS prop */ -function isCssProp(path) { - const parent = path.getParentNode(); - const parentParent = path.getParentNode(1); +function isCssProp({ parent, grandparent }) { return ( - parentParent && + grandparent && parent.type === "JSXExpressionContainer" && - parentParent.type === "JSXAttribute" && - parentParent.name.type === "JSXIdentifier" && - parentParent.name.name === "css" + grandparent.type === "JSXAttribute" && + grandparent.name.type === "JSXIdentifier" && + grandparent.name.name === "css" ); } @@ -223,10 +227,7 @@ function isStyledExtend(node) { * This intentionally excludes Relay Classic tags, as Prettier does not * support Relay Classic formatting. */ -function isGraphQL(path) { - const { node } = path; - const parent = path.getParentNode(); - +function isGraphQL({ node, parent }) { return ( hasLanguageComment(node, "GraphQL") || (parent && diff --git a/src/language-js/embed/css.js b/src/language-js/embed/css.js index 24db302a5..d7547a7f7 100644 --- a/src/language-js/embed/css.js +++ b/src/language-js/embed/css.js @@ -3,7 +3,7 @@ import { indent, hardline, softline } from "../../document/builders.js"; import { mapDoc, replaceEndOfLine, cleanDoc } from "../../document/utils.js"; import { printTemplateExpressions } from "../print/template-literal.js"; -async function format(textToDoc, print, path /*, options*/) { +async function embedCss(textToDoc, print, path /*, options*/) { const { node } = path; // Get full template literal with expressions replaced by placeholders @@ -20,18 +20,8 @@ async function format(textToDoc, print, path /*, options*/) { currVal, "" ); - const doc = await textToDoc(text, { parser: "scss" }); + const quasisDoc = await textToDoc(text, { parser: "scss" }); 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) { @@ -69,4 +59,4 @@ function replacePlaceholders(quasisDoc, expressionDocs) { return expressionDocs.length === replaceCounter ? newDoc : null; } -export default format; +export default embedCss; diff --git a/src/language-js/embed/graphql.js b/src/language-js/embed/graphql.js index 85cac00ab..0a786dae4 100644 --- a/src/language-js/embed/graphql.js +++ b/src/language-js/embed/graphql.js @@ -4,13 +4,10 @@ import { printTemplateExpressions, } from "../print/template-literal.js"; -async function format(textToDoc, print, path /*, options*/) { +async function embedGraphQL(textToDoc, print, path /*, options*/) { const { node } = path; const numQuasis = node.quasis.length; - if (numQuasis === 1 && node.quasis[0].value.raw.trim() === "") { - return "``"; - } const expressionDocs = printTemplateExpressions(path, print); const parts = []; @@ -97,4 +94,4 @@ function printGraphqlComments(lines) { return parts.length === 0 ? null : join(hardline, parts); } -export default format; +export default embedGraphQL; diff --git a/src/language-js/embed/html.js b/src/language-js/embed/html.js index 5632ee919..df10e7972 100644 --- a/src/language-js/embed/html.js +++ b/src/language-js/embed/html.js @@ -7,7 +7,7 @@ import { // The counter is needed to distinguish nested embeds. let htmlTemplateLiteralCounter = 0; -async function format(textToDoc, print, path, options, parser) { +async function embedHtmlLike(parser, textToDoc, print, path, options) { const { node } = path; const counter = htmlTemplateLiteralCounter; htmlTemplateLiteralCounter = (htmlTemplateLiteralCounter + 1) >>> 0; @@ -24,9 +24,6 @@ async function format(textToDoc, print, path, options, parser) { .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; @@ -89,7 +86,5 @@ async function format(textToDoc, print, path, options, parser) { ]); } -export const formatHtml = (textToDoc, print, path, options) => - format(textToDoc, print, path, options, "html"); -export const formatAngular = (textToDoc, print, path, options) => - format(textToDoc, print, path, options, "angular"); +export const embedHtml = embedHtmlLike.bind(undefined, "html"); +export const embedAngular = embedHtmlLike.bind(undefined, "angular"); diff --git a/src/language-js/embed/markdown.js b/src/language-js/embed/markdown.js index 42ede6b1c..df6a5ba41 100644 --- a/src/language-js/embed/markdown.js +++ b/src/language-js/embed/markdown.js @@ -6,7 +6,7 @@ import { } from "../../document/builders.js"; import { escapeTemplateCharacters } from "../print/template-literal.js"; -async function format(textToDoc, print, path /*, options*/) { +async function embedMarkdown(textToDoc, print, path /*, options*/) { const { node } = path; let text = node.quasis[0].value.raw.replace( /((?:\\\\)*)\\`/g, @@ -34,4 +34,4 @@ function getIndentation(str) { return firstMatchedIndent === null ? "" : firstMatchedIndent[1]; } -export default format; +export default embedMarkdown; diff --git a/tests/format/misc/embedded_language_formatting/in-javascript/__snapshots__/jsfmt.spec.js.snap b/tests/format/misc/embedded_language_formatting/in-javascript/__snapshots__/jsfmt.spec.js.snap index ee4fe0670..30d06f125 100644 --- a/tests/format/misc/embedded_language_formatting/in-javascript/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/misc/embedded_language_formatting/in-javascript/__snapshots__/jsfmt.spec.js.snap @@ -65,9 +65,7 @@ css\` } \`; -md\` - -\`; +md\`\`; graphql( \`