Repository: prettier/prettier (JavaScript)

Issue:
Prettier should not re-escape a backslash in a string literal
## Description
Formatting handlebars prettier is escaping a backslash with another backslash.

Currently this PR only adds a breaking test. 
 
For this example
```hbs
<DateFormat @pathFormat="dateFormat:YYYY-MM-DD hh\:mm\:ss" />
```

Current output
```hbs
<DateFormat @pathFormat="dateFormat:YYYY-MM-DD hh\\:mm\\:ss" />
```

Expected output
```hbs
<DateFormat @pathFormat="dateFormat:YYYY-MM-DD hh\:mm\:ss" />
```

## Checklist

<!-- Please ensure you’ve done all of these things (if applicable). -->
<!-- You can replace the `[ ]` with `[x]` to mark each task as done. -->

- [x] I’ve added tests to confirm my change works.
- [ ] (If changing the API or CLI) I’ve documented the changes I’ve made (in the `docs/` directory).
- [ ] (If the change is user-facing) I’ve added my changes to `changelog_unreleased/*/XXXX.md` file following `changelog_unreleased/TEMPLATE.md`.
- [x] I’ve read the [contributing guidelines](https://github.com/prettier/prettier/blob/main/CONTRIBUTING.md).

<!-- Please DO NOT remove the playground link -->

**✨[Try the playground for this PR](https://prettier.io/playground-redirect)✨**



Hints:
Can you open an issue instead? Or do you want fix it?
I have tried to fix it. But I was in able to fix it. 

@fisker Is it worth adding a failing test and a issue. 
Have you try unescape [here](https://github.com/prettier/prettier/blob/2380e113624300bfee65cc9ea1cffae44e8208be/src/language-handlebars/printer-glimmer.js#L194)?
Or maybe it's bug in the glimmer parser?
This function seems to be responsible for duplicating the first backslash (`\`): https://github.com/prettier/prettier/blob/c9c6d3364f8cf5140c3c16b44b92943464be7873/src/language-handlebars/parser-glimmer.js#L7-L20

`node.chars` is as expected before but have the duplicated slash afterwards. Not a big surprise if looking at that code.

It is run as a plugin for Glimmer parser. https://github.com/prettier/prettier/blob/c9c6d3364f8cf5140c3c16b44b92943464be7873/src/language-handlebars/parser-glimmer.js#L43-L46

If I remove that plugin for the Glimmer parser the following tests start to fail:

```
 FAIL  tests/format/handlebars/mustache-statement/jsfmt.spec.js
  ● escaped.hbs › format

    expect(received).toMatchSnapshot()

    Snapshot name: `escaped.hbs format 1`

    - Snapshot  - 2
    + Received  + 2

    @@ -11,8 +11,8 @@
      \\\{{my-component}}

      =====================================output=====================================
      an escaped mustache:
      \{{my-component}} a non-escaped mustache:
    - \\{{my-component}}
    - another non-escaped mustache: \\\{{my-component}}
    + \{{my-component}}
    + another non-escaped mustache: \\{{my-component}}
      ================================================================================

      283 |         CURSOR_PLACEHOLDER,
      284 |       })
    > 285 |     ).toMatchSnapshot();
          |       ^
      286 |   });
      287 |
      288 |   if (!FULL_TEST) {

      at Object.<anonymous> (tests/config/format-test.js:285:7)

  ● escaped.hbs - {"singleQuote":true} › format

    expect(received).toMatchSnapshot()

    Snapshot name: `escaped.hbs - {"singleQuote":true} format 1`

    - Snapshot  - 2
    + Received  + 2

    @@ -12,8 +12,8 @@
      \\\{{my-component}}

      =====================================output=====================================
      an escaped mustache:
      \{{my-component}} a non-escaped mustache:
    - \\{{my-component}}
    - another non-escaped mustache: \\\{{my-component}}
    + \{{my-component}}
    + another non-escaped mustache: \\{{my-component}}
      ================================================================================

      283 |         CURSOR_PLACEHOLDER,
      284 |       })
    > 285 |     ).toMatchSnapshot();
          |       ^
      286 |   });
      287 |
      288 |   if (!FULL_TEST) {

      at Object.<anonymous> (tests/config/format-test.js:285:7)

 › 2 snapshots failed.
```

So it seems to be still needed to work-a-round another bug in the Glimmer parser.

To be honest until today I have never seen that escape syntax. Not sure if it is even officially supported by Glimmer at all.

Patch:
diff --git a/src/language-handlebars/parser-glimmer.js b/src/language-handlebars/parser-glimmer.js
--- a/src/language-handlebars/parser-glimmer.js
+++ b/src/language-handlebars/parser-glimmer.js
@@ -12,8 +12,21 @@ function addBackslash(/* options*/) {
   return {
     name: "addBackslash",
     visitor: {
-      TextNode(node) {
-        node.chars = node.chars.replace(/\\/, "\\\\");
+      All(node) {
+        const childrenOrBody = node.children || node.body;
+        if (childrenOrBody) {
+          for (let i = 0; i < childrenOrBody.length - 1; i++) {
+            if (
+              childrenOrBody[i].type === "TextNode" &&
+              childrenOrBody[i + 1].type === "MustacheStatement"
+            ) {
+              childrenOrBody[i].chars = childrenOrBody[i].chars.replace(
+                /\\$/,
+                "\\\\"
+              );
+            }
+          }
+        }
       },
     },
   };


Test patch:
diff --git a/tests/format/handlebars/escape/__snapshots__/jsfmt.spec.js.snap b/tests/format/handlebars/escape/__snapshots__/jsfmt.spec.js.snap
--- a/tests/format/handlebars/escape/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/handlebars/escape/__snapshots__/jsfmt.spec.js.snap
@@ -1,5 +1,37 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`backslashes.hbs format 1`] = `
+====================================options=====================================
+parsers: ["glimmer"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+<p>\\</p>
+<p>\\\\</p>
+<p>\\\\\\</p>
+<p>\\\\\\ \\\\{{non-escaped-moustache}}</p>
+
+=====================================output=====================================
+<p>\\</p>
+<p>\\\\</p>
+<p>\\\\\\</p>
+<p>\\\\\\ \\\\{{non-escaped-moustache}}</p>
+================================================================================
+`;
+
+exports[`backslashes-in-attributes.hbs format 1`] = `
+====================================options=====================================
+parsers: ["glimmer"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+<p data-attr="backslash \\ in an attribute"></p>
+
+=====================================output=====================================
+<p data-attr="backslash \\ in an attribute"></p>
+================================================================================
+`;
+
 exports[`html-entities.hbs format 1`] = `
 ====================================options=====================================
 parsers: ["glimmer"]
diff --git a/tests/format/handlebars/escape/backslashes-in-attributes.hbs b/tests/format/handlebars/escape/backslashes-in-attributes.hbs
new file mode 100644
--- /dev/null
+++ b/tests/format/handlebars/escape/backslashes-in-attributes.hbs
@@ -0,0 +1 @@
+<p data-attr="backslash \ in an attribute"></p>
diff --git a/tests/format/handlebars/escape/backslashes.hbs b/tests/format/handlebars/escape/backslashes.hbs
new file mode 100644
--- /dev/null
+++ b/tests/format/handlebars/escape/backslashes.hbs
@@ -0,0 +1,4 @@
+<p>\</p>
+<p>\\</p>
+<p>\\\</p>
+<p>\\\ \\{{non-escaped-moustache}}</p>