diff --git a/.github/ISSUE_TEMPLATE/formatting.md b/.github/ISSUE_TEMPLATE/formatting.md
index 7849d0248faa..823cb8b4b772 100644
--- a/.github/ISSUE_TEMPLATE/formatting.md
+++ b/.github/ISSUE_TEMPLATE/formatting.md
@@ -26,7 +26,7 @@ Don't fill the form below manually! Let a program create a report for you:
-->
-**Prettier 3.9.6**
+**Prettier 3.9.7**
[Playground link](https://prettier.io/playground/#.....)
```sh
diff --git a/.github/ISSUE_TEMPLATE/integration.md b/.github/ISSUE_TEMPLATE/integration.md
index 9d78e6d82895..70501cfcf654 100644
--- a/.github/ISSUE_TEMPLATE/integration.md
+++ b/.github/ISSUE_TEMPLATE/integration.md
@@ -20,7 +20,7 @@ BEFORE SUBMITTING AN ISSUE:
**Environments:**
-- Prettier Version: 3.9.6
+- Prettier Version: 3.9.7
- Running Prettier via:
- Runtime:
- Operating System:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0f8737f13220..dea72898b210 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,197 @@
+# 3.9.7
+
+[diff](https://github.com/prettier/prettier/compare/3.9.6...3.9.7)
+
+#### Markdown: Prevent indentation drift in list-item code blocks ([#19647](https://github.com/prettier/prettier/pull/19647), [#19990](https://github.com/prettier/prettier/pull/19990) by [@Austin1serb](https://github.com/Austin1serb), [@giaBaoJS](https://github.com/giaBaoJS))
+
+
+```markdown
+
+- [x] short first line.
+
+ second paragraph at six spaces that wraps
+ onto another line here.
+
+
+- [x] short first line.
+
+ second paragraph at six spaces that wraps
+ onto another line here.
+
+
+- [x] short first line.
+
+ second paragraph at six spaces that wraps
+ onto another line here.
+```
+
+#### JavaScript: Fix embedded template literal idempotency ([#19725](https://github.com/prettier/prettier/pull/19725) by [@fisker](https://github.com/fisker))
+
+
+```jsx
+// Input
+const foo = html`
+
${getText({
+ value,
+ })}
+`;
+
+// Prettier 3.9.6 (first format)
+const foo = html`
+
+ ${getText({
+ value,
+ })}
+
+`;
+
+// Prettier 3.9.6 (second format)
+const foo = html`
+
+ ${getText({
+ value,
+ })}
+
+`;
+
+// Prettier 3.9.7
+const foo = html`
+
+ ${getText({
+ value,
+ })}
+
+`;
+```
+
+#### Markdown: Preserve Liquid blocks after Markdown tables ([#19730](https://github.com/prettier/prettier/pull/19730) by [@wanxiankai](https://github.com/wanxiankai), [@seiyab](https://github.com/seiyab))
+
+
+```markdown
+
+| Argument | Type |
+| -------- | ---- |
+{% set default_params = {
+ "model": ["str"],
+} %}
+
+
+| Argument | Type |
+| ------------------------- | ---- |
+| {% set default_params = { |
+
+ "model": ["str"],
+
+} %}
+
+
+| Argument | Type |
+| -------- | ---- |
+{% set default_params = {
+ "model": ["str"],
+} %}
+```
+
+#### Markdown: Preserve single tildes in Markdown ([#19739](https://github.com/prettier/prettier/pull/19739) by [@lazerg](https://github.com/lazerg))
+
+Since 3.9.0, single-tilde spans like `H~2~O` were rewritten to double tildes, turning subscript syntax into strike-through. Only double tildes are treated that way now, matching GitHub.
+
+
+```markdown
+
+H~2~O
+
+
+H~~2~~O
+
+
+H~2~O
+```
+
+#### Markdown: Fix Markdown blockquote containing `>` characters ([#19802](https://github.com/prettier/prettier/pull/19802) by [@seiyab](https://github.com/seiyab))
+
+
+```md
+
+> **test**\>
+
+
+> **test**>\>
+
+
+> **test**\>
+```
+
+#### Markdown: Strip blockquote markers from a setext heading's continuation lines ([#19878](https://github.com/prettier/prettier/pull/19878) by [@Kjubikstronk](https://github.com/Kjubikstronk))
+
+A setext heading spanning multiple lines inside a blockquote kept the `>` marker of its continuation lines as literal text.
+
+
+```md
+
+> Multi
+> Line
+> ===
+
+
+> Multi > Line
+> ===
+
+
+> Multi
+> Line
+> ===
+```
+
+#### Markdown: Fix HTML block indentation drift in markdown list items ([#19987](https://github.com/prettier/prettier/pull/19987) by [@lazerg](https://github.com/lazerg))
+
+
+```markdown
+
+1. Enable images:
+
+
+
+
+
+
+1. Enable images:
+
+
+
+
+
+
+1. Enable images:
+
+
+
+
+```
+
+#### Angular: Support `@boundary` error boundaries ([#20014](https://github.com/prettier/prettier/pull/20014) by [@Yahiro025](https://github.com/Yahiro025))
+
+
+```html
+
+@boundary {
+
+}
+@error (let err) {
+{{err.message}}
+}
+
+
+SyntaxError: Unexpected character "EOF" (Do you have an unescaped "{" in your template? Use "{{ '{' }}") to escape it.) (6:1)
+
+
+@boundary {
+
+} @error (let err) {
+ {{ err.message }}
+}
+```
+
# 3.9.6
[diff](https://github.com/prettier/prettier/compare/3.9.5...3.9.6)
diff --git a/changelog_unreleased/angular/20014.md b/changelog_unreleased/angular/20014.md
new file mode 100644
index 000000000000..3bfb0ef09579
--- /dev/null
+++ b/changelog_unreleased/angular/20014.md
@@ -0,0 +1,22 @@
+#### Support `@boundary` error boundaries (#20014 by @Yahiro025)
+
+
+```html
+
+@boundary {
+
+}
+@error (let err) {
+{{err.message}}
+}
+
+
+SyntaxError: Unexpected character "EOF" (Do you have an unescaped "{" in your template? Use "{{ '{' }}") to escape it.) (6:1)
+
+
+@boundary {
+
+} @error (let err) {
+ {{ err.message }}
+}
+```
diff --git a/changelog_unreleased/javascript/19629.md b/changelog_unreleased/javascript/19629.md
deleted file mode 100644
index 1717459ce6f1..000000000000
--- a/changelog_unreleased/javascript/19629.md
+++ /dev/null
@@ -1,26 +0,0 @@
-#### Added a new official plugin `@prettier/plugin-yuku` (#19628, #19629 by @fisker)
-
-`@prettier/plugin-yuku` is powered by [Yuku](https://yuku.fyi/) (A high-performance JavaScript/TypeScript compiler toolchain written in Zig).
-
-This plugin includes two new parsers: `yuku` (JavaScript syntax) and `yuku-ts` (TypeScript syntax).
-
-**To use this plugin:**
-
-1. Install the plugin:
-
- ```bash
- yarn add --dev prettier @prettier/plugin-yuku
- ```
-
-2. Add it to your `.prettierrc`:
-
- ```yaml
- plugins:
- - "@prettier/plugin-yuku"
- ```
-
-Due to package size limitations, this plugin is not bundled with the main prettier package and must be installed separately.
-
-For more information, check [the package homepage](https://github.com/prettier/prettier/tree/main/packages/plugin-yuku).
-
-Big thanks to [@arshad-yaseen](https://github.com/arshad-yaseen) for his excellent work.
diff --git a/changelog_unreleased/javascript/19725.md b/changelog_unreleased/javascript/19725.md
new file mode 100644
index 000000000000..088f037de697
--- /dev/null
+++ b/changelog_unreleased/javascript/19725.md
@@ -0,0 +1,38 @@
+#### Fix embedded template literal idempotency (#19725 by @fisker)
+
+
+```jsx
+// Input
+const foo = html`
+ ${getText({
+ value,
+ })}
+`;
+
+// Prettier stable (first format)
+const foo = html`
+
+ ${getText({
+ value,
+ })}
+
+`;
+
+// Prettier stable (second format)
+const foo = html`
+
+ ${getText({
+ value,
+ })}
+
+`;
+
+// Prettier main
+const foo = html`
+
+ ${getText({
+ value,
+ })}
+
+`;
+```
diff --git a/changelog_unreleased/markdown/19647.md b/changelog_unreleased/markdown/19647.md
new file mode 100644
index 000000000000..813a730d5e8c
--- /dev/null
+++ b/changelog_unreleased/markdown/19647.md
@@ -0,0 +1,22 @@
+#### Prevent indentation drift in list-item code blocks (#19647, #19990 by @Austin1serb, @giaBaoJS)
+
+
+```markdown
+
+- [x] short first line.
+
+ second paragraph at six spaces that wraps
+ onto another line here.
+
+
+- [x] short first line.
+
+ second paragraph at six spaces that wraps
+ onto another line here.
+
+
+- [x] short first line.
+
+ second paragraph at six spaces that wraps
+ onto another line here.
+```
diff --git a/changelog_unreleased/markdown/19730.md b/changelog_unreleased/markdown/19730.md
new file mode 100644
index 000000000000..82aa68636831
--- /dev/null
+++ b/changelog_unreleased/markdown/19730.md
@@ -0,0 +1,27 @@
+#### Preserve Liquid blocks after Markdown tables (#19730 by @wanxiankai, @seiyab)
+
+
+```markdown
+
+| Argument | Type |
+| -------- | ---- |
+{% set default_params = {
+ "model": ["str"],
+} %}
+
+
+| Argument | Type |
+| ------------------------- | ---- |
+| {% set default_params = { |
+
+ "model": ["str"],
+
+} %}
+
+
+| Argument | Type |
+| -------- | ---- |
+{% set default_params = {
+ "model": ["str"],
+} %}
+```
diff --git a/changelog_unreleased/markdown/19739.md b/changelog_unreleased/markdown/19739.md
new file mode 100644
index 000000000000..a4ed6a4963a3
--- /dev/null
+++ b/changelog_unreleased/markdown/19739.md
@@ -0,0 +1,15 @@
+#### Preserve single tildes in Markdown (#19739 by @lazerg)
+
+Since 3.9.0, single-tilde spans like `H~2~O` were rewritten to double tildes, turning subscript syntax into strike-through. Only double tildes are treated that way now, matching GitHub.
+
+
+```markdown
+
+H~2~O
+
+
+H~~2~~O
+
+
+H~2~O
+```
diff --git a/changelog_unreleased/markdown/19802.md b/changelog_unreleased/markdown/19802.md
new file mode 100644
index 000000000000..b3e670c80fe7
--- /dev/null
+++ b/changelog_unreleased/markdown/19802.md
@@ -0,0 +1,13 @@
+#### Fix Markdown blockquote containing `>` characters (#19802 by @seiyab)
+
+
+```md
+
+> **test**\>
+
+
+> **test**>\>
+
+
+> **test**\>
+```
diff --git a/changelog_unreleased/markdown/19878.md b/changelog_unreleased/markdown/19878.md
new file mode 100644
index 000000000000..61af67a38550
--- /dev/null
+++ b/changelog_unreleased/markdown/19878.md
@@ -0,0 +1,20 @@
+#### Strip blockquote markers from a setext heading's continuation lines (#19878 by @Kjubikstronk)
+
+A setext heading spanning multiple lines inside a blockquote kept the `>` marker of its continuation lines as literal text.
+
+
+```md
+
+> Multi
+> Line
+> ===
+
+
+> Multi > Line
+> ===
+
+
+> Multi
+> Line
+> ===
+```
diff --git a/changelog_unreleased/markdown/19987.md b/changelog_unreleased/markdown/19987.md
new file mode 100644
index 000000000000..49fcd70eaf36
--- /dev/null
+++ b/changelog_unreleased/markdown/19987.md
@@ -0,0 +1,25 @@
+#### Fix HTML block indentation drift in markdown list items (#19987 by @lazerg)
+
+
+```markdown
+
+1. Enable images:
+
+
+
+
+
+
+1. Enable images:
+
+
+
+
+
+
+1. Enable images:
+
+
+
+
+```
diff --git a/changelog_unreleased/typescript/19621.md b/changelog_unreleased/typescript/19621.md
deleted file mode 100644
index 91228377781b..000000000000
--- a/changelog_unreleased/typescript/19621.md
+++ /dev/null
@@ -1,19 +0,0 @@
-#### Preserve quotes for methods named `new` (#19621 by @kovsu)
-
-
-```tsx
-// Input
-interface Container {
- "new"(id: string): number;
-}
-
-// Prettier stable
-interface Container {
- new(id: string): number;
-}
-
-// Prettier main
-interface Container {
- "new"(id: string): number;
-}
-```
diff --git a/changelog_unreleased/typescript/19624.md b/changelog_unreleased/typescript/19624.md
deleted file mode 100644
index 0155f29588d1..000000000000
--- a/changelog_unreleased/typescript/19624.md
+++ /dev/null
@@ -1,13 +0,0 @@
-#### Support `import defer` (#19624, #19675 by @fisker)
-
-
-```tsx
-// Input
-import defer * as foo from "foo";
-
-// Prettier stable
-import * as foo from "foo";
-
-// Prettier main
-import defer * as foo from "foo";
-```
diff --git a/docs/browser.md b/docs/browser.md
index 873cb8ed82ed..9b03bd1fa9f2 100644
--- a/docs/browser.md
+++ b/docs/browser.md
@@ -97,7 +97,7 @@ This syntax doesn’t necessarily work in the browser, but it can be used when b
```js
import * as prettier from "https://unpkg.com/prettier@%PRETTIER_VERSION%/standalone.mjs";
-import * as prettierPluginGraphql from "https://unpkg.com/prettier@%PRETTIER_VERSION%1/plugins/graphql.mjs";
+import * as prettierPluginGraphql from "https://unpkg.com/prettier@%PRETTIER_VERSION%/plugins/graphql.mjs";
const formatted = await prettier.format("type Query { hello: String }", {
parser: "graphql",
diff --git a/package.json b/package.json
index b330ab4d2dbc..50962f05438b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "prettier",
- "version": "3.9.6",
+ "version": "3.9.7",
"description": "Prettier is an opinionated code formatter",
"bin": "./bin/prettier.cjs",
"repository": "prettier/prettier",
@@ -53,12 +53,12 @@
"@prettier/html-event-attributes": "0.0.2",
"@prettier/html-tags": "2.2.0",
"@prettier/parse-srcset": "3.1.0",
- "@typescript-eslint/typescript-estree": "8.65.0",
- "@typescript-eslint/visitor-keys": "8.65.0",
+ "@typescript-eslint/typescript-estree": "8.70.0",
+ "@typescript-eslint/visitor-keys": "8.70.0",
"acorn": "8.17.0",
"acorn-jsx": "5.3.2",
"angular-estree-parser": "15.5.0",
- "angular-html-parser": "10.11.0",
+ "angular-html-parser": "10.12.0",
"camelcase": "9.0.0",
"ci-info": "4.4.0",
"cjk-regex": "3.4.0",
@@ -139,8 +139,8 @@
"@eslint/js": "10.0.1",
"@stylistic/eslint-plugin": "5.10.0",
"@types/jsesc": "3.0.3",
- "@typescript-eslint/eslint-plugin": "8.65.0",
- "@typescript-eslint/parser": "8.65.0",
+ "@typescript-eslint/eslint-plugin": "8.70.0",
+ "@typescript-eslint/parser": "8.70.0",
"@yuku-parser/wasm": "0.7.0",
"base64-arraybuffer-es6": "3.1.0",
"browserslist": "4.28.6",
@@ -184,7 +184,7 @@
"node-style-text": "2.1.2",
"npm-run-all2": "9.0.2",
"open-editor": "6.0.0",
- "prettier": "3.9.5",
+ "prettier": "3.9.6",
"pretty-bytes": "7.1.0",
"pretty-ms": "9.3.0",
"puppeteer": "25.3.0",
diff --git a/packages/plugin-hermes/package.json b/packages/plugin-hermes/package.json
index 7632ac503393..cf1a0297c826 100644
--- a/packages/plugin-hermes/package.json
+++ b/packages/plugin-hermes/package.json
@@ -1,6 +1,6 @@
{
"name": "@prettier/plugin-hermes",
- "version": "0.2.1",
+ "version": "0.2.2",
"description": "Prettier Hermes plugin.",
"type": "module",
"exports": {
diff --git a/packages/plugin-oxc/package.json b/packages/plugin-oxc/package.json
index 66130941d999..4defb218b6ab 100644
--- a/packages/plugin-oxc/package.json
+++ b/packages/plugin-oxc/package.json
@@ -1,6 +1,6 @@
{
"name": "@prettier/plugin-oxc",
- "version": "0.2.1",
+ "version": "0.2.2",
"description": "Prettier Oxc plugin.",
"type": "module",
"exports": {
diff --git a/packages/plugin-yuku/package.json b/packages/plugin-yuku/package.json
index e91966f27c90..a7f4c0109668 100644
--- a/packages/plugin-yuku/package.json
+++ b/packages/plugin-yuku/package.json
@@ -1,6 +1,6 @@
{
"name": "@prettier/plugin-yuku",
- "version": "0.0.0",
+ "version": "0.0.1",
"description": "Prettier yuku plugin.",
"type": "module",
"exports": {
diff --git a/scripts/build/transform/index.js b/scripts/build/transform/index.js
index c44ec24c2e7e..82afe7369d85 100644
--- a/scripts/build/transform/index.js
+++ b/scripts/build/transform/index.js
@@ -28,7 +28,10 @@ const packageTransforms = new Map([
transforms["method-at"],
["@glimmer/syntax", "angular-estree-parser", "espree"],
],
- [transforms["object-has-own"], ["@babel/parser", "meriyah", "graphql"]],
+ [
+ transforms["object-has-own"],
+ ["@babel/parser", "graphql", "meriyah", "angular-html-parser"],
+ ],
[transforms["string-raw"], ["camelcase", "@angular/compiler", "js-yaml"]],
[transforms["method-is-well-formed"], ["meriyah"]],
/* spell-checker: enable */
diff --git a/src/language-html/print/angular-control-flow-block-settings.evaluate.js b/src/language-html/print/angular-control-flow-block-settings.evaluate.js
index bc9b33c232f0..48243b99a0ec 100644
--- a/src/language-html/print/angular-control-flow-block-settings.evaluate.js
+++ b/src/language-html/print/angular-control-flow-block-settings.evaluate.js
@@ -15,6 +15,8 @@ const ANGULAR_CONTROL_FLOW_BLOCK_SETTINGS = new Map(
["placeholder", ["placeholder", "error", "loading"]],
["error", ["placeholder", "error", "loading"]],
["loading", ["placeholder", "error", "loading"]],
+
+ ["boundary", ["error"]],
]
.filter(([, followingBlockNames]) => followingBlockNames.length > 0)
.map(([name, followingBlockNames]) => [name, new Set(followingBlockNames)]),
diff --git a/src/language-js/embed/css.js b/src/language-js/embed/css.js
index f3171e7603e4..baaa60b51036 100644
--- a/src/language-js/embed/css.js
+++ b/src/language-js/embed/css.js
@@ -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) {
diff --git a/src/language-js/embed/graphql.js b/src/language-js/embed/graphql.js
index 4d98a5ab4bb5..02421933fc60 100644
--- a/src/language-js/embed/graphql.js
+++ b/src/language-js/embed/graphql.js
@@ -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++) {
diff --git a/src/language-js/embed/html.js b/src/language-js/embed/html.js
index e2ac60357e11..ce4f6bb8d602 100644
--- a/src/language-js/embed/html.js
+++ b/src/language-js/embed/html.js
@@ -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+)`),
diff --git a/src/language-js/print/template-literal.js b/src/language-js/print/template-literal.js
index dbb668b1ce20..e84e235ed702 100644
--- a/src/language-js/print/template-literal.js
+++ b/src/language-js/print/template-literal.js
@@ -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,
};
diff --git a/src/language-markdown/parse/micromark/micromark-extension-liquid.js b/src/language-markdown/parse/micromark/micromark-extension-liquid.js
index cac4a27a4d0b..45b011a28d08 100644
--- a/src/language-markdown/parse/micromark/micromark-extension-liquid.js
+++ b/src/language-markdown/parse/micromark/micromark-extension-liquid.js
@@ -1,4 +1,4 @@
-import { markdownLineEnding } from "micromark-util-character";
+import { markdownLineEnding, markdownSpace } from "micromark-util-character";
import { codes, types } from "micromark-util-symbol";
/**
@@ -7,6 +7,7 @@ import { codes, types } from "micromark-util-symbol";
* @typedef {import('mdast-util-from-markdown').CompileContext} CompileContext
* @typedef {import('mdast-util-from-markdown').Handle} Handle
* @typedef {import('micromark-util-types').State} State
+ * @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext
*/
const nodeType = "liquidNode";
@@ -46,15 +47,37 @@ function liquidFromMarkdown() {
*/
function liquidSyntax() {
return {
+ flow: {
+ [codes.leftCurlyBrace]: {
+ name: "liquidFlow",
+ tokenize: tokenizeFlow,
+ },
+ },
text: {
[codes.leftCurlyBrace]: {
- name: "liquid",
- tokenize,
+ name: "liquidText",
+ tokenize: tokenizeText,
},
},
};
- function tokenize(effects, ok, nok) {
+ /** @this {TokenizeContext} */
+ function tokenizeFlow(effects, ok, nok) {
+ return tokenize.call(this, effects, ok, nok, "flow");
+ }
+
+ /** @this {TokenizeContext} */
+ function tokenizeText(effects, ok, nok) {
+ return tokenize.call(this, effects, ok, nok, "text");
+ }
+
+ /**
+ * @this {TokenizeContext}
+ * @param mode {"text" | "flow"}
+ */
+ function tokenize(effects, ok, nok, mode) {
+ const isFlow = mode === "flow";
+ const { interrupt, now, parser } = this;
/** @type {typeof codes.rightCurlyBrace | typeof codes.percentSign} */
let closingCode;
@@ -74,7 +97,7 @@ function liquidSyntax() {
? codes.percentSign
: codes.rightCurlyBrace;
effects.consume(code);
- return inside;
+ return isFlow && interrupt ? ok : inside;
default:
return nok(code);
}
@@ -95,6 +118,9 @@ function liquidSyntax() {
effects.enter(types.lineEnding);
effects.consume(code);
effects.exit(types.lineEnding);
+ if (isFlow) {
+ return afterLineEnding;
+ }
effects.enter(types.data);
return inside;
}
@@ -103,17 +129,63 @@ function liquidSyntax() {
}
}
+ /** @type {State} */
+ function afterLineEnding(code) {
+ if (parser.lazy[now().line]) {
+ return nok(code);
+ }
+
+ if (markdownLineEnding(code)) {
+ effects.enter(types.lineEnding);
+ effects.consume(code);
+ effects.exit(types.lineEnding);
+ return afterLineEnding;
+ }
+
+ effects.enter(types.data);
+ return inside(code);
+ }
+
/** @type {State} */
function mayClose(code) {
if (code === codes.rightCurlyBrace) {
effects.consume(code);
effects.exit(types.data);
effects.exit(nodeType);
- return ok;
+ return isFlow ? afterClose : ok;
}
return inside;
}
+
+ /** @type {State} */
+ function afterClose(code) {
+ if (markdownSpace(code)) {
+ effects.enter(types.whitespace);
+ effects.consume(code);
+ return afterWhitespace;
+ }
+
+ return after(code);
+ }
+
+ /** @type {State} */
+ function afterWhitespace(code) {
+ if (markdownSpace(code)) {
+ effects.consume(code);
+ return afterWhitespace;
+ }
+
+ effects.exit(types.whitespace);
+ return after(code);
+ }
+
+ /** @type {State} */
+ function after(code) {
+ return code === codes.eof || markdownLineEnding(code)
+ ? ok(code)
+ : nok(code);
+ }
}
}
diff --git a/src/language-markdown/parse/parse-markdown.js b/src/language-markdown/parse/parse-markdown.js
index 8c07cc832ec3..ba7f76a2b3c2 100644
--- a/src/language-markdown/parse/parse-markdown.js
+++ b/src/language-markdown/parse/parse-markdown.js
@@ -16,7 +16,7 @@ let markdownParseOptions;
function getMarkdownParseOptions() {
return (markdownParseOptions ??= {
extensions: [
- gfmSyntax(),
+ gfmSyntax({ singleTilde: false }),
mathSyntax(),
wikiLinkSyntax({
// We don't need support alias, use a fake string to bypass
diff --git a/src/language-markdown/print/children.js b/src/language-markdown/print/children.js
index be78f2e95450..9b313551d753 100644
--- a/src/language-markdown/print/children.js
+++ b/src/language-markdown/print/children.js
@@ -42,7 +42,11 @@ function printChildren(path, options, print, events = {}) {
}
function shouldPrePrintHardline({ node, parent }) {
- const isInlineNode = INLINE_NODE_TYPES.has(node.type);
+ const isInlineNode =
+ INLINE_NODE_TYPES.has(node.type) &&
+ !(
+ node.type === "liquidNode" && !INLINE_NODE_WRAPPER_TYPES.has(parent.type)
+ );
const isInlineHTML =
node.type === "html" && INLINE_NODE_WRAPPER_TYPES.has(parent.type);
@@ -119,6 +123,9 @@ function shouldPrePrintDoubleHardline(path, options) {
parent.type === "listItem" &&
previous.type === "paragraph" &&
previous.position.end.line + 1 === node.position.start.line;
+ const isBlockLiquidWithoutBlankLine =
+ (node.type === "liquidNode" || previous.type === "liquidNode") &&
+ previous.position.end.line + 1 === node.position.start.line;
return !(
isSiblingNode ||
@@ -126,7 +133,8 @@ function shouldPrePrintDoubleHardline(path, options) {
isPrevNodePrettierIgnore ||
isBlockHtmlWithoutBlankLineBetweenPrevHtml ||
isBlockHtmlWithoutBlankLineBetweenPrevParagraph ||
- isHtmlDirectAfterListItem
+ isHtmlDirectAfterListItem ||
+ isBlockLiquidWithoutBlankLine
);
}
diff --git a/src/language-markdown/print/list.js b/src/language-markdown/print/list.js
index 911325b6f349..755d76769a95 100644
--- a/src/language-markdown/print/list.js
+++ b/src/language-markdown/print/list.js
@@ -99,13 +99,14 @@ function printListItem(path, options, print, listPrefix) {
prefix,
printChildren(path, options, print, {
processor({ node, isFirst }) {
- if (
- (isFirst && node.type !== "list") ||
- (node.type === "code" && node.isIndented)
- ) {
+ if ((isFirst && node.type !== "list") || node.type === "html") {
return align(" ".repeat(prefix.length), print());
}
+ if (node.type === "code" && node.isIndented) {
+ return print();
+ }
+
const alignment = " ".repeat(
clamp(options.tabWidth - listPrefix.length, 0, 3), // 4+ will cause indented code block
);
@@ -211,6 +212,10 @@ function printListItemLegacy(path, options, print, listPrefix) {
return align(" ".repeat(prefix.length), print());
}
+ if (node.type === "code" && node.isIndented) {
+ return print();
+ }
+
const alignment = " ".repeat(
clamp(options.tabWidth - listPrefix.length, 0, 3), // 4+ will cause indented code block
);
diff --git a/src/language-markdown/print/preprocess.js b/src/language-markdown/print/preprocess.js
index 84b203131eb7..bffce2cf3b6d 100644
--- a/src/language-markdown/print/preprocess.js
+++ b/src/language-markdown/print/preprocess.js
@@ -1,5 +1,6 @@
import htmlWhitespace from "../../utilities/html-whitespace.js";
import { getOrderedListItemInfo, mapAst, splitText } from "../utilities.js";
+import { getBlockquoteRawText } from "../utilities/get-blockquote-raw-text.js";
// 0x0 ~ 0x10ffff
const isSingleCharRegex = /^\\?.$/su;
@@ -182,8 +183,11 @@ function splitTextIntoSentences(ast) {
// Using `node.value`, we don't need to care about markers like `\n > ` in `blockquote`s.
let text = node.raw;
+ // A setext heading's text can span multiple raw source lines too, e.g.
+ // `> Title\n> continued\n> ===`.
const paragraphIndex = parentStack.findIndex(
- (ancestor) => ancestor?.type === "paragraph",
+ (ancestor) =>
+ ancestor?.type === "paragraph" || ancestor?.type === "heading",
);
const paragraphNode =
@@ -248,19 +252,6 @@ function splitTextIntoSentences(ast) {
}
}
-function getBlockquoteRawText(text, node) {
- const angleBracketsRegex = /^([ \t]*>[ \t]*)*/;
- const rawLines = text.split("\n");
- const valueLines = node.value.split("\n");
- const resultLines = rawLines.map((rawLine, index) => {
- const valueLine = valueLines[index] ?? "";
- const leadingTextAngleBrackets =
- valueLine.match(angleBracketsRegex)[0] ?? "";
- return rawLine.replace(angleBracketsRegex, leadingTextAngleBrackets);
- });
- return resultLines.join("\n");
-}
-
function transformIndentedCodeblock(ast, options) {
return mapAst(ast, (node) => {
if (node.type !== "code") {
diff --git a/src/language-markdown/utilities/get-blockquote-raw-text.js b/src/language-markdown/utilities/get-blockquote-raw-text.js
new file mode 100644
index 000000000000..8c9279bb31f9
--- /dev/null
+++ b/src/language-markdown/utilities/get-blockquote-raw-text.js
@@ -0,0 +1,67 @@
+/**
+ * @param line {string}
+ * @returns {number}
+ */
+function countValueLeadingGreaterThan(line) {
+ let count = 0;
+
+ for (const character of line) {
+ if (character === ">") {
+ count++;
+ } else if (character !== " " && character !== "\t") {
+ break;
+ }
+ }
+
+ return count;
+}
+
+/**
+ * @param line {string}
+ * @param greaterThanToKeep {number}
+ * @returns {string}
+ */
+function removeBlockquoteMarkers(line, greaterThanToKeep) {
+ const rawBlockquoteRegexp =
+ /[ \t]*(?:>|\\>|>|>|*62;|[xX]0*3[eE];)[ \t]*/y;
+ /** @type {Array} */
+ const greaterThanIndexes = [];
+ let { lastIndex } = rawBlockquoteRegexp;
+
+ let match;
+ while ((match = rawBlockquoteRegexp.exec(line))) {
+ greaterThanIndexes.push(match.index);
+ lastIndex = rawBlockquoteRegexp.lastIndex;
+ }
+
+ if (greaterThanToKeep === 0) {
+ return line.slice(lastIndex);
+ }
+ const firstGreaterThanToKeep = greaterThanIndexes.at(-greaterThanToKeep);
+ /* c8 ignore next 3 */
+ if (firstGreaterThanToKeep === null) {
+ return line;
+ }
+ return line.slice(firstGreaterThanToKeep);
+}
+
+const newLineRegexp = /(? {
+ const valueLine = valueLines[valueIndex++];
+ const decodedNewlines = rawLine.matchAll(newLineRegexp);
+ valueIndex += [...decodedNewlines].length;
+
+ if (index === 0) {
+ return rawLine;
+ }
+ const valueMarkerCount = countValueLeadingGreaterThan(valueLine);
+ return removeBlockquoteMarkers(rawLine, valueMarkerCount);
+ });
+ return resultLines.join("\n");
+}
+
+export { getBlockquoteRawText };
diff --git a/tests/config/format-test/failed-format-tests.js b/tests/config/format-test/failed-format-tests.js
index 625c6a413b10..54f332b64a7f 100644
--- a/tests/config/format-test/failed-format-tests.js
+++ b/tests/config/format-test/failed-format-tests.js
@@ -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",
diff --git a/tests/config/format-test/verify-fixtures.js b/tests/config/format-test/verify-fixtures.js
index facc6c63d9d2..48afb6ea6e53 100644
--- a/tests/config/format-test/verify-fixtures.js
+++ b/tests/config/format-test/verify-fixtures.js
@@ -138,7 +138,7 @@ const categoryParsers = new Map([
["lwc", { parsers: ["lwc"], verifyParsers: [], extensions: [".html"] }],
[
"markdown",
- { parsers: ["markdown"], verifyParsers: [], extensions: [".md"] },
+ { parsers: ["markdown"], verifyParsers: ["mdx"], extensions: [".md"] },
],
["mdx", { parsers: ["mdx"], verifyParsers: [], extensions: [".mdx"] }],
[
diff --git a/tests/format/angular/control-flow/__snapshots__/format.test.js.snap b/tests/format/angular/control-flow/__snapshots__/format.test.js.snap
index 8cf72183e5f7..f5f199f6eefb 100644
--- a/tests/format/angular/control-flow/__snapshots__/format.test.js.snap
+++ b/tests/format/angular/control-flow/__snapshots__/format.test.js.snap
@@ -52,6 +52,258 @@ parsers: ["angular"]
================================================================================
`;
+exports[`boundary.html format 1`] = `
+====================================options=====================================
+parsers: ["angular"]
+ printWidth: 80 (default) |
+=====================================input======================================
+@boundary {
+
+} @error {
+ Something went wrong!
+}
+
+@boundary {
+
+} @error {
+ Error occurred: {{ $error.message }}
+}
+
+@boundary {
+
+} @error {
+ Loading failed.
+ Try again
+}
+
+@boundary {
+
+} @error {
+
+}
+
+@boundary {
+
+} @error (let err; retry = $reset; when isNetworkError(err)) {
+
+ Retry
+} @error {
+
+}
+
+@boundary {
+
+} @error (let err; reset = $reset; when isRenderError(err)) {
+ Network issue. Check your connection.
+ Retry
+} @error {
+ An unexpected error occurred: {{ $error.message }}
+}
+
+@boundary {
+
+} @error (let err = $error; let r = $reset) {
+ Failed to load chart: {{err.message}}
+ Retry
+}
+
+@boundary {
+ Outer Main
+ @boundary {
+ Inner Main
+ } @error {
+ Inner Fallback
+ }
+} @error {
+ Outer Fallback
+}
+
+@defer {
+ @boundary {
+
+ } @error {
+ Render error
+ }
+} @error {
+ Load error
+}
+
+=====================================output=====================================
+@boundary {
+
+} @error {
+ Something went wrong!
+}
+
+@boundary {
+
+} @error {
+ Error occurred: {{ $error.message }}
+}
+
+@boundary {
+
+} @error {
+ Loading failed.
+ Try again
+}
+
+@boundary {
+
+} @error {
+
+}
+
+@boundary {
+
+} @error (let err; retry = $reset; when isNetworkError(err)) {
+
+ Retry
+} @error {
+
+}
+
+@boundary {
+
+} @error (let err; reset = $reset; when isRenderError(err)) {
+ Network issue. Check your connection.
+ Retry
+} @error {
+ An unexpected error occurred: {{ $error.message }}
+}
+
+@boundary {
+
+} @error (let err = $error; let r = $reset) {
+ Failed to load chart: {{ err.message }}
+ Retry
+}
+
+@boundary {
+ Outer Main
+ @boundary {
+ Inner Main
+ } @error {
+ Inner Fallback
+ }
+} @error {
+ Outer Fallback
+}
+
+@defer {
+ @boundary {
+
+ } @error {
+ Render error
+ }
+} @error {
+ Load error
+}
+
+================================================================================
+`;
+
+exports[`boundary-error-parameters.html format 1`] = `
+====================================options=====================================
+parsers: ["angular"]
+ printWidth: 80 (default) |
+=====================================input======================================
+@boundary {
+
+} @error (let err; retry
+
+= $reset; when
+isNetworkError(err)) {
+
+ Retry
+} @error {
+
+}
+
+@boundary {
+
+} @error (let
+err; reset
+=
+$reset; when
+isRenderError(err)) {
+ Network issue. Check your connection.
+ Retry
+} @error {
+ An unexpected error occurred: {{$error.message}}
+}
+
+@boundary {
+
+} @error (let err=$error; let r=$reset) {
+ Failed to load chart: {{err.message}}
+ Retry
+}
+
+@boundary {
+ Main
+} @error (let err; when err.message === '404') {
+ Not Found
+}
+
+@boundary {
+ Main
+} @error (err = $error, retry
+=
+$reset) {
+ Retry
+}
+
+=====================================output=====================================
+@boundary {
+
+} @error (let err; retry
+
+= $reset; when
+isNetworkError(err)) {
+
+ Retry
+} @error {
+
+}
+
+@boundary {
+
+} @error (let
+err; reset
+=
+$reset; when
+isRenderError(err)) {
+ Network issue. Check your connection.
+ Retry
+} @error {
+ An unexpected error occurred: {{ $error.message }}
+}
+
+@boundary {
+
+} @error (let err=$error; let r=$reset) {
+ Failed to load chart: {{ err.message }}
+ Retry
+}
+
+@boundary {
+ Main
+} @error (let err; when err.message === '404') {
+ Not Found
+}
+
+@boundary {
+ Main
+} @error (err = $error, retry
+=
+$reset) {
+ Retry
+}
+
+================================================================================
+`;
+
exports[`chid.html format 1`] = `
====================================options=====================================
parsers: ["angular"]
diff --git a/tests/format/angular/control-flow/boundary-error-parameters.html b/tests/format/angular/control-flow/boundary-error-parameters.html
new file mode 100644
index 000000000000..5bcec9af436c
--- /dev/null
+++ b/tests/format/angular/control-flow/boundary-error-parameters.html
@@ -0,0 +1,45 @@
+@boundary {
+
+} @error (let err; retry
+
+= $reset; when
+isNetworkError(err)) {
+
+ Retry
+} @error {
+
+}
+
+@boundary {
+
+} @error (let
+err; reset
+=
+$reset; when
+isRenderError(err)) {
+ Network issue. Check your connection.
+ Retry
+} @error {
+ An unexpected error occurred: {{$error.message}}
+}
+
+@boundary {
+
+} @error (let err=$error; let r=$reset) {
+ Failed to load chart: {{err.message}}
+ Retry
+}
+
+@boundary {
+ Main
+} @error (let err; when err.message === '404') {
+ Not Found
+}
+
+@boundary {
+ Main
+} @error (err = $error, retry
+=
+$reset) {
+ Retry
+}
diff --git a/tests/format/angular/control-flow/boundary.html b/tests/format/angular/control-flow/boundary.html
new file mode 100644
index 000000000000..16d0ea579f3b
--- /dev/null
+++ b/tests/format/angular/control-flow/boundary.html
@@ -0,0 +1,70 @@
+@boundary {
+
+} @error {
+ Something went wrong!
+}
+
+@boundary {
+
+} @error {
+ Error occurred: {{ $error.message }}
+}
+
+@boundary {
+
+} @error {
+ Loading failed.
+ Try again
+}
+
+@boundary {
+
+} @error {
+
+}
+
+@boundary {
+
+} @error (let err; retry = $reset; when isNetworkError(err)) {
+
+ Retry
+} @error {
+
+}
+
+@boundary {
+
+} @error (let err; reset = $reset; when isRenderError(err)) {
+ Network issue. Check your connection.
+ Retry
+} @error {
+ An unexpected error occurred: {{ $error.message }}
+}
+
+@boundary {
+
+} @error (let err = $error; let r = $reset) {
+ Failed to load chart: {{err.message}}
+ Retry
+}
+
+@boundary {
+ Outer Main
+ @boundary {
+ Inner Main
+ } @error {
+ Inner Fallback
+ }
+} @error {
+ Outer Fallback
+}
+
+@defer {
+ @boundary {
+
+ } @error {
+ Render error
+ }
+} @error {
+ Load error
+}
diff --git a/tests/format/js/dynamic-import/__snapshots__/format.test.js.snap b/tests/format/js/dynamic-import/__snapshots__/format.test.js.snap
index 05d949c40731..62b2d68fc1c7 100644
--- a/tests/format/js/dynamic-import/__snapshots__/format.test.js.snap
+++ b/tests/format/js/dynamic-import/__snapshots__/format.test.js.snap
@@ -93,6 +93,16 @@ exports[`import-phase.js format[meriyah] 1`] = `
Cause: [1:7-1:13]: The only valid meta property for import is 'import.meta'"
`;
+exports[`import-phase.js format[typescript] 1`] = `
+"'source' is not a valid meta-property for keyword 'import'. (1:1)
+> 1 | import.source(\`data:text/javascript,
+ | ^^^^^^^^^^^^^
+ 2 | console.log("RUN");
+ 3 | \`)
+ 4 |
+Cause: 'source' is not a valid meta-property for keyword 'import'."
+`;
+
exports[`template-literal.js format 1`] = `
====================================options=====================================
parsers: ["babel", "flow", "typescript"]
diff --git a/tests/format/js/dynamic-import/format.test.js b/tests/format/js/dynamic-import/format.test.js
index 3c75120caad2..137e1854f72d 100644
--- a/tests/format/js/dynamic-import/format.test.js
+++ b/tests/format/js/dynamic-import/format.test.js
@@ -5,5 +5,6 @@ runFormatTest(import.meta, ["babel", "flow", "typescript"], {
acorn: ["import-phase.js"],
espree: ["import-phase.js"],
meriyah: ["import-phase.js"],
+ typescript: ["import-phase.js"],
},
});
diff --git a/tests/format/js/embeded/indention/19518.js b/tests/format/js/embeded/indention/19518.js
new file mode 100644
index 000000000000..0f3a45acb325
--- /dev/null
+++ b/tests/format/js/embeded/indention/19518.js
@@ -0,0 +1,36 @@
+const t = html`
+
+ ${items.map(
+ (entry) => html`
+
+ ${entry.children
+ ? html`
+
+ ${entry.children.map(
+ (child) => html`${child.title} `,
+ )}
+
+ `
+ : entry.title}
+
+ `,
+ )}
+
+`;
+
+export function foo() {
+ return html`
+
+
${JSON.stringify({
+ a: 1,
+ b: 2,
+ })}
+
+ `;
+}
+
+const a = html`
+ ${{
+ c: y,
+ }}
+`;
diff --git a/tests/format/js/embeded/indention/__snapshots__/format.test.js.snap b/tests/format/js/embeded/indention/__snapshots__/format.test.js.snap
new file mode 100644
index 000000000000..4ff6e4d96ba0
--- /dev/null
+++ b/tests/format/js/embeded/indention/__snapshots__/format.test.js.snap
@@ -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\`
+
+ \${items.map(
+ (entry) => html\`
+
+ \${entry.children
+ ? html\`
+
+ \${entry.children.map(
+ (child) => html\`\${child.title} \`,
+ )}
+
+ \`
+ : entry.title}
+
+ \`,
+ )}
+
+\`;
+
+export function foo() {
+ return html\`
+
+
\${JSON.stringify({
+ a: 1,
+ b: 2,
+ })}
+
+ \`;
+}
+
+const a = html\`
+ \${{
+ c: y,
+ }}
+\`;
+
+=====================================output=====================================
+const t = html\`
+
+ \${items.map(
+ (entry) => html\`
+
+ \${
+ entry.children
+ ? html\`
+
+ \${entry.children.map(
+ (child) => html\`\${child.title} \`,
+ )}
+
+ \`
+ : entry.title
+ }
+
+ \`,
+ )}
+
+\`;
+
+export function foo() {
+ return html\`
+
+
+\${JSON.stringify({
+ a: 1,
+ b: 2,
+ })}
+
+ \`;
+}
+
+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\`
+
+ \${
+ a
+ // comment
+ + b}
+
+\`;
+
+_ = gql\`
+
+ \${
+ a
+ // comment
+ + b}
+
+\`;
+
+=====================================output=====================================
+_ = \`
+ line
+ \${
+ a +
+ // comment
+ b
+ }
+ line
+\`;
+
+_ = String.raw\`
+ line
+ \${
+ a +
+ // comment
+ b
+ }
+ line
+\`;
+
+_ = css\`
+ a {
+ color: \${
+ a +
+ // comment
+ b
+ };
+ }
+\`;
+
+_ = html\`
+
+ \${
+ a +
+ // comment
+ b
+ }
+
+\`;
+
+_ = gql\`
+ \${
+ a +
+ // comment
+ b
+ }
+\`;
+
+================================================================================
+`;
+
+exports[`indention-2.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "flow", "typescript"]
+ printWidth: 80 (default) |
+=====================================input======================================
+_ = html\`
+
+ \${
+ a + //
+ b
+ }
+
+
+\`
+
+=====================================output=====================================
+_ = html\`
+
+ \${
+ a + //
+ b
+ }
+
+
+
+
+
+ \${
+ a + //
+ b
+ }
+
+
+
+
+\`;
+
+================================================================================
+`;
diff --git a/tests/format/js/embeded/indention/format.test.js b/tests/format/js/embeded/indention/format.test.js
new file mode 100644
index 000000000000..67c251cced19
--- /dev/null
+++ b/tests/format/js/embeded/indention/format.test.js
@@ -0,0 +1 @@
+runFormatTest(import.meta, ["babel", "flow", "typescript"]);
diff --git a/tests/format/js/embeded/indention/indention-2.js b/tests/format/js/embeded/indention/indention-2.js
new file mode 100644
index 000000000000..587876202e4b
--- /dev/null
+++ b/tests/format/js/embeded/indention/indention-2.js
@@ -0,0 +1,14 @@
+_ = html`
+
+ ${
+ a + //
+ b
+ }
+
+
+`
diff --git a/tests/format/js/embeded/indention/indention.js b/tests/format/js/embeded/indention/indention.js
new file mode 100644
index 000000000000..39c45a0c6a1c
--- /dev/null
+++ b/tests/format/js/embeded/indention/indention.js
@@ -0,0 +1,46 @@
+_ = `
+ line
+ ${
+ a
+ // comment
+ + b}
+ line
+`;
+
+_ = String.raw`
+ line
+ ${
+ a
+ // comment
+ + b}
+ line
+`;
+
+_ = css`
+ a{
+ color:
+ ${
+ a
+ // comment
+ + b}
+ ;
+ }
+`;
+
+_ = html`
+
+ ${
+ a
+ // comment
+ + b}
+
+`;
+
+_ = gql`
+
+ ${
+ a
+ // comment
+ + b}
+
+`;
diff --git a/tests/format/js/multiparser-comments/__snapshots__/format.test.js.snap b/tests/format/js/multiparser-comments/__snapshots__/format.test.js.snap
index 9eeb77132da5..212d9cbc64af 100644
--- a/tests/format/js/multiparser-comments/__snapshots__/format.test.js.snap
+++ b/tests/format/js/multiparser-comments/__snapshots__/format.test.js.snap
@@ -84,9 +84,9 @@ html\`\${
}\`;
html\`
\${
- foo
- /* comment */
-}
+ foo
+ /* comment */
+ }
\`;
graphql\`
@@ -97,9 +97,9 @@ graphql\`
\`;
graphql\`
\${
- foo
- /* comment */
-}
+ foo
+ /* comment */
+ }
\`;
css\`
@@ -110,9 +110,9 @@ css\`
\`;
css\`
\${
- foo
- /* comment */
-}
+ foo
+ /* comment */
+ }
\`;
markdown\`\${
diff --git a/tests/format/jsx/template/__snapshots__/format.test.js.snap b/tests/format/jsx/template/__snapshots__/format.test.js.snap
index c9ca791785ca..deead232333a 100644
--- a/tests/format/jsx/template/__snapshots__/format.test.js.snap
+++ b/tests/format/jsx/template/__snapshots__/format.test.js.snap
@@ -63,8 +63,8 @@ parsers: ["babel", "typescript", "flow"]
;