Fix embedded template literal idempotency (#19725)

This commit is contained in:
fisker Cheung authored and fisker committed 2026-09-16 15:32:11 +08:00
1 parent 103c2e8118
commit b923138612
12 files changed
+405 -13

No files matched your search

+38
View File
@@ -0,0 +1,38 @@
#### Fix embedded template literal idempotency (#19725 by @fisker)
<!-- prettier-ignore -->
```jsx
// Input
const foo = html`
<div>${getText({
value,
})}</div>
`;
// Prettier stable (first format)
const foo = html`
<div>
${getText({
value,
})}
</div>
`;
// Prettier stable (second format)
const foo = html`
<div>
${getText({
value,
})}
</div>
`;
// Prettier main
const foo = html`
<div>
${getText({
value,
})}
</div>
`;
```
+2 -2
View File
@@ -7,7 +7,7 @@ import {
softline,
} from "../../document/index.js";
import isNonEmptyArray from "../../utilities/is-non-empty-array.js";
import { printTemplateExpressions } from "../print/template-literal.js";
import { printEmbeddedTemplateExpressions } from "../print/template-literal.js";
import { isNodeMatches } from "../utilities/is-node-matches.js";
import { isAngularComponentStyles } from "./utilities.js";
@@ -26,7 +26,7 @@ async function printEmbedCss(textToDoc, print, path, options) {
text += raw;
}
const quasisDoc = await textToDoc(text, { parser: "scss" });
const expressionDocs = printTemplateExpressions(path, options, print);
const expressionDocs = printEmbeddedTemplateExpressions(path, options, print);
const newDoc = replacePlaceholders(quasisDoc, expressionDocs);
/* c8 ignore next 3 */
if (!newDoc) {
+2 -2
View File
@@ -1,7 +1,7 @@
import { hardline, indent, join } from "../../document/index.js";
import {
escapeTemplateCharacters,
printTemplateExpressions,
printEmbeddedTemplateExpressions,
} from "../print/template-literal.js";
import { hasLanguageComment } from "./utilities.js";
@@ -10,7 +10,7 @@ async function printEmbedGraphQL(textToDoc, print, path, options) {
const numQuasis = node.quasis.length;
const expressionDocs = printTemplateExpressions(path, options, print);
const expressionDocs = printEmbeddedTemplateExpressions(path, options, print);
const parts = [];
for (let i = 0; i < numQuasis; i++) {
+2 -2
View File
@@ -7,7 +7,7 @@ import {
mapDoc,
} from "../../document/index.js";
import {
printTemplateExpressions,
printEmbeddedTemplateExpressions,
uncookTemplateElementValue,
} from "../print/template-literal.js";
import { hasLanguageComment, isAngularComponentTemplate } from "./utilities.js";
@@ -30,7 +30,7 @@ async function printEmbedHtmlLike(parser, textToDoc, print, path, options) {
)
.join("");
const expressionDocs = printTemplateExpressions(path, options, print);
const expressionDocs = printEmbeddedTemplateExpressions(path, options, print);
const placeholderRegex = new RegExp(
composePlaceholder(String.raw`(\d+)`),
+25 -2
View File
@@ -203,7 +203,12 @@ function getTemplateLiteralExpressionIndent(path, options) {
- `TemplateLiteral`
- `TSTemplateLiteralType` (TypeScript)
*/
function printTemplateExpression(path, options, print) {
function printTemplateExpression(
path,
options,
print,
shouldPreserveIndentation = true,
) {
const { node, index } = path;
let expressionDoc = print();
@@ -249,6 +254,11 @@ function printTemplateExpression(path, options, print) {
expressionDoc = [indent([softline, expressionDoc]), softline];
}
// There is no way to know how the quasis will be printed
if (!shouldPreserveIndentation) {
return group(["${", expressionDoc, lineSuffixBoundary, "}"]);
}
// For a template literal of the following form:
// `someQuery {
// ${call({
@@ -283,6 +293,19 @@ function printTemplateExpressions(path, options, print) {
);
}
function printEmbeddedTemplateExpressions(path, options, print) {
return path.map(
() =>
printTemplateExpression(
path,
options,
print,
/* shouldPreserveIndentation */ false,
),
"expressions",
);
}
function escapeTemplateCharacters(doc, raw) {
return mapDoc(doc, (currentDoc) => {
if (typeof currentDoc === "string") {
@@ -331,8 +354,8 @@ function isJestEachTemplateLiteral({ node, parent }) {
export {
escapeTemplateCharacters,
printEmbeddedTemplateExpressions,
printTaggedTemplateExpression,
printTemplateExpressions,
printTemplateLiteral,
uncookTemplateElementValue,
};
@@ -25,8 +25,6 @@ const unstableTests = new Map(
"typescript/satisfies-operators/comments-unstable.ts",
"jsx/comments/in-attributes.js",
"typescript/import-type/long-module-name/long-module-name4.ts",
// Unstable due to lack of indent information
"js/multiparser-comments/comment-inside.js",
[
"typescript/method-chain/object/issue-17239.ts",
(options) => options.objectWrap !== "collapse",
@@ -0,0 +1,36 @@
const t = html`
<ol>
${items.map(
(entry) => html`
<li>
${entry.children
? html`
<ol>
${entry.children.map(
(child) => html`<li>${child.title}</li>`,
)}
</ol>
`
: entry.title}
</li>
`,
)}
</ol>
`;
export function foo() {
return html`
<div>
<pre>${JSON.stringify({
a: 1,
b: 2,
})}</pre>
</div>
`;
}
const a = html`
${{
c: y,
}}
`;
@@ -0,0 +1,236 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`19518.js format 1`] = `
====================================options=====================================
parsers: ["babel", "flow", "typescript"]
printWidth: 80 (default) |
=====================================input======================================
const t = html\`
<ol>
\${items.map(
(entry) => html\`
<li>
\${entry.children
? html\`
<ol>
\${entry.children.map(
(child) => html\`<li>\${child.title}</li>\`,
)}
</ol>
\`
: entry.title}
</li>
\`,
)}
</ol>
\`;
export function foo() {
return html\`
<div>
<pre>\${JSON.stringify({
a: 1,
b: 2,
})}</pre>
</div>
\`;
}
const a = html\`
\${{
c: y,
}}
\`;
=====================================output=====================================
const t = html\`
<ol>
\${items.map(
(entry) => html\`
<li>
\${
entry.children
? html\`
<ol>
\${entry.children.map(
(child) => html\`<li>\${child.title}</li>\`,
)}
</ol>
\`
: entry.title
}
</li>
\`,
)}
</ol>
\`;
export function foo() {
return html\`
<div>
<pre>
\${JSON.stringify({
a: 1,
b: 2,
})}</pre>
</div>
\`;
}
const a = html\`
\${{
c: y,
}}
\`;
================================================================================
`;
exports[`indention.js format 1`] = `
====================================options=====================================
parsers: ["babel", "flow", "typescript"]
printWidth: 80 (default) |
=====================================input======================================
_ = \`
line
\${
a
// comment
+ b}
line
\`;
_ = String.raw\`
line
\${
a
// comment
+ b}
line
\`;
_ = css\`
a{
color:
\${
a
// comment
+ b}
;
}
\`;
_ = html\`
<div>
\${
a
// comment
+ b}
</div>
\`;
_ = gql\`
\${
a
// comment
+ b}
\`;
=====================================output=====================================
_ = \`
line
\${
a +
// comment
b
}
line
\`;
_ = String.raw\`
line
\${
a +
// comment
b
}
line
\`;
_ = css\`
a {
color: \${
a +
// comment
b
};
}
\`;
_ = html\`
<div>
\${
a +
// comment
b
}
</div>
\`;
_ = gql\`
\${
a +
// comment
b
}
\`;
================================================================================
`;
exports[`indention-2.js format 1`] = `
====================================options=====================================
parsers: ["babel", "flow", "typescript"]
printWidth: 80 (default) |
=====================================input======================================
_ = html\`
<div>
\${
a + //
b
}
</div>
<div><div><div><div>
\${
a + //
b
}
</div></div></div></div>
\`
=====================================output=====================================
_ = html\`
<div>
\${
a + //
b
}
</div>
<div>
<div>
<div>
<div>
\${
a + //
b
}
</div>
</div>
</div>
</div>
\`;
================================================================================
`;
@@ -0,0 +1 @@
runFormatTest(import.meta, ["babel", "flow", "typescript"]);
@@ -0,0 +1,14 @@
_ = html`
<div>
${
a + //
b
}
</div>
<div><div><div><div>
${
a + //
b
}
</div></div></div></div>
`
@@ -0,0 +1,46 @@
_ = `
line
${
a
// comment
+ b}
line
`;
_ = String.raw`
line
${
a
// comment
+ b}
line
`;
_ = css`
a{
color:
${
a
// comment
+ b}
;
}
`;
_ = html`
<div>
${
a
// comment
+ b}
</div>
`;
_ = gql`
${
a
// comment
+ b}
`;
@@ -86,7 +86,7 @@ html\`
\${
foo
/* comment */
}
}
\`;
graphql\`
@@ -99,7 +99,7 @@ graphql\`
\${
foo
/* comment */
}
}
\`;
css\`
@@ -112,7 +112,7 @@ css\`
\${
foo
/* comment */
}
}
\`;
markdown\`\${