Repository: sveltejs/prettier-plugin-svelte
Pull request #267 (2021-12-02 08:29:09)

format MustacheTag in pre
- (fix) format MustacheTag in pre

Also added test case.

hyrious: > Could you please remove all unrelated changes, like bumping the workflow file or package.json version bumps? Please also remove the removal of `concat` - this plugin is backwards compatible with a version of prettier where `concat` was the only way of constructing an AST array.

Fine, will do it tomorrow.
hyrious: I'm ready.
hyrious: I see. However I still think there exists some solution to them:
1. let me add more `printPre` to these `isPreTagContent` codes.
2. line breaks in pre should be replaced with `hardline`. the example code you post is because "Goodbye.\n  this text goes..." being treated as one element in prettier.
dummdidumm: If the code doesn't end up being too big or too complex, then I'm okay with that. On a general note: Prettier HTML formatting doesn't format anything inside `<pre>` tags either, so I would be ok with this being a limitation which we don't handle.
hyrious: It seems to be easier than I thought.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 375dee1..29616f7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
 
 * (fix) Better handling of destructured values with defaults in each and await blocks ([#269](https://github.com/sveltejs/prettier-plugin-svelte/issues/269))
 * (fix) Prevent script/style contents from being erased in certain cases ([#268](https://github.com/sveltejs/prettier-plugin-svelte/issues/268))
+* (fix) Format mustache tags inside `<pre>` tags ([#266](https://github.com/sveltejs/prettier-plugin-svelte/issues/266))
 
 ## 2.5.0
 
diff --git a/README.md b/README.md
index d155e4e..54456a3 100644
--- a/README.md
+++ b/README.md
@@ -93,7 +93,7 @@ Example:
 
 > Deprecated since 2.5.0. Use Prettier 2.4.0 and [bracketSameLine](https://prettier.io/docs/en/options.html#bracket-line) instead.
 
-Put the `>` of a multiline element on a new line. Roughly the Svelte equivalent of the [jsxBracketSameLine](https://prettier.io/docs/en/options.html#jsx-brackets) rule. Setting this to `false` will have no effect for whitespace-sensitive tags (inline elements) when there's no whitespace between the `>` of the start tag and the inner content, or when there's no whitespace after the `>` of the end tag. 
+Put the `>` of a multiline element on a new line. Roughly the Svelte equivalent of the [jsxBracketSameLine](https://prettier.io/docs/en/options.html#jsx-brackets) rule. Setting this to `false` will have no effect for whitespace-sensitive tags (inline elements) when there's no whitespace between the `>` of the start tag and the inner content, or when there's no whitespace after the `>` of the end tag. You can read more about HTML whitespace sensitivity [here](https://prettier.io/blog/2018/11/07/1.15.0.html#whitespace-sensitive-formatting). You can adjust whitespace sensitivity through [this setting](https://prettier.io/docs/en/options.html#html-whitespace-sensitivity). 
 
 Example:
 
diff --git a/src/index.ts b/src/index.ts
index af3c060..1b84f11 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -26,7 +26,7 @@ export const parsers: Record<string, Parser> = {
         parse: (text) => {
             try {
                 return <ASTNode>{ ...require(`svelte/compiler`).parse(text), __isRoot: true };
-            } catch (err) {
+            } catch (err: any) {
                 if (err.start != null && err.end != null) {
                     // Prettier expects error objects to have loc.start and loc.end fields.
                     // Svelte uses start and end directly on the error.
diff --git a/src/print/doc-helpers.ts b/src/print/doc-helpers.ts
index 7f5fb38..b891d6b 100644
--- a/src/print/doc-helpers.ts
+++ b/src/print/doc-helpers.ts
@@ -33,13 +33,19 @@ function deepEqual(x: any, y: any): boolean {
     }
 }
 
+function isDocCommand(doc: Doc): doc is doc.builders.DocCommand {
+    return typeof doc === 'object' && doc !== null;
+}
+
 export function isLine(docToCheck: Doc): boolean {
     return (
         isHardline(docToCheck) ||
-        (typeof docToCheck === 'object' && docToCheck.type === 'line') ||
-        (typeof docToCheck === 'object' &&
+        (isDocCommand(docToCheck) && docToCheck.type === 'line') ||
+        (isDocCommand(docToCheck) &&
             docToCheck.type === 'concat' &&
-            docToCheck.parts.every(isLine))
+            docToCheck.parts.every(isLine)) ||
+        // Since Prettier 2.3.0, concats are represented as flat arrays
+        (Array.isArray(docToCheck) && docToCheck.every(isLine))
     );
 }
 
@@ -51,7 +57,7 @@ export function isEmptyDoc(doc: Doc): boolean {
         return doc.length === 0;
     }
 
-    if (doc.type === 'line') {
+    if (isDocCommand(doc) && doc.type === 'line') {
         return !doc.keepIfLonely;
     }
 
diff --git a/src/print/index.ts b/src/print/index.ts
index fd852b9..53dd4bd 100644
--- a/src/print/index.ts
+++ b/src/print/index.ts
@@ -240,7 +240,7 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D
                         ? () => line
                         : () => (bracketSameLine ? softline : '');
             } else if (isPreTagContent(path)) {
-                body = () => printRaw(node, options.originalText);
+                body = () => printPre(node, options.originalText, path, print);
             } else if (!isSupportedLanguage) {
                 body = () => printRaw(node, options.originalText, true);
             } else if (isInlineElement(path, options, node) && !isPreTagContent(path)) {
@@ -267,7 +267,7 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D
             ];
 
             if (!isSupportedLanguage && !isEmpty) {
-                // Format template tags so that there's a hardline but no intendation.
+                // Format template tags so that there's a hardline but no indention.
                 // That way the `lang="X"` and the closing `>` of the start tag stay in one line
                 // which is the 99% use case.
                 return groupConcat([
@@ -758,13 +758,36 @@ function printSvelteBlockChildren(path: FastPath, print: PrintFn, options: Parse
     ]);
 }
 
+function printPre(
+    node: Parameters<typeof printRaw>[0],
+    originalText: string,
+    path: FastPath,
+    print: PrintFn,
+): Doc {
+    const result: Doc = [];
+    const length = node.children.length;
+    for (let i = 0; i < length; i++) {
+        const child = node.children[i];
+        if (child.type === 'Text') {
+            const lines = originalText.substring(child.start, child.end).split(/\r?\n/);
+            lines.forEach((line, j) => {
+                if (j > 0) result.push(literalline);
+                result.push(line);
+            });
+        } else {
+            result.push(path.call(print, 'children', i));
+        }
+    }
+    return concat(result);
+}
+
 function printChildren(path: FastPath, print: PrintFn, options: ParserOptions): Doc {
     if (isPreTagContent(path)) {
         return concat(path.map(print, 'children'));
     }
 
     const childNodes: Node[] = prepareChildren(path.getValue().children, path, print);
-    // modifiy original array because it's accessed later through map(print, 'children', idx)
+    // modify original array because it's accessed later through map(print, 'children', idx)
     path.getValue().children = childNodes;
     if (childNodes.length === 0) {
         return '';
@@ -838,7 +861,7 @@ function printChildren(path: FastPath, print: PrintFn, options: ParserOptions):
             (nextChild.type !== 'Text' ||
                 // Only handle text which starts with a whitespace and has text afterwards,
                 // or is empty but followed by an inline element. The latter is done
-                // so that if the children break, the inline element afterwards is in a seperate line.
+                // so that if the children break, the inline element afterwards is in a separate line.
                 ((!isEmptyTextNode(nextChild) ||
                     (childNodes[idx + 2] && isInlineElement(path, options, childNodes[idx + 2]))) &&
                     !isTextNodeStartingWithLinebreak(nextChild)))
@@ -850,7 +873,7 @@ function printChildren(path: FastPath, print: PrintFn, options: ParserOptions):
 
     /**
      * Print text child. First/last child white space handling
-     * is done in parent already. By defintion of the Svelte AST,
+     * is done in parent already. By definition of the Svelte AST,
      * a text node always is inbetween other tags. Add hardlines
      * if the users wants to have them inbetween.
      * If the text is trimmed right, toggle flag telling
@@ -907,7 +930,7 @@ function printChildren(path: FastPath, print: PrintFn, options: ParserOptions):
 
 /**
  * `svelte:options` is part of the html part but needs to be snipped out and handled
- * seperately to reorder it as configured. The comment above it should be moved with it.
+ * separately to reorder it as configured. The comment above it should be moved with it.
  * Do that here.
  */
 function prepareChildren(children: Node[], path: FastPath, print: PrintFn): Node[] {
diff --git a/src/print/node-helpers.ts b/src/print/node-helpers.ts
index 0d8f94b..3699bfa 100644
--- a/src/print/node-helpers.ts
+++ b/src/print/node-helpers.ts
@@ -335,7 +335,7 @@ export function trimTextNodeLeft(node: TextNode): void {
 
 /**
  * Remove all leading whitespace up until the first non-empty text node,
- * and all trailing whitepsace from the last non-empty text node onwards.
+ * and all trailing whitespace from the last non-empty text node onwards.
  */
 export function trimChildren(children: Node[], path: FastPath): void {
     let firstNonEmptyNode = children.findIndex(
@@ -370,7 +370,7 @@ export function trimChildren(children: Node[], path: FastPath): void {
 }
 
 /**
- * Check if given node's starg tag should hug its first child. This is the case for inline elements when there's
+ * Check if given node's start tag should hug its first child. This is the case for inline elements when there's
  * no whitespace between the `>` and the first child.
  */
 export function shouldHugStart(


Test patch:
diff --git a/test/formatting/samples/pre-mustache/input.html b/test/formatting/samples/pre-mustache/input.html
new file mode 100644
index 0000000..775241c
--- /dev/null
+++ b/test/formatting/samples/pre-mustache/input.html
@@ -0,0 +1,16 @@
+<pre>Hello,
+  world!   {JSON.stringify({   foo:'bar'},null,
+  2)}Goodbye.
+  this text goes waaaaaaaaaaaaaaaaaaaaaaaaaaaaaayyyyyyyyyyyyyyyyyyyyyyyyyyy above the print width {{a: true}} and the mustache should split it
+  <code>const asd = {
+    variable };</code>
+      <code>nested
+
+        should
+
+        stay
+
+        as is
+      </code>
+			this line is intended using tabs which should be preserved
+</pre>
diff --git a/test/formatting/samples/pre-mustache/output.html b/test/formatting/samples/pre-mustache/output.html
new file mode 100644
index 0000000..6d3d431
--- /dev/null
+++ b/test/formatting/samples/pre-mustache/output.html
@@ -0,0 +1,17 @@
+<pre>Hello,
+  world!   {JSON.stringify({ foo: "bar" }, null, 2)}Goodbye.
+  this text goes waaaaaaaaaaaaaaaaaaaaaaaaaaaaaayyyyyyyyyyyyyyyyyyyyyyyyyyy above the print width {{
+        a: true,
+    }} and the mustache should split it
+  <code>const asd = {variable};</code>
+      <code
+        >nested
+
+        should
+
+        stay
+
+        as is
+      </code>
+			this line is intended using tabs which should be preserved
+</pre>