[Glimmer] improve text/mustache formatting (#6186)

master
Gavin Joyce 2019-06-14 16:11:47 +01:00 committed by Lucas Duailibe
parent 35d7e36356
commit ff7bc1c008
6 changed files with 187 additions and 40 deletions

View File

@ -74,5 +74,37 @@ const comp = (
); );
``` ```
### Handlebars: Avoid adding unwanted line breaks between text and mustaches ([#6186] by [@gavinjoyce])
Previously, Prettier added line breaks between text and mustaches which resulted in unwanted whitespace in rendered output.
<!-- prettier-ignore -->
```hbs
// Input
<p>Your username is @{{name}}</p>
<p>Hi {{firstName}} {{lastName}}</p>
// Output (Prettier stable)
<p>
Your username is @
{{name}}
</p>
<p>
Hi
{{firstName}}
{{lastName}}
</p>
// Output (Prettier master)
<p>
Your username is @{{name}}
</p>
<p>
Hi {{firstName}} {{lastName}}
</p>
```
[#6209]: https://github.com/prettier/prettier/pull/6209 [#6209]: https://github.com/prettier/prettier/pull/6209
[#6186]: https://github.com/prettier/prettier/pull/6186
[@duailibe]: https://github.com/duailibe [@duailibe]: https://github.com/duailibe
[@gavinjoyce]: https://github.com/gavinjoyce

View File

@ -1,32 +1,13 @@
"use strict"; "use strict";
const createError = require("../common/parser-create-error"); const createError = require("../common/parser-create-error");
function removeEmptyNodes(node) {
return (
node.type !== "TextNode" ||
(node.type === "TextNode" &&
node.chars.replace(/^\s+/, "").replace(/\s+$/, "") !== "")
);
}
function removeWhiteSpace() {
return {
visitor: {
Program(node) {
node.body = node.body.filter(removeEmptyNodes);
},
ElementNode(node) {
node.children = node.children.filter(removeEmptyNodes);
}
}
};
}
function parse(text) { function parse(text) {
try { try {
const glimmer = require("@glimmer/syntax").preprocess; const glimmer = require("@glimmer/syntax").preprocess;
return glimmer(text, { return glimmer(text, {
plugins: { plugins: {
ast: [removeWhiteSpace] ast: []
} }
}); });
/* istanbul ignore next */ /* istanbul ignore next */

View File

@ -32,6 +32,31 @@ const voidTags = [
// Formatter based on @glimmerjs/syntax's built-in test formatter: // Formatter based on @glimmerjs/syntax's built-in test formatter:
// https://github.com/glimmerjs/glimmer-vm/blob/master/packages/%40glimmer/syntax/lib/generation/print.ts // https://github.com/glimmerjs/glimmer-vm/blob/master/packages/%40glimmer/syntax/lib/generation/print.ts
function printChildren(path, options, print) {
return concat(
path.map((childPath, childIndex) => {
const isFirstNode = childIndex === 0;
const isLastNode =
childIndex == path.getParentNode(0).children.length - 1;
const isLastNodeInMultiNodeList = isLastNode && !isFirstNode;
if (isLastNodeInMultiNodeList) {
return concat([print(childPath, options, print)]);
} else if (
isFirstNode ||
isPreviousNodeOfSomeType(childPath, [
"ElementNode",
"CommentStatement",
"MustacheCommentStatement"
])
) {
return concat([softline, print(childPath, options, print)]);
}
return concat([print(childPath, options, print)]);
}, "children")
);
}
function print(path, options, print) { function print(path, options, print) {
const n = path.getValue(); const n = path.getValue();
@ -83,7 +108,7 @@ function print(path, options, print) {
), ),
group( group(
concat([ concat([
indent(join(softline, [""].concat(path.map(print, "children")))), indent(printChildren(path, options, print)),
ifBreak(hasChildren ? hardline : "", ""), ifBreak(hasChildren ? hardline : "", ""),
!isVoid ? concat(["</", n.tag, ">"]) : "" !isVoid ? concat(["</", n.tag, ">"]) : ""
]) ])
@ -122,18 +147,16 @@ function print(path, options, print) {
indent(concat([hardline, path.call(print, "program")])) indent(concat([hardline, path.call(print, "program")]))
]); ]);
} }
/**
* I want this boolean to be: if params are going to cause a break, const hasNonWhitespaceChildren = n.program.body.some(
* not that it has params. n => !isWhitespaceNode(n)
*/ );
const hasParams = n.params.length > 0 || n.hash.pairs.length > 0;
const hasChildren = n.program.body.length > 0;
return concat([ return concat([
printOpenBlock(path, print), printOpenBlock(path, print),
group( group(
concat([ concat([
indent(concat([softline, path.call(print, "program")])), indent(concat([softline, path.call(print, "program")])),
hasParams && hasChildren ? hardline : softline, hasNonWhitespaceChildren ? hardline : softline,
printCloseBlock(path, print) printCloseBlock(path, print)
]) ])
) )
@ -193,9 +216,22 @@ function print(path, options, print) {
return concat([n.key, "=", path.call(print, "value")]); return concat([n.key, "=", path.call(print, "value")]);
} }
case "TextNode": { case "TextNode": {
const isWhitespaceOnly = !/\S/.test(n.chars);
if (
isWhitespaceOnly &&
isPreviousNodeOfSomeType(path, ["MustacheStatement", "TextNode"])
) {
return " ";
}
let leadingSpace = ""; let leadingSpace = "";
let trailingSpace = ""; let trailingSpace = "";
if (isNextNodeOfType(path, "MustacheStatement")) {
trailingSpace = " ";
}
// preserve a space inside of an attribute node where whitespace present, when next to mustache statement. // preserve a space inside of an attribute node where whitespace present, when next to mustache statement.
const inAttrNode = path.stack.indexOf("attributes") >= 0; const inAttrNode = path.stack.indexOf("attributes") >= 0;
@ -350,6 +386,52 @@ function printCloseBlock(path, print) {
return concat(["{{/", path.call(print, "path"), "}}"]); return concat(["{{/", path.call(print, "path"), "}}"]);
} }
function isWhitespaceNode(node) {
return node.type === "TextNode" && !/\S/.test(node.chars);
}
function getPreviousNode(path) {
const node = path.getValue();
const parentNode = path.getParentNode(0);
const children = parentNode.children;
if (children) {
const nodeIndex = children.indexOf(node);
if (nodeIndex > 0) {
const previousNode = children[nodeIndex - 1];
return previousNode;
}
}
}
function getNextNode(path) {
const node = path.getValue();
const parentNode = path.getParentNode(0);
const children = parentNode.children;
if (children) {
const nodeIndex = children.indexOf(node);
if (nodeIndex < children.length) {
const nextNode = children[nodeIndex + 1];
return nextNode;
}
}
}
function isPreviousNodeOfSomeType(path, types) {
const previousNode = getPreviousNode(path);
if (previousNode) {
return types.some(type => previousNode.type === type);
}
return false;
}
function isNextNodeOfType(path, type) {
const nextNode = getNextNode(path);
return nextNode && nextNode.type === type;
}
function clean(ast, newObj) { function clean(ast, newObj) {
delete newObj.loc; delete newObj.loc;

View File

@ -119,7 +119,11 @@ printWidth: 80
}} }}
Hello Hello
{{/block}} {{/block}}
{{#block}}{{#block}}hello{{/block}}{{/block}} {{#block}}
{{#block}}
hello
{{/block}}
{{/block}}
{{#block}} {{#block}}
{{#block param}} {{#block param}}
hello hello
@ -130,13 +134,14 @@ printWidth: 80
hello hello
{{/block}} {{/block}}
{{/block}} {{/block}}
{{#block}}hello{{/block}} {{#block}}
hello
{{/block}}
<MyComponent as |firstName|> <MyComponent as |firstName|>
{{firstName}} {{firstName}}
</MyComponent> </MyComponent>
<MyComponent as |firstName lastName|> <MyComponent as |firstName lastName|>
{{firstName}} {{firstName}} {{lastName}}
{{lastName}}
</MyComponent> </MyComponent>
================================================================================ ================================================================================
`; `;
@ -261,7 +266,11 @@ singleQuote: true
}} }}
Hello Hello
{{/block}} {{/block}}
{{#block}}{{#block}}hello{{/block}}{{/block}} {{#block}}
{{#block}}
hello
{{/block}}
{{/block}}
{{#block}} {{#block}}
{{#block param}} {{#block param}}
hello hello
@ -272,13 +281,14 @@ singleQuote: true
hello hello
{{/block}} {{/block}}
{{/block}} {{/block}}
{{#block}}hello{{/block}} {{#block}}
hello
{{/block}}
<MyComponent as |firstName|> <MyComponent as |firstName|>
{{firstName}} {{firstName}}
</MyComponent> </MyComponent>
<MyComponent as |firstName lastName|> <MyComponent as |firstName lastName|>
{{firstName}} {{firstName}} {{lastName}}
{{lastName}}
</MyComponent> </MyComponent>
================================================================================ ================================================================================
`; `;
@ -525,6 +535,43 @@ singleQuote: true
================================================================================ ================================================================================
`; `;
exports[`curly.hbs 1`] = `
====================================options=====================================
parsers: ["glimmer"]
printWidth: 80
| printWidth
=====================================input======================================
<p>Your username is @{{name}}</p>
<p>Hi {{firstName}} {{lastName}}</p>
=====================================output=====================================
<p>
Your username is @{{name}}
</p>
<p>
Hi {{firstName}} {{lastName}}
</p>
================================================================================
`;
exports[`curly.hbs 2`] = `
====================================options=====================================
parsers: ["glimmer"]
printWidth: 80
singleQuote: true
| printWidth
=====================================input======================================
<p>Your username is @{{name}}</p>
<p>Hi {{firstName}} {{lastName}}</p>
=====================================output=====================================
<p>
Your username is @{{name}}
</p>
<p>
Hi {{firstName}} {{lastName}}
</p>
================================================================================
`;
exports[`element-modifier-statement.hbs 1`] = ` exports[`element-modifier-statement.hbs 1`] = `
====================================options===================================== ====================================options=====================================
parsers: ["glimmer"] parsers: ["glimmer"]
@ -735,7 +782,9 @@ printWidth: 80
A long enough string to trigger a line break that would prevent wrapping more and more. A long enough string to trigger a line break that would prevent wrapping more and more.
</div> </div>
<div> <div>
{{#block}}{{hello}}{{/block}} {{#block}}
{{hello}}
{{/block}}
</div> </div>
<div> <div>
{{hello}} {{hello}}
@ -809,7 +858,9 @@ singleQuote: true
A long enough string to trigger a line break that would prevent wrapping more and more. A long enough string to trigger a line break that would prevent wrapping more and more.
</div> </div>
<div> <div>
{{#block}}{{hello}}{{/block}} {{#block}}
{{hello}}
{{/block}}
</div> </div>
<div> <div>
{{hello}} {{hello}}

2
tests/glimmer/curly.hbs Normal file
View File

@ -0,0 +1,2 @@
<p>Your username is @{{name}}</p>
<p>Hi {{firstName}} {{lastName}}</p>

View File

@ -165,8 +165,7 @@ printWidth: 80
{{title}} {{title}}
</h1> </h1>
<h2> <h2>
By By {{author.name}}
{{author.name}}
</h2> </h2>
<div class="body"> <div class="body">
{{body}} {{body}}