diff --git a/src/language-handlebars/printer-glimmer.js b/src/language-handlebars/printer-glimmer.js
index c0919657f..50092ce7f 100644
--- a/src/language-handlebars/printer-glimmer.js
+++ b/src/language-handlebars/printer-glimmer.js
@@ -1,7 +1,7 @@
"use strict";
const {
- builders: { group, hardline, ifBreak, indent, join, line, softline },
+ builders: { dedent, group, hardline, ifBreak, indent, join, line, softline },
utils: { getDocParts },
} = require("../document");
const { isNonEmptyArray } = require("../common/util");
@@ -12,6 +12,7 @@ const {
getNextNode,
getPreviousNode,
hasPrettierIgnore,
+ isLastNodeOfSiblings,
isNextNodeOfSomeType,
isNodeOfSomeType,
isParentOfSomeType,
@@ -41,28 +42,44 @@ function print(path, options, print) {
case "Template": {
return group(path.map(print, "body"));
}
+
case "ElementNode": {
- // TODO: make it whitespace sensitive
- const bim = isNextNodeOfSomeType(path, ["ElementNode"]) ? hardline : "";
+ const startingTag = group(printStartingTag(path, print));
+
+ const escapeNextElementNode =
+ options.htmlWhitespaceSensitivity !== "strict" &&
+ isNextNodeOfSomeType(path, ["ElementNode"])
+ ? softline
+ : "";
if (isVoid(n)) {
- return [group(printStartingTag(path, print)), bim];
+ return [startingTag, escapeNextElementNode];
}
- const isWhitespaceOnly = n.children.every((n) => isWhitespaceNode(n));
+ const endingTag = ["", n.tag, ">"];
+
+ if (n.children.length === 0) {
+ return [startingTag, indent(endingTag), escapeNextElementNode];
+ }
+
+ if (options.htmlWhitespaceSensitivity !== "strict") {
+ return [
+ startingTag,
+ indent(printChildren(path, options, print)),
+ hardline,
+ indent(endingTag),
+ escapeNextElementNode,
+ ];
+ }
return [
- group(printStartingTag(path, print)),
- group([
- isWhitespaceOnly ? "" : indent(printChildren(path, options, print)),
- n.children.length > 0 ? hardline : "",
- "",
- n.tag,
- ">",
- ]),
- bim,
+ startingTag,
+ indent(group(printChildren(path, options, print))),
+ indent(endingTag),
+ escapeNextElementNode,
];
}
+
case "BlockStatement": {
const pp = path.getParentNode(1);
@@ -76,20 +93,21 @@ function print(path, options, print) {
if (isElseIf) {
return [
printElseIfBlock(path, print),
- printProgram(path, print),
- printInverse(path, print),
+ printProgram(path, print, options),
+ printInverse(path, print, options),
];
}
return [
printOpenBlock(path, print),
group([
- printProgram(path, print),
- printInverse(path, print),
- printCloseBlock(path, print),
+ printProgram(path, print, options),
+ printInverse(path, print, options),
+ printCloseBlock(path, print, options),
]),
];
}
+
case "ElementModifierStatement": {
return group(["{{", printPathAndParams(path, print), softline, "}}"]);
}
@@ -160,6 +178,65 @@ function print(path, options, print) {
return [n.key, "=", path.call(print, "value")];
}
case "TextNode": {
+ const inAttrNode = path.stack.includes("attributes");
+
+ if (options.htmlWhitespaceSensitivity === "strict" && !inAttrNode) {
+ // https://infra.spec.whatwg.org/#ascii-whitespace
+ const leadingWhitespacesRE = /^[\t\n\f\r ]*/;
+ const trailingWhitespacesRE = /[\t\n\f\r ]*$/;
+ const whitespacesOnlyRE = /^[\t\n\f\r ]*$/;
+
+ if (whitespacesOnlyRE.test(n.chars)) {
+ let breaks = [line];
+
+ const newlines = countNewLines(n.chars);
+ if (newlines) {
+ breaks = generateHardlines(newlines, 2);
+ }
+
+ if (isLastNodeOfSiblings(path)) {
+ breaks = breaks.map((newline) => dedent(newline));
+ }
+
+ return breaks;
+ }
+
+ const [lead] = n.chars.match(leadingWhitespacesRE);
+ const [tail] = n.chars.match(trailingWhitespacesRE);
+
+ let text = n.chars;
+
+ let leadBreaks = [];
+ if (lead) {
+ leadBreaks = [line];
+
+ const leadingNewlines = countNewLines(lead);
+ if (leadingNewlines) {
+ leadBreaks = generateHardlines(countNewLines(lead) || 1, 2);
+ }
+
+ text = text.replace(leadingWhitespacesRE, "");
+ }
+
+ let trailBreaks = [];
+ if (tail) {
+ trailBreaks = [line];
+
+ const trailingNewlines = countNewLines(tail);
+ if (trailingNewlines) {
+ trailBreaks = generateHardlines(trailingNewlines || 1, 2);
+
+ if (isLastNodeOfSiblings(path)) {
+ trailBreaks = trailBreaks.map((hardline) => dedent(hardline));
+ }
+ }
+
+ text = text.replace(trailingWhitespacesRE, "");
+ }
+
+ return [...leadBreaks, text, ...trailBreaks];
+ }
+
const maxLineBreaksToPreserve = 2;
const isFirstElement = !getPreviousNode(path);
const isLastElement = !getNextNode(path);
@@ -193,7 +270,6 @@ function print(path, options, print) {
}
}
- const inAttrNode = path.stack.includes("attributes");
if (inAttrNode) {
// TODO: format style and srcset attributes
if (!isInAttributeOfName(path, "class")) {
@@ -349,12 +425,20 @@ function printStartingTag(path, print) {
}
function printChildren(path, options, print) {
+ const node = path.getValue();
+ const isEmpty = node.children.every((n) => isWhitespaceNode(n));
+ if (options.htmlWhitespaceSensitivity !== "strict" && isEmpty) {
+ return "";
+ }
+
return path.map((childPath, childIndex) => {
- if (childIndex === 0) {
- return [softline, print(childPath, options, print)];
+ const printedChild = print(childPath, options, print);
+
+ if (childIndex === 0 && options.htmlWhitespaceSensitivity !== "strict") {
+ return [softline, printedChild];
}
- return print(childPath, options, print);
+ return printedChild;
}, "children");
}
@@ -444,9 +528,9 @@ function printOpenBlock(path, print) {
]);
}
-function printElseBlock(node) {
+function printElseBlock(node, options) {
return [
- hardline,
+ options.htmlWhitespaceSensitivity !== "strict" ? hardline : "",
printInverseBlockOpeningMustache(node),
"else",
printInverseBlockClosingMustache(node),
@@ -464,11 +548,23 @@ function printElseIfBlock(path, print) {
];
}
-function printCloseBlock(path, print) {
+function printCloseBlock(path, print, options) {
const node = path.getValue();
+ if (options.htmlWhitespaceSensitivity !== "strict") {
+ const escape = blockStatementHasOnlyWhitespaceInProgram(node)
+ ? softline
+ : hardline;
+
+ return [
+ escape,
+ printClosingBlockOpeningMustache(node),
+ path.call(print, "path"),
+ printClosingBlockClosingMustache(node),
+ ];
+ }
+
return [
- blockStatementHasOnlyWhitespaceInProgram(node) ? softline : hardline,
printClosingBlockOpeningMustache(node),
path.call(print, "path"),
printClosingBlockClosingMustache(node),
@@ -495,7 +591,7 @@ function blockStatementHasElse(node) {
return isNodeOfSomeType(node, ["BlockStatement"]) && node.inverse;
}
-function printProgram(path, print) {
+function printProgram(path, print, options) {
const node = path.getValue();
if (blockStatementHasOnlyWhitespaceInProgram(node)) {
@@ -503,21 +599,29 @@ function printProgram(path, print) {
}
const program = path.call(print, "program");
- return indent([hardline, program]);
+
+ if (options.htmlWhitespaceSensitivity !== "strict") {
+ return indent([hardline, program]);
+ }
+
+ return indent(program);
}
-function printInverse(path, print) {
+function printInverse(path, print, options) {
const node = path.getValue();
const inverse = path.call(print, "inverse");
- const parts = [hardline, inverse];
+ const printed =
+ options.htmlWhitespaceSensitivity !== "strict"
+ ? [hardline, inverse]
+ : inverse;
if (blockStatementHasElseIf(node)) {
- return parts;
+ return printed;
}
if (blockStatementHasElse(node)) {
- return [printElseBlock(node), indent(parts)];
+ return [printElseBlock(node, options), indent(printed)];
}
return "";
diff --git a/src/language-handlebars/utils.js b/src/language-handlebars/utils.js
index c7b20c3d4..7b41711cb 100644
--- a/src/language-handlebars/utils.js
+++ b/src/language-handlebars/utils.js
@@ -2,6 +2,27 @@
const htmlVoidElements = require("html-void-elements");
+function isLastNodeOfSiblings(path) {
+ const node = path.getValue();
+ const parentNode = path.getParentNode(0);
+
+ if (
+ isParentOfSomeType(path, ["ElementNode"]) &&
+ parentNode.children[parentNode.children.length - 1] === node
+ ) {
+ return true;
+ }
+
+ if (
+ isParentOfSomeType(path, ["Block"]) &&
+ parentNode.body[parentNode.body.length - 1] === node
+ ) {
+ return true;
+ }
+
+ return false;
+}
+
function isUppercase(string) {
return string.toUpperCase() === string;
}
@@ -83,6 +104,7 @@ module.exports = {
getNextNode,
getPreviousNode,
hasPrettierIgnore,
+ isLastNodeOfSiblings,
isNextNodeOfSomeType,
isNodeOfSomeType,
isParentOfSomeType,
diff --git a/tests/handlebars/html-whitespace-sensitivity/__snapshots__/jsfmt.spec.js.snap b/tests/handlebars/html-whitespace-sensitivity/__snapshots__/jsfmt.spec.js.snap
index 5168494e5..5cb12c015 100644
--- a/tests/handlebars/html-whitespace-sensitivity/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/handlebars/html-whitespace-sensitivity/__snapshots__/jsfmt.spec.js.snap
@@ -433,125 +433,118 @@ sometimes{{nogaps}}areimportant
- Hi {{firstName}} {{ - lastName - }} , welcome! -
+Hi + {{firstName}} + {{lastName}} + , welcome!
{{#component propA}} - for {{propB}} do {{propC}} f + for + {{propB}} + do + {{propC}} + f {{/component}} {{#component propA}} - for {{propB}} - - name - - do {{propC}} f + for + {{propB}} + namedo + {{propC}} + f {{/component}} -{{propA}} {{propB}} +{{propA}} +{{propB}} {{propC}}{{propD}} - - {{propE}} {{propF}} - - - {{propG}}{{propH}} - +{{propE}} {{propF}} +{{propG}}{{propH}} hey - +| - A - | -- B - | -- C - | +A | +B | +C |
|---|
| - A - | -- B - | -- C - | -
|---|
| A | B | C |
|---|
| - A - | - -- B - | - -- C - | +A | +B | +C |
|---|
X or Y
X or Y
+=====================================output===================================== +X + or + Y
X or Y
+================================================================================ +`; + +exports[`snippet: \`U+2005\` should format like \`U+005F\` not like \`U+0020\` - {"htmlWhitespaceSensitivity":"strict","printWidth":40} format 1`] = ` +====================================options===================================== +htmlWhitespaceSensitivity: "strict" +parsers: ["glimmer"] +printWidth: 40 + | printWidth +=====================================input====================================== + +X \u2005 or \u2005 Y
X \u2005 or \u2005 Y
", + + { + name: "`U+2005` should format like `U+005F` not like `U+0020`", + code: outdent` + +