Repository: swc-project/swc (rust)

Issue:
SWC incorrectly handles double backslashes in template literals
### Describe the bug

I encountered an issue with SWC when compiling code that contains strings with double backslashes. It seems this problem only occurs when `"target": "es2018"`  is set and compress is enabled. This problem will cause components as `react-katex` or other web latex library output weird results.

It seems this problem dose not occur in v1.3.1, and occurs in v.1.5.3 and above. 

### Input code

```typescript
export function _jsx(a, b) {
    return b;
}

export const a = _jsx("math", `P \\vdash q`)
export const b = _jsx("math", "P \\vdash q")
```


### Config

```json
{
 // Enable minification
 "minify": true,
 // Optional, configure minification options
 "jsc": {
 "minify": {
 "compress": {
 "unused": true
 },
 "mangle": true
 },
 "target": "es2018"
 }
}
```


### Playground link (or link to the minimal reproduction)

https://play.swc.rs/?version=1.5.3&code=H4sIAAAAAAAAA13K0QpAMBQG4PvzFH%2B7svIG8g4eQLHNhLKxnUnJu1Nu8F1%2Fdl98YPTJGR69QzPFPVM5tMRBuAXLKTjogk4i%2B2zjXWQolE8Xs%2BJB5Ggr1PXWqThgbeU3638Wrywk0QXDQon6iQAAAA%3D%3D&config=H4sIAAAAAAAAA41VwW7bMAy99ysCn3dodyiKfcBu%2BwZBsShHmSwaIpUmKPLvo2U7TRva2CWI%2BfRIkXykPp52u%2BZIbfNr9yF%2F5WOwmSDfvsVCl8T2LJYG2t5Sm8PAzY8FPdIIeRsJquk6IQ3b3AFXFv18fnmbGU1EJFgYs60PKfjLfcwW%2ByED0Z1NrOKy9JCYvvJnLOP7CHAu9%2FY9YgSbNhBjyYTE0EHWHLcYox0IzMlmxct4U5sDoRZiBAuDM0PGQcWTCxwwScxH1IF1pkUHChQytBxOoNEkltASSXpKPhV2sC9dV%2Fv8jQ0nG4tlJSaca0vktorXAwZi40vSSjiBKzWYwLm435nBmwxccnrkHTGklZ78BZAKREuUbA%2Ba33rCi57W2H6TGZIXyfJFwUXfWpYJOimqCcErlR0rA5mD1s0MrrQwVrbVrjPDK%2BWj4MCA96IVxTW9B24PWlC%2BDIBeAaS%2F1muqmgBzm8IVfByIDfi3ZMm6wOYTveXDOkqXfo9xI0APfEC3cUBawbgOZ9kS52EdL8mBSAOceqRQBR6XgAwAo4l1Xz5oQ8ZDPJou4v5zTcwHrrc93NvU1Xm%2F25eMQ4QTxDUZ%2F8eIbKJyt9Oo6mWyH6YX3jRRWhm88PL85dmQVJ6W35pU06MrdwlVTU7PyWvzeWh5OZaqNYH%2BLMTJdv0H%2FO0MreQGAAA%3D

### SWC Info output

``` 
Operating System:
      Platform: linux
      Arch: x64
      Machine Type: x86_64
      Version: #29~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Apr  4 14:39:20 UTC 2
      CPU: (12 cores)
          Models: AMD Ryzen 5 3600 6-Core Processor

  Binaries:
      Node: 18.18.0
      npm: 9.8.1
      Yarn: 1.22.19
      pnpm: N/A

  Relevant Packages:
      @swc/core: 1.5.3
      @swc/helpers: N/A
      @swc/types: 0.1.6
      

  SWC Config:
      output: N/A
      .swcrc path: N/A

  Next.js info:
      output: N/A
```

### Expected behavior

The output should be

```js
export function _jsx(t,s){return s}export const a=_jsx("math","P \\vdash q");export const b=_jsx("math","P \\vdash q");
```

### Actual behavior

The output is

```js
export function _jsx(t,s){return s}export const a=_jsx("math","P \\\vdash q");export const b=_jsx("math","P \\vdash q");
```

Note the `\\\vdash` here, this is incorrect.

### Version

1.5.3

### Additional context

_No response_

Pull request:
fix(es/minifier): Fix a bug about `Tpl` => `Str`
**Description:**

I added the method to `swc_ecma_ast` to reuse it from plugins.

**Related issue:**

 - Closes #8931

