diff --git a/changelog_unreleased/handlebars/pr-8634.md b/changelog_unreleased/handlebars/pr-8634.md new file mode 100644 index 000000000..85b7821f8 --- /dev/null +++ b/changelog_unreleased/handlebars/pr-8634.md @@ -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) + + +```hbs +{{!-- Input --}} +\{{mustache}} +\\{{mustache}} +\\\{{mustache}} + +{{!-- Prettier stable --}} +{{mustache}} +\{{mustache}} +\\{{mustache}} + +{{!-- Prettier master --}} +\{{mustache}} +\\{{mustache}} +\\\{{mustache}} +``` diff --git a/src/language-handlebars/preprocess.js b/src/language-handlebars/preprocess.js new file mode 100644 index 000000000..fd376682d --- /dev/null +++ b/src/language-handlebars/preprocess.js @@ -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; diff --git a/src/language-handlebars/printer-glimmer.js b/src/language-handlebars/printer-glimmer.js index 69440fdc2..1476f82ce 100644 --- a/src/language-handlebars/printer-glimmer.js +++ b/src/language-handlebars/printer-glimmer.js @@ -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, }; diff --git a/tests/handlebars/mustache-statement/__snapshots__/jsfmt.spec.js.snap b/tests/handlebars/mustache-statement/__snapshots__/jsfmt.spec.js.snap index 370a22663..e41cc264a 100644 --- a/tests/handlebars/mustache-statement/__snapshots__/jsfmt.spec.js.snap +++ b/tests/handlebars/mustache-statement/__snapshots__/jsfmt.spec.js.snap @@ -389,3 +389,50 @@ printWidth: 80 ================================================================================ `; + +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}} +================================================================================ +`; diff --git a/tests/handlebars/mustache-statement/escaped.hbs b/tests/handlebars/mustache-statement/escaped.hbs new file mode 100644 index 000000000..0b5c550ca --- /dev/null +++ b/tests/handlebars/mustache-statement/escaped.hbs @@ -0,0 +1,6 @@ +an escaped mustache: +\{{my-component}} +a non-escaped mustache: +\\{{my-component}} +another non-escaped mustache: +\\\{{my-component}}