Format <style> containing mustaches

This commit is contained in:
Konv Suu committed 2026-09-13 17:23:36 +08:00
1 parent 891accaaab
commit 05b8f7a0a8
8 files changed
+264 -67

No files matched your search

+58 -10
View File
@@ -1,4 +1,8 @@
import { isPlainTextStyleOrScriptElement } from "./utilities.js"; import { indent, replaceEndOfLine, softline } from "../document/index.js";
import { replacePlaceholders } from "../language-js/embed/css.js";
import { locEnd, locStart } from "./loc.js";
import { printStartingTag } from "./print/tag.js";
import { isPlainTextElement } from "./utilities.js";
function getAttribute(node, name) { function getAttribute(node, name) {
return node.attributes.find( return node.attributes.find(
@@ -29,16 +33,61 @@ function getScriptTextToDocOptions(node) {
} }
} }
function getStyleTextToDocOptions(node) { function isEmbedCss(node) {
const langAttribute = getAttribute(node, "lang"); if (node.type !== "ElementNode" || node.tag !== "style") {
if (!langAttribute || getTextValue(langAttribute) === "css") { return false;
return { parser: "css" };
} }
const langAttribute = getAttribute(node, "lang");
return !langAttribute || getTextValue(langAttribute) === "css";
}
async function printEmbedCss(textToDoc, print, path, options) {
const { node } = path;
let text = "";
const mustacheDocs = [];
for (const child of node.children) {
if (child.type === "TextNode") {
text += child.chars;
continue;
}
text += `@prettier-placeholder-${mustacheDocs.length}-id`;
mustacheDocs.push(
replaceEndOfLine(
options.originalText.slice(locStart(child), locEnd(child)),
),
);
}
if (!text.trim()) {
return [printStartingTag(path, options, print), "</style>"];
}
const doc = await textToDoc(text, { parser: "css" });
const contentDoc = replacePlaceholders(doc, mustacheDocs);
/* c8 ignore next 3 */
if (!contentDoc) {
throw new Error("Couldn't insert all the mustaches");
}
return [
printStartingTag(path, options, print),
indent([softline, contentDoc]),
softline,
"</style>",
];
} }
function embed(path /* , options*/) { function embed(path /* , options*/) {
const { node } = path; const { node } = path;
if (isEmbedCss(node)) {
return printEmbedCss;
}
if (node.type !== "TextNode") { if (node.type !== "TextNode") {
return; return;
} }
@@ -46,15 +95,14 @@ function embed(path /* , options*/) {
const { parent } = path; const { parent } = path;
if (!( if (!(
isPlainTextStyleOrScriptElement(parent) && parent.children[0] === node isPlainTextElement(parent) &&
parent.tag === "script" &&
parent.children[0] === node
)) { )) {
return; return;
} }
const textToDocOptions = const textToDocOptions = getScriptTextToDocOptions(parent);
parent.tag === "style"
? getStyleTextToDocOptions(parent)
: getScriptTextToDocOptions(parent);
if (!textToDocOptions) { if (!textToDocOptions) {
return; return;
+5 -3
View File
@@ -1,5 +1,5 @@
import htmlWhitespace from "../../utilities/html-whitespace.js"; import htmlWhitespace from "../../utilities/html-whitespace.js";
import { isPlainTextStyleOrScriptElement } from "../utilities.js"; import { isPlainTextElement } from "../utilities.js";
function massageAstNode(original, cloned, parent) { function massageAstNode(original, cloned, parent) {
// (Glimmer/HTML) ignore TextNode // (Glimmer/HTML) ignore TextNode
@@ -11,8 +11,10 @@ function massageAstNode(original, cloned, parent) {
// CSS/JS will be formatted // CSS/JS will be formatted
if ( if (
isPlainTextStyleOrScriptElement(parent) && (parent.type === "ElementNode" && parent.tag === "style") ||
parent.children[0] === original (isPlainTextElement(parent) &&
parent.tag === "script" &&
parent.children[0] === original)
) { ) {
cloned.chars = ""; cloned.chars = "";
} else { } else {
+58
View File
@@ -0,0 +1,58 @@
import {
group,
ifBreak,
indent,
line,
softline,
} from "../../document/index.js";
import isNonEmptyArray from "../../utilities/is-non-empty-array.js";
import { locStart } from "../loc.js";
import { isVoidElement } from "../utilities.js";
/* ElementNode print helpers */
function sortByLoc(a, b) {
return locStart(a) - locStart(b);
}
function printStartingTag(path, options, print) {
const { node } = path;
const types = ["attributes", "modifiers", "comments"].filter((property) =>
isNonEmptyArray(node[property]),
);
const attributes = types.flatMap((type) => node[type]).sort(sortByLoc);
for (const attributeType of types) {
path.each(({ node }) => {
const index = attributes.indexOf(node);
attributes[index] = [line, print()];
}, attributeType);
}
if (isNonEmptyArray(node.blockParams)) {
attributes.push(line, printBlockParams(node));
}
return [
options.htmlWhitespaceSensitivity === "ignore" &&
path.previous?.type === "ElementNode"
? softline
: "",
group(["<", node.tag, indent(attributes), printStartingTagEndMarker(node)]),
];
}
function printStartingTagEndMarker(node) {
if (isVoidElement(node)) {
return ifBreak([softline, "/>"], [" />", softline]);
}
return ifBreak([softline, ">"], ">");
}
function printBlockParams(node) {
return ["as |", node.blockParams.join(" "), "|"];
}
export { printBlockParams, printStartingTag };
+2 -47
View File
@@ -3,7 +3,6 @@ import {
fill, fill,
group, group,
hardline, hardline,
ifBreak,
indent, indent,
join, join,
line, line,
@@ -18,6 +17,7 @@ import embed from "./embed.js";
import getVisitorKeys from "./get-visitor-keys.js"; import getVisitorKeys from "./get-visitor-keys.js";
import { locEnd, locStart } from "./loc.js"; import { locEnd, locStart } from "./loc.js";
import { massageAstNode } from "./massage-ast/index.js"; import { massageAstNode } from "./massage-ast/index.js";
import { printBlockParams, printStartingTag } from "./print/tag.js";
import { import {
hasPrettierIgnore, hasPrettierIgnore,
isPlainTextStyleOrScriptElement, isPlainTextStyleOrScriptElement,
@@ -48,12 +48,7 @@ function print(path, options, print) {
const isWhitespaceSensitive = const isWhitespaceSensitive =
options.htmlWhitespaceSensitivity !== "ignore"; options.htmlWhitespaceSensitivity !== "ignore";
const startingTag = [ const startingTag = printStartingTag(path, options, print);
!isWhitespaceSensitive && path.previous?.type === "ElementNode"
? softline
: "",
group([printStartingTag(path, print)]),
];
if (isVoidElement(node)) { if (isVoidElement(node)) {
return [startingTag]; return [startingTag];
@@ -424,42 +419,6 @@ function print(path, options, print) {
} }
} }
/* ElementNode print helpers */
function sortByLoc(a, b) {
return locStart(a) - locStart(b);
}
function printStartingTag(path, print) {
const { node } = path;
const types = ["attributes", "modifiers", "comments"].filter((property) =>
isNonEmptyArray(node[property]),
);
const attributes = types.flatMap((type) => node[type]).sort(sortByLoc);
for (const attributeType of types) {
path.each(({ node }) => {
const index = attributes.indexOf(node);
attributes[index] = [line, print()];
}, attributeType);
}
if (isNonEmptyArray(node.blockParams)) {
attributes.push(line, printBlockParams(node));
}
return ["<", node.tag, indent(attributes), printStartingTagEndMarker(node)];
}
function printStartingTagEndMarker(node) {
if (isVoidElement(node)) {
return ifBreak([softline, "/>"], [" />", softline]);
}
return ifBreak([softline, ">"], ">");
}
/* MustacheStatement print helpers */ /* MustacheStatement print helpers */
function printOpeningMustache(node) { function printOpeningMustache(node) {
@@ -801,10 +760,6 @@ function printParams(path, print) {
return join(line, parts); return join(line, parts);
} }
function printBlockParams(node) {
return ["as |", node.blockParams.join(" "), "|"];
}
// https://handlebarsjs.com/guide/expressions.html#literal-segments // https://handlebarsjs.com/guide/expressions.html#literal-segments
const PATH_EXPRESSION_FORBIDDEN_CHARACTERS = new Set( const PATH_EXPRESSION_FORBIDDEN_CHARACTERS = new Set(
"!\"#%&'()*+,./;<=>@[\\]^`{|}~", "!\"#%&'()*+,./;<=>@[\\]^`{|}~",
+1
View File
@@ -69,6 +69,7 @@ function hasPrettierIgnore(path) {
export { export {
hasPrettierIgnore, hasPrettierIgnore,
isPlainTextElement,
isPlainTextStyleOrScriptElement, isPlainTextStyleOrScriptElement,
isVoidElement, isVoidElement,
isWhitespaceNode, isWhitespaceNode,
+1 -1
View File
@@ -171,4 +171,4 @@ const isEmbedCss = (path /* , options*/) =>
isCssProp(path) || isCssProp(path) ||
isAngularComponentStyles(path); isAngularComponentStyles(path);
export { isEmbedCss, printEmbedCss }; export { isEmbedCss, printEmbedCss, replacePlaceholders };
@@ -218,19 +218,113 @@ parsers: ["glimmer"]
<style>{{foo}}</style> <style>{{foo}}</style>
=====================================output=====================================
<style> <style>
a { color: .{{className}} { width:{{width}}px;background:url({{url}}) }
{{foo}} </style>
<style>
a::after { content: "{{content}}" }
</style>
<style>
/* {{comment}} */
a { color:red }
</style>
<style>
@media (max-width: {{breakpoint}}px) { a { color: red } }
</style>
<style>
a {
{{mixin}}
color: red;
} }
</style> </style>
<style> <style>
.a { color: a {
{{color}}; } {{mixin}};
color: red;
}
</style>
<style>
.a { color:red }
{{#if debug}}
.b {color:red}
.c {color:red}
{{/if}}
</style>
=====================================output=====================================
<style>
a {
color: {{foo}};
}
</style>
<style>
.a {
color: {{color}};
}
{{#if debug}}.b { color: red; }{{/if}} {{#if debug}}.b { color: red; }{{/if}}
</style> </style>
<style>{{foo}}</style> <style>
{{foo}}
</style>
<style>
.{{className}} {
width: {{width}}px;
background: url({{url}});
}
</style>
<style>
a::after {
content: "{{content}}";
}
</style>
<style>
/* {{comment}} */
a {
color: red;
}
</style>
<style>
@media (max-width: {{breakpoint}}px) {
a {
color: red;
}
}
</style>
<style>
a {
{{mixin}}
color: red;
}
</style>
<style>
a {
{{mixin}};
color: red;
}
</style>
<style>
.a {
color: red;
}
{{#if debug}}
.b {color:red}
.c {color:red}
{{/if}}
</style>
================================================================================ ================================================================================
`; `;
@@ -10,3 +10,42 @@
</style> </style>
<style>{{foo}}</style> <style>{{foo}}</style>
<style>
.{{className}} { width:{{width}}px;background:url({{url}}) }
</style>
<style>
a::after { content: "{{content}}" }
</style>
<style>
/* {{comment}} */
a { color:red }
</style>
<style>
@media (max-width: {{breakpoint}}px) { a { color: red } }
</style>
<style>
a {
{{mixin}}
color: red;
}
</style>
<style>
a {
{{mixin}};
color: red;
}
</style>
<style>
.a { color:red }
{{#if debug}}
.b {color:red}
.c {color:red}
{{/if}}
</style>