handlebars: Support escaping a mustache with a backslash (#8634)

* Add a failing test

* Support escaping a mustache with `\`

It is documented in handlebarjs documentation here:
https://handlebarsjs.com/guide/expressions.html#escaping-handlebars-expressions

* Preprocess TextNode to adjust the need of backslashes

Given that template:
```hbs
a non-escaped mustache: \\\{{helper}}
```

Glimmer parser will produce two body nodes:
- a TextNode where `node.chars === "a non-escaped mustache: \\\\"`
- a MustacheStatement for `{{helper}}`

In order to correctly print the input, we need to adjust the TextNode to
`node.chars === "a non-escaped mustache: \\\\\\"`, namely we have to add
a backslash that is removed by Glimmer parser. This is this commit
purpose.

* Add changelog note
This commit is contained in:
Cyrille David authored and GitHub committed 2020-06-24 23:55:02 +03:00
1 parent 05262d5e4e
commit bff049fd96
5 files changed
+118 -1

No files matched your search

@@ -0,0 +1,19 @@
#### Support escaping a mustache with a backslash ([#8634](https://github.com/prettier/prettier/pull/8634) by [@dcyriller](https://github.com/dcyriller)
<!-- prettier-ignore -->
```hbs
{{!-- Input --}}
\{{mustache}}
\\{{mustache}}
\\\{{mustache}}
{{!-- Prettier stable --}}
{{mustache}}
\{{mustache}}
\\{{mustache}}
{{!-- Prettier master --}}
\{{mustache}}
\\{{mustache}}
\\\{{mustache}}
```
+34
View File
@@ -0,0 +1,34 @@
"use strict";
const transform = require("@glimmer/syntax").traverse;
const PREPROCESS_PIPELINE = [addBackslash];
/* VISITORS */
/*
*/
function addBackslash(/* options*/) {
return {
TextNode(node) {
/* from the following template: `non-escaped mustache \\{{helper}}`
* glimmer parser will produce an AST missing a backslash
* so here we add it back
* */
if (node.chars.includes("\\")) {
node.chars = node.chars.replace(/\\/, "\\\\");
}
},
};
}
function preprocess(ast, options) {
for (const fn of PREPROCESS_PIPELINE) {
const visitor = fn(options);
transform(ast, visitor);
}
return ast;
}
module.exports = preprocess;
+12 -1
View File
@@ -1,6 +1,7 @@
"use strict";
const clean = require("./clean");
const preprocess = require("./preprocess");
const {
concat,
@@ -251,9 +252,18 @@ function print(path, options, print) {
}
}
let text = n.chars;
/* if `{{my-component}}` (or any text starting with a mustache)
* makes it to the TextNode,
* it means it was escaped,
* so let's print it escaped, ie.; `\{{my-component}}` */
if (text.startsWith("{{") && text.includes("}}")) {
text = "\\" + text;
}
return concat([
...generateHardlines(leadingLineBreaksCount, maxLineBreaksToPreserve),
n.chars.replace(/^\s+/g, leadingSpace).replace(/\s+$/, trailingSpace),
text.replace(/^\s+/g, leadingSpace).replace(/\s+$/, trailingSpace),
...generateHardlines(trailingLineBreaksCount, maxLineBreaksToPreserve),
]);
}
@@ -652,4 +662,5 @@ function locationToOffset(source, line, column) {
module.exports = {
print,
massageAstNode: clean,
preprocess,
};
@@ -389,3 +389,50 @@ printWidth: 80
</div>
================================================================================
`;
exports[`escaped.hbs - {"singleQuote":true} format 1`] = `
====================================options=====================================
parsers: ["glimmer"]
printWidth: 80
singleQuote: true
| printWidth
=====================================input======================================
an escaped mustache:
\\{{my-component}}
a non-escaped mustache:
\\\\{{my-component}}
another non-escaped mustache:
\\\\\\{{my-component}}
=====================================output=====================================
an escaped mustache:
\\{{my-component}}
a non-escaped mustache:
\\\\{{my-component}}
another non-escaped mustache:
\\\\\\{{my-component}}
================================================================================
`;
exports[`escaped.hbs format 1`] = `
====================================options=====================================
parsers: ["glimmer"]
printWidth: 80
| printWidth
=====================================input======================================
an escaped mustache:
\\{{my-component}}
a non-escaped mustache:
\\\\{{my-component}}
another non-escaped mustache:
\\\\\\{{my-component}}
=====================================output=====================================
an escaped mustache:
\\{{my-component}}
a non-escaped mustache:
\\\\{{my-component}}
another non-escaped mustache:
\\\\\\{{my-component}}
================================================================================
`;
@@ -0,0 +1,6 @@
an escaped mustache:
\{{my-component}}
a non-escaped mustache:
\\{{my-component}}
another non-escaped mustache:
\\\{{my-component}}