Format <script> content in glimmer templates (#20012)

Co-authored-by: fisker <lionkay@gmail.com>
This commit is contained in:
Konv Suuandfisker authored and GitHub committed 2026-09-12 20:31:10 +08:00
1 parent 968c1a62c8
commit 2529b92818
14 files changed
+357 -28

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
#### Support embedded `<script>` (#20012 by @kovsu)
<!-- prettier-ignore -->
```hbs
{{! Input }}
<script>
const a = 1;
console.log( a );
</script>
{{! Prettier stable }}
<script>
const a = 1; console.log( a );
</script>
{{! Prettier main }}
<script>
const a = 1;
console.log(a);
</script>
```
+49 -16
View File
@@ -1,4 +1,40 @@
import { isPlainTextStyleElement } from "./utilities.js";
import { isPlainTextStyleOrScriptElement } from "./utilities.js";
function getAttribute(node, name) {
return node.attributes.find(
(attribute) => attribute.type === "AttrNode" && attribute.name === name,
);
}
function getTextValue(attribute) {
return attribute.value.type === "TextNode"
? attribute.value.chars
: undefined;
}
function getScriptTextToDocOptions(node) {
if (getAttribute(node, "src")) {
return;
}
const typeAttribute = getAttribute(node, "type");
const type = typeAttribute ? getTextValue(typeAttribute) : undefined;
if (!typeAttribute || type === "module" || type === "text/javascript") {
return {
parser: "babel",
__embeddedInHtml: true,
__babelSourceType: type === "module" ? "module" : "script",
};
}
}
function getStyleTextToDocOptions(node) {
const langAttribute = getAttribute(node, "lang");
if (!langAttribute || getTextValue(langAttribute) === "css") {
return { parser: "css" };
}
}
function embed(path /* , options*/) {
const { node } = path;
@@ -9,30 +45,27 @@ function embed(path /* , options*/) {
const { parent } = path;
if (!(isPlainTextStyleElement(parent) && parent.children[0] === node)) {
if (!(
isPlainTextStyleOrScriptElement(parent) && parent.children[0] === node
)) {
return;
}
const languageAttribute = parent.attributes.find(
(attribute) => attribute.type === "AttrNode" && attribute.name === "lang",
);
if (
languageAttribute &&
!(
languageAttribute.value.type === "TextNode" &&
languageAttribute.value.chars === "css"
)
) {
const textToDocOptions =
parent.tag === "style"
? getStyleTextToDocOptions(parent)
: getScriptTextToDocOptions(parent);
if (!textToDocOptions) {
return;
}
return async (textToDoc) => {
const context = node.chars;
if (!context.trim()) {
const content = node.chars;
if (!content.trim()) {
return "";
}
const doc = await textToDoc(context, { parser: "css" });
return doc;
return await textToDoc(content, textToDocOptions);
};
}
+6 -3
View File
@@ -1,5 +1,5 @@
import htmlWhitespace from "../../utilities/html-whitespace.js";
import { isPlainTextStyleElement } from "../utilities.js";
import { isPlainTextStyleOrScriptElement } from "../utilities.js";
function massageAstNode(original, cloned, parent) {
// (Glimmer/HTML) ignore TextNode
@@ -9,8 +9,11 @@ function massageAstNode(original, cloned, parent) {
return null;
}
// CSS will be formatted
if (isPlainTextStyleElement(parent) && parent.children[0] === original) {
// CSS/JS will be formatted
if (
isPlainTextStyleOrScriptElement(parent) &&
parent.children[0] === original
) {
cloned.chars = "";
} else {
cloned.chars = htmlWhitespace.split(trimmed).join(" ");
+10 -6
View File
@@ -20,7 +20,7 @@ import { locEnd, locStart } from "./loc.js";
import { massageAstNode } from "./massage-ast/index.js";
import {
hasPrettierIgnore,
isPlainTextStyleElement,
isPlainTextStyleOrScriptElement,
isVoidElement,
isWhitespaceNode,
} from "./utilities.js";
@@ -60,11 +60,11 @@ function print(path, options, print) {
}
const endingTag = ["</", node.tag, ">"];
const isPlainStyle = isPlainTextStyleElement(node);
const isPlainStyleOrScript = isPlainTextStyleOrScriptElement(node);
if (
node.children.length === 0 ||
((!isWhitespaceSensitive || isPlainStyle) &&
((!isWhitespaceSensitive || isPlainStyleOrScript) &&
node.children.every((node) => isWhitespaceNode(node)))
) {
return [startingTag, endingTag];
@@ -72,7 +72,7 @@ function print(path, options, print) {
const parts = path.map(print, "children");
if (isPlainStyle || !isWhitespaceSensitive) {
if (!isWhitespaceSensitive || isPlainStyleOrScript) {
return [startingTag, indent([softline, ...parts]), softline, endingTag];
}
@@ -163,7 +163,8 @@ function print(path, options, print) {
case "TextNode": {
// Don't format content:
// 1. in `<pre>`,
// 2. in `<style>`
// 2. in `<style>`,
// 3. in `<script>`
let text = node.chars;
@@ -173,7 +174,10 @@ function print(path, options, print) {
return replaceEndOfLine(text);
}
if (isPlainTextStyleElement(parent)) {
if (
isPlainTextStyleOrScriptElement(parent) &&
parent.children[0] === node
) {
text = text.replaceAll(/^\n+/g, "");
text = htmlWhitespace.trimEnd(text);
text = htmlWhitespace.dedentString(text);
+5 -3
View File
@@ -42,8 +42,10 @@ function isPlainTextElement(node) {
);
}
function isPlainTextStyleElement(node) {
return isPlainTextElement(node) && node.tag === "style";
function isPlainTextStyleOrScriptElement(node) {
return (
isPlainTextElement(node) && (node.tag === "style" || node.tag === "script")
);
}
function isPrettierIgnoreNode(node) {
@@ -67,7 +69,7 @@ function hasPrettierIgnore(path) {
export {
hasPrettierIgnore,
isPlainTextStyleElement,
isPlainTextStyleOrScriptElement,
isVoidElement,
isWhitespaceNode,
};
@@ -0,0 +1,181 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`script-tag.hbs format 1`] = `
====================================options=====================================
parsers: ["glimmer"]
printWidth: 80 (default) |
=====================================input======================================
<script></script>
<script> </script>
<script type="module">
import a from "a" ;
</script>
<script>
// Should not support TLA in script without \`[type="module"]\`
await shouldNotFormat ( a );
</script>
<script type="module">
// TLA
await shouldFormat ( a );
</script>
<script type="module">
<!--Should not support HTML comment in \`script[type="module"]\`
shouldNotFormat ( a );
-->
</script>
<script>
<!--html comment
shouldFormat ( a );
-->
</script>
<script>
const template = html\`<div><script>shouldFormat ( )<\\/script></div>\`;
</script>
=====================================output=====================================
<script></script>
<script></script>
<script type="module">
import a from "a";
</script>
<script>
// Should not support TLA in script without \`[type="module"]\`
await shouldNotFormat ( a );
</script>
<script type="module">
// TLA
await shouldFormat(a);
</script>
<script type="module">
<!--Should not support HTML comment in \`script[type="module"]\`
shouldNotFormat ( a );
-->
</script>
<script>
<!--html comment
shouldFormat(a);
-->
</script>
<script>
const template = html\`<div>
<script>
shouldFormat();
<\\/script>
</div>\`;
</script>
================================================================================
`;
exports[`type-attribute.hbs format 1`] = `
====================================options=====================================
parsers: ["glimmer"]
printWidth: 80 (default) |
=====================================input======================================
<script>
shouldFormat ( );
</script>
<script type="text/javascript">
shouldFormat ( );
</script>
<script type>
shouldNotFormat ( );
</script>
<script type="">
shouldNotFormat ( );
</script>
<script type="unknown">
shouldNotFormat ( );
</script>
<script type="application/ld+json">
{ "should" : "not format" }
</script>
=====================================output=====================================
<script>
shouldFormat();
</script>
<script type="text/javascript">
shouldFormat();
</script>
<script type>
shouldNotFormat ( );
</script>
<script type="">
shouldNotFormat ( );
</script>
<script type="unknown">
shouldNotFormat ( );
</script>
<script type="application/ld+json">
{ "should" : "not format" }
</script>
================================================================================
`;
exports[`with-mustache.hbs format 1`] = `
====================================options=====================================
parsers: ["glimmer"]
printWidth: 80 (default) |
=====================================input======================================
<script>
shouldNotFormat( {{foo}} )
</script>
<script>
shouldNotFormat( '{{user.name}}' )
{{#if debug}}shouldNotFormat( 'debug' ){{/if}}
</script>
<script>{{foo}}</script>
=====================================output=====================================
<script>
shouldNotFormat(
{{foo}}
)
</script>
<script>
shouldNotFormat( '{{user.name}}' )
{{#if debug}}shouldNotFormat( 'debug' ){{/if}}
</script>
<script>{{foo}}</script>
================================================================================
`;
exports[`with-src.hbs format 1`] = `
====================================options=====================================
parsers: ["glimmer"]
printWidth: 80 (default) |
=====================================input======================================
<script src="main.js"></script>
<script src="main.js">shouldNotFormat ( );</script>
<script src>shouldNotFormat ( );</script>
<script src="">shouldNotFormat ( );</script>
=====================================output=====================================
<script src="main.js"></script>
<script src="main.js">
shouldNotFormat ( );
</script>
<script src>
shouldNotFormat ( );
</script>
<script src="">
shouldNotFormat ( );
</script>
================================================================================
`;
@@ -0,0 +1,16 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`test.hbs - {"embeddedLanguageFormatting":"off"} format 1`] = `
====================================options=====================================
embeddedLanguageFormatting: "off"
parsers: ["glimmer"]
printWidth: 80 (default) |
=====================================input======================================
<script>
call ( );
</script>
=====================================output=====================================
<script>call ( );</script>
================================================================================
`;
@@ -0,0 +1 @@
runFormatTest(import.meta, ["glimmer"], { embeddedLanguageFormatting: "off" });
@@ -0,0 +1,3 @@
<script>
call ( );
</script>
@@ -0,0 +1 @@
runFormatTest(import.meta, ["glimmer"]);
@@ -0,0 +1,31 @@
<script></script>
<script> </script>
<script type="module">
import a from "a" ;
</script>
<script>
// Should not support TLA in script without `[type="module"]`
await shouldNotFormat ( a );
</script>
<script type="module">
// TLA
await shouldFormat ( a );
</script>
<script type="module">
<!--Should not support HTML comment in `script[type="module"]`
shouldNotFormat ( a );
-->
</script>
<script>
<!--html comment
shouldFormat ( a );
-->
</script>
<script>
const template = html`<div><script>shouldFormat ( )<\/script></div>`;
</script>
@@ -0,0 +1,21 @@
<script>
shouldFormat ( );
</script>
<script type="text/javascript">
shouldFormat ( );
</script>
<script type>
shouldNotFormat ( );
</script>
<script type="">
shouldNotFormat ( );
</script>
<script type="unknown">
shouldNotFormat ( );
</script>
<script type="application/ld+json">
{ "should" : "not format" }
</script>
@@ -0,0 +1,8 @@
<script>
shouldNotFormat( {{foo}} )
</script>
<script>
shouldNotFormat( '{{user.name}}' )
{{#if debug}}shouldNotFormat( 'debug' ){{/if}}
</script>
<script>{{foo}}</script>
@@ -0,0 +1,4 @@
<script src="main.js"></script>
<script src="main.js">shouldNotFormat ( );</script>
<script src>shouldNotFormat ( );</script>
<script src="">shouldNotFormat ( );</script>