diff --git a/apps/oxfmt/test/cli/embedded_languages/__snapshots__/embedded_languages.test.ts.snap b/apps/oxfmt/test/cli/embedded_languages/__snapshots__/embedded_languages.test.ts.snap
index d43a0a2667fd1..a1657de4c47df 100644
--- a/apps/oxfmt/test/cli/embedded_languages/__snapshots__/embedded_languages.test.ts.snap
+++ b/apps/oxfmt/test/cli/embedded_languages/__snapshots__/embedded_languages.test.ts.snap
@@ -16,6 +16,19 @@ const mixedTemplate = html\`
Title
\`;
const mixedDocs = md\`#Documentation
This is **important**.\`;
+// Empty - Regular template literals retain newlines and spaces, but embedded ones are condensed
+const empty = css\`\`;
+const empty2 = styled\`
+\`;
+const empty3 = styled.div\` \`;
+const empty4 = gql\` \`;
+const empty5 = html\`
+
+\`;
+const empty6 = md\`
+
+\`;
+
--- AFTER ----------
// Multiple embedded languages in one file
const mixedStyles = css\`
@@ -45,6 +58,14 @@ const mixedDocs = md\`
This is **important**.
\`;
+// Empty - Regular template literals retain newlines and spaces, but embedded ones are condensed
+const empty = css\`\`;
+const empty2 = styled\`\`;
+const empty3 = styled.div\`\`;
+const empty4 = gql\`\`;
+const empty5 = html\`\`;
+const empty6 = md\`\`;
+
--------------------"
`;
@@ -144,7 +165,7 @@ var(--color),
--------------------"
`;
-exports[`embedded_languages > angular.ts > should format (auto) 1`] = `
+exports[`embedded_languages > should format angular.ts (auto) 1`] = `
"--- FILE -----------
angular.ts
--- BEFORE ---------
@@ -250,7 +271,7 @@ export class AppComponent2 {}
--------------------"
`;
-exports[`embedded_languages > css.js > should format (auto) 1`] = `
+exports[`embedded_languages > should format css.js (auto) 1`] = `
"--- FILE -----------
css.js
--- BEFORE ---------
@@ -333,7 +354,7 @@ const styledJsx = (
--------------------"
`;
-exports[`embedded_languages > graphql.js > should format (auto) 1`] = `
+exports[`embedded_languages > should format graphql.js (auto) 1`] = `
"--- FILE -----------
graphql.js
--- BEFORE ---------
@@ -365,7 +386,7 @@ const mutation = graphql\`
--------------------"
`;
-exports[`embedded_languages > html.js > should format (auto) 1`] = `
+exports[`embedded_languages > should format html.js (auto) 1`] = `
"--- FILE -----------
html.js
--- BEFORE ---------
@@ -390,7 +411,7 @@ const component = html\`
--------------------"
`;
-exports[`embedded_languages > markdown.js > should format (auto) 1`] = `
+exports[`embedded_languages > should format markdown.js (auto) 1`] = `
"--- FILE -----------
markdown.js
--- BEFORE ---------
diff --git a/apps/oxfmt/test/cli/embedded_languages/embedded_languages.test.ts b/apps/oxfmt/test/cli/embedded_languages/embedded_languages.test.ts
index dbe9dd9d2e630..6d852e4854397 100644
--- a/apps/oxfmt/test/cli/embedded_languages/embedded_languages.test.ts
+++ b/apps/oxfmt/test/cli/embedded_languages/embedded_languages.test.ts
@@ -6,11 +6,9 @@ const fixturesDir = join(import.meta.dirname, "fixtures");
const languages = ["css.js", "graphql.js", "html.js", "markdown.js", "angular.ts"];
describe("embedded_languages", () => {
- describe.each(languages)("%s", (lang) => {
- it("should format (auto)", async () => {
- const snapshot = await runWriteModeAndSnapshot(fixturesDir, [lang]);
- expect(snapshot).toMatchSnapshot();
- });
+ it.each(languages)(`should format %s (auto)`, async (lang) => {
+ const snapshot = await runWriteModeAndSnapshot(fixturesDir, [lang]);
+ expect(snapshot).toMatchSnapshot();
});
it("should not format any language (off)", async () => {
diff --git a/apps/oxfmt/test/cli/embedded_languages/fixtures/mixed.js b/apps/oxfmt/test/cli/embedded_languages/fixtures/mixed.js
index 72c4c44d543ea..1c5ec4ef9df01 100644
--- a/apps/oxfmt/test/cli/embedded_languages/fixtures/mixed.js
+++ b/apps/oxfmt/test/cli/embedded_languages/fixtures/mixed.js
@@ -9,3 +9,16 @@ const mixedTemplate = html`Title
`;
const mixedDocs = md`#Documentation
This is **important**.`;
+
+// Empty - Regular template literals retain newlines and spaces, but embedded ones are condensed
+const empty = css``;
+const empty2 = styled`
+`;
+const empty3 = styled.div` `;
+const empty4 = gql` `;
+const empty5 = html`
+
+`;
+const empty6 = md`
+
+`;
diff --git a/crates/oxc_formatter/src/print/template.rs b/crates/oxc_formatter/src/print/template.rs
index 406566fbb4585..23cf5cd0156cf 100644
--- a/crates/oxc_formatter/src/print/template.rs
+++ b/crates/oxc_formatter/src/print/template.rs
@@ -782,6 +782,15 @@ fn format_embedded_template<'a>(
language: &str,
template_content: &str,
) -> bool {
+ // If the content is whitespace only,
+ // just trim it and skip calling the embedded formatter
+ if template_content.trim().is_empty() {
+ write!(f, ["``"]);
+ // Return `true` (mark as formatted),
+ // since whitespace-only regular template literals are preserved as-is
+ return true;
+ }
+
let Some(Ok(formatted)) =
f.context().external_callbacks().format_embedded(language, template_content)
else {