Repository: swc-project/swc (Rust)

Issue:
JSX attributes with HTML entities don't always escape correctly.
<!--
If you are using swc at work, please considering adding your company to https://swc.rs/users/
If then, your issue will be fixed more quickly.
-->

**Describe the bug**

When compiling JSX with an attribute containing an HTML entity that would be an escapable character in JavaScript, the character gets double escaped.

**Input code**

```javascript
<Header text="Hello &quot;World&quot;" />
```

**Config**

```json
{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": true
    }
  }
}
```

**Expected output**

```javascript
/*#__PURE__*/ React.createElement(Header, {
    text: "Hello \"World\""
});
```

**Actual output**

```javascript
/*#__PURE__*/ React.createElement(Header, {
    text: "Hello \\\"World\\\""
});
```

(note the double escaping of the backslashes/quotes)

**Version**

In our case we compile swc ourselves with the dependency versions below, but I did confirm this also happens in the official REPL on the docs site with the config above.

```
swc_atoms v0.2.7
swc_common v0.11.9 ["sourcemap"]
swc_ecmascript v0.52.1 ["compat", "codegen", "dep_graph", "parser", "transforms", "visit", "react", "typescript"]
```



Discussion:
This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.



Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 204c2f345297..3e49061f704d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -341,9 +341,9 @@ checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536"
 
 [[package]]
 name = "core-foundation"
-version = "0.9.1"
+version = "0.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62"
+checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3"
 dependencies = [
  "core-foundation-sys",
  "libc",
@@ -351,9 +351,9 @@ dependencies = [
 
 [[package]]
 name = "core-foundation-sys"
-version = "0.8.2"
+version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
+checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
 
 [[package]]
 name = "core_extensions"
@@ -1633,9 +1633,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.29"
+version = "1.0.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d"
+checksum = "edc3358ebc67bc8b7fa0c007f945b0b18226f78437d61bec735a9eb96b61ee70"
 dependencies = [
  "unicode-xid",
 ]
@@ -2184,9 +2184,9 @@ dependencies = [
 
 [[package]]
 name = "sharded-slab"
-version = "0.1.3"
+version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "740223c51853f3145fe7c90360d2d4232f2b62e3449489c207eccde818979982"
+checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
 dependencies = [
  "lazy_static",
 ]
@@ -2924,7 +2924,7 @@ dependencies = [
 
 [[package]]
 name = "swc_ecma_transforms_react"
-version = "0.48.1"
+version = "0.48.2"
 dependencies = [
  "ahash",
  "base64 0.13.0",
diff --git a/ecmascript/transforms/react/Cargo.toml b/ecmascript/transforms/react/Cargo.toml
index 2e285dde3509..a0a63c396b5c 100644
--- a/ecmascript/transforms/react/Cargo.toml
+++ b/ecmascript/transforms/react/Cargo.toml
@@ -7,7 +7,7 @@ include = ["Cargo.toml", "src/**/*.rs"]
 license = "Apache-2.0/MIT"
 name = "swc_ecma_transforms_react"
 repository = "https://github.com/swc-project/swc.git"
-version = "0.48.1"
+version = "0.48.2"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
diff --git a/ecmascript/transforms/react/src/jsx/mod.rs b/ecmascript/transforms/react/src/jsx/mod.rs
index df5f79d5d258..c89e1bc96728 100644
--- a/ecmascript/transforms/react/src/jsx/mod.rs
+++ b/ecmascript/transforms/react/src/jsx/mod.rs
@@ -1190,7 +1190,7 @@ fn transform_jsx_attr_str(v: &str) -> String {
             '\\' => buf.push_str("\\\\"),
 
             '\'' if single_quote => buf.push_str("\\'"),
-            '"' if !single_quote => buf.push_str("\\\""),
+            '"' if !single_quote => buf.push_str("\""),
 
             '\x01'..='\x0f' | '\x10'..='\x1f' => {
                 buf.push(c);


Test patch:
diff --git a/ecmascript/transforms/react/src/jsx/tests.rs b/ecmascript/transforms/react/src/jsx/tests.rs
index f6ad825cd9d7..e55168e5e22c 100644
--- a/ecmascript/transforms/react/src/jsx/tests.rs
+++ b/ecmascript/transforms/react/src/jsx/tests.rs
@@ -1110,6 +1110,20 @@ React.createElement(Component, _extends({
 }));"#
 );
 
+test!(
+    ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsConfig {
+        jsx: true,
+        ..Default::default()
+    }),
+    |t| tr(t, Default::default(), Mark::fresh(Mark::root())),
+    react_attribute_html_entity_quote,
+    r#"<Component text="Hello &quot;World&quot;" />"#,
+    r#"
+React.createElement(Component, {
+  text: "Hello \"World\""
+});"#
+);
+
 test!(
     ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsConfig {
         jsx: true,