Repository: sveltejs/prettier-plugin-svelte (ts)

Issue:
[MARKUP] text warped to next line inside tags.
### Input
```html
<h1>
  Svelte Materialify
  <small class="text-h5 d-block text--secondary">
    🔨 Under Construction
  </small>
</h1>
```

### Output
```html
<h1>
  Svelte Materialify <small class="text-h5 d-block text--secondary"> 🔨 Under
    Construction </small>
</h1>
```

The `🔨 Under Construction` should stay in one line, looks ugly otherwise.


Pull request description:
Keep inline elements together even when inside a text
Do:

```
<div>
    This text combined with
    <b>this text is longer than a line, but the second text by itself isn't.</b>
</div>
```

Don't

```
<div>
    This text combined with <b>this text is longer than a line, but the 
        second text by itself isn't.</b>
</div>
```

Fixes #139

The real change in the code is having `extractOutermostNewlines` run on each batched node individually, rather than all of them as a group. This pulls up `line` nodes one level higher, outside of the `flow` they'd otherwise be in. I hope this doesn't have side-effects elsewhere, but I guess we'll expand tests as we find new special cases. There's certainly plenty of those...


Patch:
diff --git a/src/print/index.ts b/src/print/index.ts
index 628d183..8de0040 100644
--- a/src/print/index.ts
+++ b/src/print/index.ts
@@ -22,10 +22,10 @@ import {
 import {
     isLine,
     isLineDiscardedIfLonely,
-    isEmptyGroup,
     trim,
     trimLeft,
     trimRight,
+    isEmptyDoc,
 } from './doc-helpers';
 
 const {
@@ -540,14 +540,12 @@ function printChildren(path: FastPath, print: PrintFn): Doc[] {
 
     /**
      * Add a document to the output.
-     * @param childDoc null means do not add anything but allow for the possibility of a linebreak here.
+     * @param childDoc undefined means do not add anything but allow for the possibility of a linebreak here.
+     * @param fromNode the Node the doc was generated from. undefined if childDoc is undefined.
      */
-    function outputChildDoc(childDoc: Doc | null, fromNodes: Node[]) {
+    function outputChildDoc(childDoc?: Doc, fromNode?: Node) {
         if (!isPreformat) {
-            const firstNode = fromNodes[0];
-            const lastNode = fromNodes[fromNodes.length - 1];
-
-            if (!childDoc || canBreakBefore(firstNode)) {
+            if ((!childDoc || !fromNode || canBreakBefore(fromNode))) {
                 linebreakPossible();
 
                 const lastChild = childDocs[childDocs.length - 1];
@@ -564,7 +562,7 @@ function printChildren(path: FastPath, print: PrintFn): Doc[] {
                 }
             }
 
-            if (lastBreakIndex < 0 && childDoc && !canBreakAfter(lastNode)) {
+            if (lastBreakIndex < 0 && childDoc && fromNode && !canBreakAfter(fromNode)) {
                 lastBreakIndex = childDocs.length;
             }
         }
@@ -576,7 +574,7 @@ function printChildren(path: FastPath, print: PrintFn): Doc[] {
 
     function lastChildDocProduced() {
         // line breaks are ok after last child
-        outputChildDoc(null, []);
+        outputChildDoc();
     }
 
     /**
@@ -587,11 +585,10 @@ function printChildren(path: FastPath, print: PrintFn): Doc[] {
      * desired to have text directly wrapping a mustache tag without additional whitespace.
      */
     function flush() {
-        let groupDocs = currentGroup.map((item) => item.doc);
-        const groupNodes = currentGroup.map((item) => item.node);
-
-        for (let doc of extractOutermostNewlines(groupDocs)) {
-            outputChildDoc(doc, groupNodes);
+        for (let { doc, node } of currentGroup) {
+            for (const childDoc of extractOutermostNewlines(doc)) {
+                outputChildDoc(childDoc, node);
+            }
         }
 
         currentGroup = [];
@@ -607,9 +604,10 @@ function printChildren(path: FastPath, print: PrintFn): Doc[] {
             flush();
 
             if (childDoc !== '') {
-                outputChildDoc(isLine(childDoc) ? childDoc : concat([breakParent, childDoc]), [
+                outputChildDoc(
+                    isLine(childDoc) ? childDoc : concat([breakParent, childDoc]),
                     childNode,
-                ]);
+                );
             }
         }
     }, 'children');
@@ -680,16 +678,11 @@ function dedentFinalNewline(docs: Doc[]): Doc[] {
 /**
  * Pull out any nested leading or trailing lines and put them at the top level.
  */
+function extractOutermostNewlines(doc: Doc): Doc[] {
+    const leadingLines: Doc[] = trimLeft([doc], isLine) || [];
+    const trailingLines: Doc[] = trimRight([doc], isLine) || [];
 
-function extractOutermostNewlines(docs: Doc[]): Doc[] {
-    const leadingLines: Doc[] = trimLeft(docs, isLine) || [];
-    const trailingLines: Doc[] = trimRight(docs, isLine) || [];
-
-    return [
-        ...leadingLines,
-        ...(!isEmptyGroup(docs) ? [fill(docs)] : ([] as Doc[])),
-        ...trailingLines,
-    ];
+    return [...leadingLines, ...(!isEmptyDoc(doc) ? [doc] : ([] as Doc[])), ...trailingLines];
 }
 
 function printJS(path: FastPath, print: PrintFn, name?: string) {
diff --git a/src/print/node-helpers.ts b/src/print/node-helpers.ts
index 20df0d3..0536995 100644
--- a/src/print/node-helpers.ts
+++ b/src/print/node-helpers.ts
@@ -26,6 +26,10 @@ export function canBreakAfter(node: Node) {
             return isWhitespaceChar(node.raw[node.raw.length - 1]);
         case 'Element':
             return !isInlineElement(node);
+        case 'IfBlock':
+        case 'EachBlock':
+        case 'MustacheTag':
+            return false;
         default:
             return true;
     }
@@ -37,6 +41,10 @@ export function canBreakBefore(node: Node) {
             return isWhitespaceChar(node.raw[0]);
         case 'Element':
             return !isInlineElement(node);
+        case 'IfBlock':
+        case 'EachBlock':
+        case 'MustacheTag':
+            return false;
         default:
             return true;
     }


Test patch:
diff --git a/test/formatting/samples/no-html-whitespace-inside-inline-element/output.html b/test/formatting/samples/no-html-whitespace-inside-inline-element/output.html
index e2e7671..90ab355 100644
--- a/test/formatting/samples/no-html-whitespace-inside-inline-element/output.html
+++ b/test/formatting/samples/no-html-whitespace-inside-inline-element/output.html
@@ -1,4 +1,8 @@
 <p>
-    <b>Apples</b>, <b>Orange</b>, <b>Bananas</b>, <b>Pineapples</b>, <b>Grapefruit</b>,
+    <b>Apples</b>,
+    <b>Orange</b>,
+    <b>Bananas</b>,
+    <b>Pineapples</b>,
+    <b>Grapefruit</b>,
     <b>Kiwi</b>
 </p>
diff --git a/test/printer/samples/element-with-text-and-inline-element.html b/test/printer/samples/element-with-text-and-inline-element.html
new file mode 100644
index 0000000..c81ee32
--- /dev/null
+++ b/test/printer/samples/element-with-text-and-inline-element.html
@@ -0,0 +1,7 @@
+<div>
+    <p>
+        This is line A.
+        <b>This is B. len(A + B) &gt; 80+ chars. len(b) lt; 80.</b>
+    </p>
+    <p>This is line A. <b>This is B. len(A + B) &lt; 80 chars.</b></p>
+</div>