handlebars: Support htmlWhitespaceSensitivity=strict option (2/3) (#9885)

* hbs: Add a strict mode

* Fix lint (after concat removal)

* Another lint error
This commit is contained in:
Cyrille David authored and GitHub committed 2021-01-18 15:49:39 +02:00
1 parent 8af20aeeaf
commit c8cbbb0a79
4 files changed
+466 -137

No files matched your search

+137 -33
View File
@@ -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 "";
+22
View File
@@ -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,
@@ -433,125 +433,118 @@ sometimes{{nogaps}}areimportant<Hello></Hello>
</table>
=====================================output=====================================
<p>
Hi {{firstName}} {{
lastName
}} , welcome!
</p>
<p>Hi
{{firstName}}
{{lastName}}
, welcome!</p>
{{#component propA}}
for {{propB}} do {{propC}} f
for
{{propB}}
do
{{propC}}
f
{{/component}}
{{#component propA}}
for {{propB}}
<span>
name
</span>
do {{propC}} f
for
{{propB}}
<span>name</span>do
{{propC}}
f
{{/component}}
{{propA}} {{propB}}
{{propA}}
{{propB}}
{{propC}}{{propD}}
<span>
{{propE}} {{propF}}
</span>
<span>
{{propG}}{{propH}}
</span>
<span>{{propE}} {{propF}}</span>
<span>{{propG}}{{propH}}</span>
hey
<button>
Click here! Click here! Click here! Click here! Click here! Click here!
</button>
<button
>Click here! Click here! Click here! Click here! Click here! Click here!</button>
<button>
Click here! Click here! Click here! Click here! Click here! Click here!
</button>
<div>
<button>
Click here! Click here! Click here! Click here! Click here! Click here!
</button>
<button>
Click here! Click here! Click here! Click here! Click here! Click here!
</button>
<button
>Click here! Click here! Click here! Click here! Click here! Click here!</button><button
>Click here! Click here! Click here! Click here! Click here! Click here!</button>
</div>
<div>
<button>
Click here! Click here! Click here! Click here! Click here! Click here!
</button>
<button>
Click here! Click here! Click here! Click here! Click here! Click here!
</button>
<button
>Click here! Click here! Click here! Click here! Click here! Click here!</button>
<button
>Click here! Click here! Click here! Click here! Click here! Click here!</button>
</div>
<video src="brave.webm"></video>
<SomeComponent />
{{name}}
Some sentence with {{dynamic
}} expressions.
Some sentence with
{{dynamic}}
expressions.
sometimes{{nogaps
}}areimportant
<Hello />
}}areimportant<Hello />
{{name}} is your name
{{name}}
is your name
{{#block}}{{/block}}
{{#block}}
some {{text}}
some
{{text}}
{{/block}}
{{#block}}
some {{text}}
some
{{text}}
{{/block}}
<HelloWorld />
<HelloWorld>
some {{text}}
some
{{text}}
</HelloWorld>
<HelloWorld>
some {{text}}
some
{{text}}
</HelloWorld>
<div class="a list of classes">
</div>
<div class="a list of classes">
</div>
<span>
This is your name: {{name}}.
</span>
<span>
This is your name: {{name}} (employee)
</span>
<span>
This is your name: {{name}} ({{role}})
</span>
<span>This is your name:
{{name}}.</span>
<span>This is your name:
{{name}}
(employee)</span>
<span>This is your name:
{{name}}
({{role}})</span>
<span>123</span>
<span>
123
</span>
<span>
123
</span>
<span>
123
123</span>
<span>123
</span>
<span>
123
</span>
<div>123</div>
<div>
123
</div>
<div>
123
</div>
<div>
123
123</div>
<div>123
</div>
<div>
123
@@ -560,49 +553,23 @@ sometimes{{nogaps
<table>
<thead>
<tr>
<th>
A
</th>
<th>
B
</th>
<th>
C
</th>
<th>A</th>
<th>B</th>
<th>C</th>
</tr>
</thead>
</table>
<table>
<thead>
<tr>
<th>
A
</th>
<th>
B
</th>
<th>
C
</th>
</tr>
</thead>
</table>
<table><thead><tr><th>A</th><th
>B</th><th
>C</th></tr></thead></table>
<table>
<thead>
<tr>
<th>
A
</th>
<th>
B
</th>
<th>
C
</th>
<th> A </th>
<th> B </th>
<th> C </th>
</tr>
</thead>
</table>
@@ -613,5 +580,164 @@ sometimes{{nogaps
</tr>
</thead>
</table>
================================================================================
`;
exports[`snippet: #12 - {"htmlWhitespaceSensitivity":"strict","printWidth":40} format 1`] = `
====================================options=====================================
htmlWhitespaceSensitivity: "strict"
parsers: ["glimmer"]
printWidth: 40
| printWidth
=====================================input======================================
<div> </div>
=====================================output=====================================
<div> </div>
================================================================================
`;
exports[`snippet: #13 - {"htmlWhitespaceSensitivity":"strict","printWidth":40} format 1`] = `
====================================options=====================================
htmlWhitespaceSensitivity: "strict"
parsers: ["glimmer"]
printWidth: 40
| printWidth
=====================================input======================================
<div>   </div>
=====================================output=====================================
<div>   </div>
================================================================================
`;
exports[`snippet: #14 - {"htmlWhitespaceSensitivity":"strict","printWidth":40} format 1`] = `
====================================options=====================================
htmlWhitespaceSensitivity: "strict"
parsers: ["glimmer"]
printWidth: 40
| printWidth
=====================================input======================================
<div>    </div>
=====================================output=====================================
<div>    </div>
================================================================================
`;
exports[`snippet: #15 - {"htmlWhitespaceSensitivity":"strict","printWidth":40} format 1`] = `
====================================options=====================================
htmlWhitespaceSensitivity: "strict"
parsers: ["glimmer"]
printWidth: 40
| printWidth
=====================================input======================================
<div>     </div>
=====================================output=====================================
<div>     </div>
================================================================================
`;
exports[`snippet: #16 - {"htmlWhitespaceSensitivity":"strict","printWidth":40} format 1`] = `
====================================options=====================================
htmlWhitespaceSensitivity: "strict"
parsers: ["glimmer"]
printWidth: 40
| printWidth
=====================================input======================================
<img/> <img/>
=====================================output=====================================
<img /> <img />
================================================================================
`;
exports[`snippet: #17 - {"htmlWhitespaceSensitivity":"strict","printWidth":40} format 1`] = `
====================================options=====================================
htmlWhitespaceSensitivity: "strict"
parsers: ["glimmer"]
printWidth: 40
| printWidth
=====================================input======================================
<img/>   <img/>
=====================================output=====================================
<img />   <img />
================================================================================
`;
exports[`snippet: #18 - {"htmlWhitespaceSensitivity":"strict","printWidth":40} format 1`] = `
====================================options=====================================
htmlWhitespaceSensitivity: "strict"
parsers: ["glimmer"]
printWidth: 40
| printWidth
=====================================input======================================
<img/>    <img/>
=====================================output=====================================
<img />    <img />
================================================================================
`;
exports[`snippet: #19 - {"htmlWhitespaceSensitivity":"strict","printWidth":40} format 1`] = `
====================================options=====================================
htmlWhitespaceSensitivity: "strict"
parsers: ["glimmer"]
printWidth: 40
| printWidth
=====================================input======================================
<img/>     <img/>
=====================================output=====================================
<img />     <img />
================================================================================
`;
exports[`snippet: #20 - {"htmlWhitespaceSensitivity":"strict","printWidth":40} format 1`] = `
====================================options=====================================
htmlWhitespaceSensitivity: "strict"
parsers: ["glimmer"]
printWidth: 40
| printWidth
=====================================input======================================
<i />   |   <i />
=====================================output=====================================
<i></i>   |   <i></i>
================================================================================
`;
exports[`snippet: #21 - {"htmlWhitespaceSensitivity":"strict","printWidth":40} format 1`] = `
====================================options=====================================
htmlWhitespaceSensitivity: "strict"
parsers: ["glimmer"]
printWidth: 40
| printWidth
=====================================input======================================
<p><span>X</span>   or   <span>Y</span></p><p>X   or   Y</p>
=====================================output=====================================
<p><span>X</span>
  or  
<span>Y</span></p><p>X   or   Y</p>
================================================================================
`;
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======================================
<!-- U+2005 -->
<div>before<span> </span>afterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafter</div>
<!-- U+005F -->
<div>before<span>_</span>afterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafter</div>
<!-- U+0020 -->
<div>before<span> </span>afterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafter</div>
=====================================output=====================================
<!-- U+2005 -->
<div>before<span
> </span>afterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafter</div>
<!-- U+005F -->
<div>before<span
>_</span>afterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafter</div>
<!-- U+0020 -->
<div>before<span>
</span>afterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafter</div>
================================================================================
`;
@@ -1,8 +1,85 @@
const { outdent } = require("outdent");
run_spec(__dirname, ["glimmer"], {
htmlWhitespaceSensitivity: "ignore",
printWidth: 40,
});
run_spec(__dirname, ["glimmer"], {
htmlWhitespaceSensitivity: "strict",
printWidth: 40,
});
run_spec(
{
dirname: __dirname,
snippets: [
// single
// https://developer.mozilla.org/en-US/docs/Glossary/Whitespace#In_HTML
...["\u0009", "\u000C", "\u0020"].map((textContent) => ({
code: `<div>${textContent}</div>`,
name: "content",
output: "<div> </div>",
})),
...["\u000A", "\u000D"].map((textContent) => ({
code: `<div>${textContent}</div>`,
name: "content",
output: "<div>\n</div>",
})),
// many
{
code: "<div>\u0009\u000A\u000C\u000D\u0020</div>",
output: "<div>\n\n</div>",
},
// single
...["\u0009", "\u000C", "\u0020"].map((textContent) => ({
code: `<img/>${textContent}<img/>`,
name: "between",
output: "<img /> <img />",
})),
...["\u000A", "\u000D"].map((textContent) => ({
code: `<img/>${textContent}<img/>`,
name: "between",
output: "<img />\n<img />",
})),
// many
{
code: "<img/>\u0009\u000A\u000C\u000D\u0020<img/>",
output: "<img />\n\n<img />",
},
// non-space
...[
"\u2005",
" \u2005 ",
" \u2005\u2005 ",
" \u2005 \u2005 ",
].map((textContent) => `<div>${textContent}</div>`),
...[
"\u2005",
" \u2005 ",
" \u2005\u2005 ",
" \u2005 \u2005 ",
].map((textContent) => `<img/>${textContent}<img/>`),
"<i /> \u2005 | \u2005 <i />",
"<p><span>X</span> \u2005 or \u2005 <span>Y</span></p><p>X \u2005 or \u2005 Y</p>",
{
name: "`U+2005` should format like `U+005F` not like `U+0020`",
code: outdent`
<!-- U+2005 -->
<div>before<span>\u2005</span>afterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafter</div>
<!-- U+005F -->
<div>before<span>\u005F</span>afterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafter</div>
<!-- U+0020 -->
<div>before<span>\u0020</span>afterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafterafter</div>
`,
},
],
},
["glimmer"],
{
htmlWhitespaceSensitivity: "strict",
printWidth: 40,
}
);