[Glimmer] improve text/mustache formatting (#6186)
parent
35d7e36356
commit
ff7bc1c008
|
@ -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
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
<p>Your username is @{{name}}</p>
|
||||||
|
<p>Hi {{firstName}} {{lastName}}</p>
|
|
@ -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}}
|
||||||
|
|
Loading…
Reference in New Issue