Repository: sveltejs/prettier-plugin-svelte
Pull request #191 (2021-01-11 10:50:28)

(feat) Support Prettier's `htmlWhitespaceSensitivity` setting


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2e8b5f0..14df444 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,8 @@
 # prettier-plugin-svelte changelog
 
-## 2.0.4 (Unreleased)
+## 2.1.0 (Unreleased)
 
+* Support Prettier's `htmlWhitespaceSensitivity` setting.
 * When `svelteBracketNewLine` is set to `true` and only the closing tag has whitespace before it, print the closing `>` in a separate line.
 
 ## 2.0.3
diff --git a/src/print/index.ts b/src/print/index.ts
index 24f043d..56aaef8 100644
--- a/src/print/index.ts
+++ b/src/print/index.ts
@@ -116,7 +116,7 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D
             if (!isPreTagContent(path)) {
                 trimChildren(node.children, path);
                 const output = trim(
-                    [printChildren(path, print)],
+                    [printChildren(path, print, options)],
                     (n) =>
                         isLine(n) ||
                         (typeof n === 'string' && n.trim() === '') ||
@@ -210,12 +210,12 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D
             let body: () => Doc;
 
             let hugContent = false;
-            const hugStart = shouldHugStart(node, isSupportedLanguage);
-            const hugEnd = shouldHugEnd(node, isSupportedLanguage);
+            const hugStart = shouldHugStart(node, isSupportedLanguage, options);
+            const hugEnd = shouldHugEnd(node, isSupportedLanguage, options);
 
             if (isEmpty) {
                 body =
-                    isInlineElement(path, node) &&
+                    isInlineElement(path, options, node) &&
                     node.children.length &&
                     isTextNodeStartingWithWhitespace(node.children[0]) &&
                     !isPreTagContent(path)
@@ -226,11 +226,11 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D
             } else if (!isSupportedLanguage) {
                 body = () => printRaw(node, options.originalText, true);
                 hugContent = true;
-            } else if (isInlineElement(path, node) && !isPreTagContent(path)) {
-                body = () => printChildren(path, print);
+            } else if (isInlineElement(path, options, node) && !isPreTagContent(path)) {
+                body = () => printChildren(path, print, options);
                 hugContent = true;
             } else {
-                body = () => printChildren(path, print);
+                body = () => printChildren(path, print, options);
             }
 
             const openingTag = [
@@ -287,18 +287,19 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D
                     if (
                         isTextNodeStartingWithLinebreak(firstChild) &&
                         firstChild !== lastChild &&
-                        (!isInlineElement(path, node) || isTextNodeEndingWithWhitespace(lastChild))
+                        (!isInlineElement(path, options, node) ||
+                            isTextNodeEndingWithWhitespace(lastChild))
                     ) {
                         noHugSeparatorStart = hardline;
                         noHugSeparatorEnd = hardline;
                         didSetEndSeparator = true;
-                    } else if (isInlineElement(path, node)) {
+                    } else if (isInlineElement(path, options, node)) {
                         noHugSeparatorStart = line;
                     }
                     trimTextNodeLeft(firstChild);
                 }
                 if (!hugEnd && lastChild && lastChild.type === 'Text') {
-                    if (isInlineElement(path, node) && !didSetEndSeparator) {
+                    if (isInlineElement(path, options, node) && !didSetEndSeparator) {
                         noHugSeparatorEnd = line;
                     }
                     trimTextNodeRight(lastChild);
@@ -716,10 +717,13 @@ function printSvelteBlockChildren(path: FastPath, print: PrintFn, options: Parse
         trimTextNodeRight(lastChild);
     }
 
-    return concat([indent(concat([startline, group(printChildren(path, print))])), endline]);
+    return concat([
+        indent(concat([startline, group(printChildren(path, print, options))])),
+        endline,
+    ]);
 }
 
-function printChildren(path: FastPath, print: PrintFn): Doc {
+function printChildren(path: FastPath, print: PrintFn, options: ParserOptions): Doc {
     if (isPreTagContent(path)) {
         return concat(path.map(print, 'children'));
     }
@@ -738,9 +742,9 @@ function printChildren(path: FastPath, print: PrintFn): Doc {
         const childNode = childNodes[i];
         if (childNode.type === 'Text') {
             handleTextChild(i, childNode);
-        } else if (isBlockElement(childNode)) {
+        } else if (isBlockElement(childNode, options)) {
             handleBlockChild(i);
-        } else if (isInlineElement(path, childNode)) {
+        } else if (isInlineElement(path, options, childNode)) {
             handleInlineChild(i);
         } else {
             childDocs.push(printChild(i));
@@ -750,7 +754,7 @@ function printChildren(path: FastPath, print: PrintFn): Doc {
 
     // If there's at least one block element and more than one node, break content
     const forceBreakContent =
-        childNodes.length > 1 && childNodes.some((child) => isBlockElement(child));
+        childNodes.length > 1 && childNodes.some((child) => isBlockElement(child, options));
     if (forceBreakContent) {
         childDocs.push(breakParent);
     }
@@ -783,7 +787,7 @@ function printChildren(path: FastPath, print: PrintFn): Doc {
         const prevChild = childNodes[idx - 1];
         if (
             prevChild &&
-            !isBlockElement(prevChild) &&
+            !isBlockElement(prevChild, options) &&
             (prevChild.type !== 'Text' ||
                 handleWhitespaceOfPrevTextNode ||
                 !isTextNodeEndingWithWhitespace(prevChild))
@@ -801,7 +805,7 @@ function printChildren(path: FastPath, print: PrintFn): Doc {
                 // 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.
                 ((!isEmptyTextNode(nextChild) ||
-                    (childNodes[idx + 2] && isInlineElement(path, childNodes[idx + 2]))) &&
+                    (childNodes[idx + 2] && isInlineElement(path, options, childNodes[idx + 2]))) &&
                     !isTextNodeStartingWithLinebreak(nextChild)))
         ) {
             childDocs.push(softline);
@@ -834,24 +838,30 @@ function printChildren(path: FastPath, print: PrintFn): Doc {
             // If node is empty, go straight through to checking the right end
             !isEmptyTextNode(childNode)
         ) {
-            if (isInlineElement(path, prevNode) && !isTextNodeStartingWithLinebreak(childNode)) {
+            if (
+                isInlineElement(path, options, prevNode) &&
+                !isTextNodeStartingWithLinebreak(childNode)
+            ) {
                 trimTextNodeLeft(childNode);
                 const lastChildDoc = childDocs.pop()!;
                 childDocs.push(groupConcat([lastChildDoc, line]));
             }
 
-            if (isBlockElement(prevNode) && !isTextNodeStartingWithLinebreak(childNode)) {
+            if (isBlockElement(prevNode, options) && !isTextNodeStartingWithLinebreak(childNode)) {
                 trimTextNodeLeft(childNode);
             }
         }
 
         if (isTextNodeEndingWithWhitespace(childNode)) {
-            if (isInlineElement(path, nextNode) && !isTextNodeEndingWithLinebreak(childNode)) {
-                handleWhitespaceOfPrevTextNode = !prevNode || !isBlockElement(prevNode);
+            if (
+                isInlineElement(path, options, nextNode) &&
+                !isTextNodeEndingWithLinebreak(childNode)
+            ) {
+                handleWhitespaceOfPrevTextNode = !prevNode || !isBlockElement(prevNode, options);
                 trimTextNodeRight(childNode);
             }
-            if (isBlockElement(nextNode) && !isTextNodeEndingWithLinebreak(childNode, 2)) {
-                handleWhitespaceOfPrevTextNode = !prevNode || !isBlockElement(prevNode);
+            if (isBlockElement(nextNode, options) && !isTextNodeEndingWithLinebreak(childNode, 2)) {
+                handleWhitespaceOfPrevTextNode = !prevNode || !isBlockElement(prevNode, options);
                 trimTextNodeRight(childNode);
             }
         }
diff --git a/src/print/node-helpers.ts b/src/print/node-helpers.ts
index 8d41bb0..2e4afd2 100644
--- a/src/print/node-helpers.ts
+++ b/src/print/node-helpers.ts
@@ -25,12 +25,20 @@ import { findLastIndex, isASTNode, isPreTagContent } from './helpers';
 
 const unsupportedLanguages = ['coffee', 'coffeescript', 'pug', 'styl', 'stylus', 'sass'];
 
-export function isInlineElement(path: FastPath, node: Node) {
-    return node && node.type === 'Element' && !isBlockElement(node) && !isPreTagContent(path);
+export function isInlineElement(path: FastPath, options: ParserOptions, node: Node) {
+    return (
+        node && node.type === 'Element' && !isBlockElement(node, options) && !isPreTagContent(path)
+    );
 }
 
-export function isBlockElement(node: Node): node is ElementNode {
-    return node && node.type === 'Element' && blockElements.includes(node.name as TagName);
+export function isBlockElement(node: Node, options: ParserOptions): node is ElementNode {
+    return (
+        node &&
+        node.type === 'Element' &&
+        options.htmlWhitespaceSensitivity !== 'strict' &&
+        (options.htmlWhitespaceSensitivity === 'ignore' ||
+            blockElements.includes(node.name as TagName))
+    );
 }
 
 export function isSvelteBlock(
@@ -280,12 +288,16 @@ 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
  * no whitespace between the `>` and the first child.
  */
-export function shouldHugStart(node: Node, isSupportedLanguage: boolean): boolean {
+export function shouldHugStart(
+    node: Node,
+    isSupportedLanguage: boolean,
+    options: ParserOptions,
+): boolean {
     if (!isSupportedLanguage) {
         return true;
     }
 
-    if (isBlockElement(node)) {
+    if (isBlockElement(node, options)) {
         return false;
     }
 
@@ -306,12 +318,16 @@ export function shouldHugStart(node: Node, isSupportedLanguage: boolean): boolea
  * Check if given node's end tag should hug its last child. This is the case for inline elements when there's
  * no whitespace between the last child and the `</`.
  */
-export function shouldHugEnd(node: Node, isSupportedLanguage: boolean): boolean {
+export function shouldHugEnd(
+    node: Node,
+    isSupportedLanguage: boolean,
+    options: ParserOptions,
+): boolean {
     if (!isSupportedLanguage) {
         return true;
     }
 
-    if (isBlockElement(node)) {
+    if (isBlockElement(node, options)) {
         return false;
     }
 
@@ -421,7 +437,7 @@ export function canOmitSoftlineBeforeClosingTag(
 ): boolean {
     return (
         !options.svelteBracketNewLine &&
-        (!hugsStartOfNextNode(node, options) || isLastChildWithinParentBlockElement(path))
+        (!hugsStartOfNextNode(node, options) || isLastChildWithinParentBlockElement(path, options))
     );
 }
 
@@ -438,9 +454,9 @@ function hugsStartOfNextNode(node: Node, options: ParserOptions): boolean {
     return !options.originalText.substring(node.end).match(/^\s/);
 }
 
-function isLastChildWithinParentBlockElement(path: FastPath): boolean {
+function isLastChildWithinParentBlockElement(path: FastPath, options: ParserOptions): boolean {
     const parent = path.getParentNode() as Node | undefined;
-    if (!parent || !isBlockElement(parent)) {
+    if (!parent || !isBlockElement(parent, options)) {
         return false;
     }
 


Test patch:
diff --git a/test/formatting/samples/all-elements-ignore-whitespace/input.html b/test/formatting/samples/all-elements-ignore-whitespace/input.html
new file mode 100644
index 0000000..0b734d4
--- /dev/null
+++ b/test/formatting/samples/all-elements-ignore-whitespace/input.html
@@ -0,0 +1,1 @@
+<span><span>looooooooooong</span>looooooooooonglooooooooooonglooooooooooonglooooooooooonglooooooooooong<span><span>hi</span><span>hi</span></span></span>
diff --git a/test/formatting/samples/all-elements-ignore-whitespace/options.json b/test/formatting/samples/all-elements-ignore-whitespace/options.json
new file mode 100644
index 0000000..e3e1f3e
--- /dev/null
+++ b/test/formatting/samples/all-elements-ignore-whitespace/options.json
@@ -0,0 +1,3 @@
+{
+    "htmlWhitespaceSensitivity": "ignore"
+}
diff --git a/test/formatting/samples/all-elements-ignore-whitespace/output.html b/test/formatting/samples/all-elements-ignore-whitespace/output.html
new file mode 100644
index 0000000..0317a9d
--- /dev/null
+++ b/test/formatting/samples/all-elements-ignore-whitespace/output.html
@@ -0,0 +1,8 @@
+<span>
+    <span>looooooooooong</span>
+    looooooooooonglooooooooooonglooooooooooonglooooooooooonglooooooooooong
+    <span>
+        <span>hi</span>
+        <span>hi</span>
+    </span>
+</span>
diff --git a/test/formatting/samples/all-elements-strict-whitespace/input.html b/test/formatting/samples/all-elements-strict-whitespace/input.html
new file mode 100644
index 0000000..f4d9757
--- /dev/null
+++ b/test/formatting/samples/all-elements-strict-whitespace/input.html
@@ -0,0 +1,1 @@
+<div><div>looooooooooong</div>looooooooooonglooooooooooonglooooooooooonglooooooooooonglooooooooooong<div><p>hi</p><p>hi</p></div></div>
diff --git a/test/formatting/samples/all-elements-strict-whitespace/options.json b/test/formatting/samples/all-elements-strict-whitespace/options.json
new file mode 100644
index 0000000..b8936f4
--- /dev/null
+++ b/test/formatting/samples/all-elements-strict-whitespace/options.json
@@ -0,0 +1,3 @@
+{
+    "htmlWhitespaceSensitivity": "strict"
+}
diff --git a/test/formatting/samples/all-elements-strict-whitespace/output.html b/test/formatting/samples/all-elements-strict-whitespace/output.html
new file mode 100644
index 0000000..d774c08
--- /dev/null
+++ b/test/formatting/samples/all-elements-strict-whitespace/output.html
@@ -0,0 +1,6 @@
+<div
+    ><div>looooooooooong</div
+    >looooooooooonglooooooooooonglooooooooooonglooooooooooonglooooooooooong<div
+        ><p>hi</p><p>hi</p></div
+    ></div
+>