Patch:
diff --git a/crates/swc_ecma_ast/src/lit.rs b/crates/swc_ecma_ast/src/lit.rs
index a682f8d9d6..9c9249504c 100644
--- a/crates/swc_ecma_ast/src/lit.rs
+++ b/crates/swc_ecma_ast/src/lit.rs
@@ -196,6 +196,54 @@ impl Str {
     pub fn is_empty(&self) -> bool {
         self.value.is_empty()
     }
+
+    pub fn from_tpl_raw(tpl_raw: &str) -> Atom {
+        let mut buf = String::with_capacity(tpl_raw.len());
+
+        let mut iter = tpl_raw.chars();
+
+        while let Some(c) = iter.next() {
+            match c {
+                '\\' => {
+                    if let Some(next) = iter.next() {
+                        match next {
+                            '`' | '$' | '\\' => {
+                                buf.push(next);
+                            }
+                            'b' => {
+                                buf.push('\u{0008}');
+                            }
+                            'f' => {
+                                buf.push('\u{000C}');
+                            }
+                            'n' => {
+                                buf.push('\n');
+                            }
+                            'r' => {
+                                buf.push('\r');
+                            }
+                            't' => {
+                                buf.push('\t');
+                            }
+                            'v' => {
+                                buf.push('\u{000B}');
+                            }
+                            _ => {
+                                buf.push('\\');
+                                buf.push(next);
+                            }
+                        }
+                    }
+                }
+
+                c => {
+                    buf.push(c);
+                }
+            }
+        }
+
+        buf.into()
+    }
 }
 
 impl EqIgnoreSpan for Str {
diff --git a/crates/swc_ecma_minifier/src/compress/pure/strings.rs b/crates/swc_ecma_minifier/src/compress/pure/strings.rs
index 8e177d66fc..607de8011d 100644
--- a/crates/swc_ecma_minifier/src/compress/pure/strings.rs
+++ b/crates/swc_ecma_minifier/src/compress/pure/strings.rs
@@ -220,23 +220,14 @@ impl Pure<'_> {
                     && !c.contains("\\x")
                     && !c.contains("\\u")
                 {
-                    let value = c
-                        .replace("\\`", "`")
-                        .replace("\\$", "$")
-                        .replace("\\b", "\u{0008}")
-                        .replace("\\f", "\u{000C}")
-                        .replace("\\n", "\n")
-                        .replace("\\r", "\r")
-                        .replace("\\t", "\t")
-                        .replace("\\v", "\u{000B}")
-                        .replace("\\\\", "\\");
+                    let value = Str::from_tpl_raw(c);
 
                     report_change!("converting a template literal to a string literal");
 
                     *e = Expr::Lit(Lit::Str(Str {
                         span: t.span,
                         raw: None,
-                        value: value.into(),
+                        value,
                     }));
                 }
             }


Test patch:
diff --git a/crates/swc/tests/fixture/issues-8xxx/8931/input/.swcrc b/crates/swc/tests/fixture/issues-8xxx/8931/input/.swcrc
new file mode 100644
index 0000000000..69928e405e
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8931/input/.swcrc
@@ -0,0 +1,14 @@
+{
+    // Enable minification
+    "minify": true,
+    // Optional, configure minification options
+    "jsc": {
+        "minify": {
+            "compress": {
+                "unused": true
+            },
+            "mangle": true
+        },
+        "target": "es2018"
+    }
+}
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-8xxx/8931/input/1.js b/crates/swc/tests/fixture/issues-8xxx/8931/input/1.js
new file mode 100644
index 0000000000..0052ec5a08
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8931/input/1.js
@@ -0,0 +1,6 @@
+export function _jsx(a, b) {
+    return b;
+}
+
+export const a = _jsx("math", `P \\vdash q`)
+export const b = _jsx("math", "P \\vdash q")
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-8xxx/8931/input/2.js b/crates/swc/tests/fixture/issues-8xxx/8931/input/2.js
new file mode 100644
index 0000000000..05dd09faa3
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8931/input/2.js
@@ -0,0 +1,6 @@
+export function _jsx(a, b) {
+    return b;
+}
+
+export const a = _jsx("math", `\` \\vdash q`)
+export const b = _jsx("math", "P \\vdash q")
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-8xxx/8931/output/1.js b/crates/swc/tests/fixture/issues-8xxx/8931/output/1.js
new file mode 100644
index 0000000000..7954eba6a0
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8931/output/1.js
@@ -0,0 +1,1 @@
+export function _jsx(t,s){return s}export const a=_jsx("math","P \\vdash q");export const b=_jsx("math","P \\vdash q");
diff --git a/crates/swc/tests/fixture/issues-8xxx/8931/output/2.js b/crates/swc/tests/fixture/issues-8xxx/8931/output/2.js
new file mode 100644
index 0000000000..2d5a91a848
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8931/output/2.js
@@ -0,0 +1,1 @@
+export function _jsx(t,s){return s}export const a=_jsx("math","` \\vdash q");export const b=_jsx("math","P \\vdash q");