Repository: swc-project/swc (rust)

Issue:
1.3.98 swc minify breaks property name with non-ascii char when asciiOnly is on
### Describe the bug

when asciiOnly is on, swc minify emits invalid property name which is original unicode.

works fine with 1.3.97 but 1.3.98

### Input code

```typescript
const a = {aób: 'ó'}

console.log(a)
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": false
    },
    "target": "es5",
    "loose": false,
    "minify": {
      "compress": true,
      "format": {
        "asciiOnly": true
      }
    }
  },
  "module": {
    "type": "es6"
  },
  "minify": false,
  "isModule": true
}
```


### Playground link (or link to the minimal reproduction)

https://play.swc.rs/?version=1.3.98&code=H4sIAAAAAAAAA0vOzysuUUhUsFWoTjy8OclKQf3wZvVaLq5koHh%2BTqpeTn66RqImAJ8BdbomAAAA&config=H4sIAAAAAAAAA02PMQ7DIAxF95wi8ty1HXqHqmdA1KmIICDbkYqi3L0OgSSLZb%2F%2FjT9L1%2FcwsoVnv2irQzLESMeshPMk5qcE0AbDllwSuDV15E0ajGcsaN0VEENflLLF92oHHyNjs1cW3OSGfD1oY0iEzMqEZjxODZGCkYtTmeZx7j35XM1VWbtWSx4I8TN7PH8pOeGe7QGnqSU54oHjV9ssr69%2FIxZbWDEBAAA%3D

### SWC Info output

_No response_

### Expected behavior

```js
console.log({
    "a\xf3b": "\xf3"
});
```


### Actual behavior

```js
console.log({
    a\xf3b: "\xf3"
});
```


### Version

1.3.98

### Additional context

_No response_

Pull request description:
fix(es/codegen): Fix codegen of a property key in ascii-only mode 
**Description:**


**Related issue:**

 - Closes #8491

Patch:
diff --git a/crates/swc_ecma_codegen/src/lib.rs b/crates/swc_ecma_codegen/src/lib.rs
index 3d00612f61..7dfa870b06 100644
--- a/crates/swc_ecma_codegen/src/lib.rs
+++ b/crates/swc_ecma_codegen/src/lib.rs
@@ -1721,7 +1721,33 @@ where
     fn emit_prop_name(&mut self, node: &PropName) -> Result {
         match node {
             PropName::Ident(ident) => {
-                emit!(ident)
+                // TODO: Use write_symbol when ident is a symbol.
+                self.emit_leading_comments_of_span(ident.span, false)?;
+
+                // Source map
+                self.wr.commit_pending_semi()?;
+
+                srcmap!(ident, true);
+
+                if self.cfg.ascii_only {
+                    if self.wr.can_ignore_invalid_unicodes() {
+                        self.wr.write_symbol(
+                            DUMMY_SP,
+                            &get_ascii_only_ident(&ident.sym, true, self.cfg.target),
+                        )?;
+                    } else {
+                        self.wr.write_symbol(
+                            DUMMY_SP,
+                            &get_ascii_only_ident(
+                                &handle_invalid_unicodes(&ident.sym),
+                                true,
+                                self.cfg.target,
+                            ),
+                        )?;
+                    }
+                } else {
+                    emit!(ident);
+                }
             }
             PropName::Str(ref n) => emit!(n),
             PropName::Num(ref n) => emit!(n),
@@ -2259,12 +2285,18 @@ where
 
         if self.cfg.ascii_only {
             if self.wr.can_ignore_invalid_unicodes() {
-                self.wr
-                    .write_symbol(DUMMY_SP, &get_ascii_only_ident(&ident.sym, self.cfg.target))?;
+                self.wr.write_symbol(
+                    DUMMY_SP,
+                    &get_ascii_only_ident(&ident.sym, false, self.cfg.target),
+                )?;
             } else {
                 self.wr.write_symbol(
                     DUMMY_SP,
-                    &get_ascii_only_ident(&handle_invalid_unicodes(&ident.sym), self.cfg.target),
+                    &get_ascii_only_ident(
+                        &handle_invalid_unicodes(&ident.sym),
+                        false,
+                        self.cfg.target,
+                    ),
                 )?;
             }
         } else if self.wr.can_ignore_invalid_unicodes() {
@@ -3751,7 +3783,7 @@ fn get_template_element_from_raw(s: &str, ascii_only: bool) -> String {
     buf
 }
 
-fn get_ascii_only_ident(sym: &str, target: EsVersion) -> Cow<str> {
+fn get_ascii_only_ident(sym: &str, may_need_quote: bool, target: EsVersion) -> Cow<str> {
     if sym.chars().all(|c| c.is_ascii()) {
         return Cow::Borrowed(sym);
     }
@@ -3759,6 +3791,7 @@ fn get_ascii_only_ident(sym: &str, target: EsVersion) -> Cow<str> {
     let mut first = true;
     let mut buf = String::with_capacity(sym.len() + 8);
     let mut iter = sym.chars().peekable();
+    let mut need_quote = false;
 
     while let Some(c) = iter.next() {
         match c {
@@ -3859,7 +3892,11 @@ fn get_ascii_only_ident(sym: &str, target: EsVersion) -> Cow<str> {
             '\x20'..='\x7e' => {
                 buf.push(c);
             }
-            '\u{7f}'..='\u{ff}' if !first => {
+            '\u{7f}'..='\u{ff}' => {
+                if may_need_quote {
+                    need_quote = true;
+                }
+
                 let _ = write!(buf, "\\x{:x}", c as u8);
             }
             '\u{2028}' => {
@@ -3897,7 +3934,11 @@ fn get_ascii_only_ident(sym: &str, target: EsVersion) -> Cow<str> {
         first = false;
     }
 
-    Cow::Owned(buf)
+    if need_quote {
+        Cow::Owned(format!("\"{}\"", buf))
+    } else {
+        Cow::Owned(buf)
+    }
 }
 
 fn get_quoted_utf16(v: &str, ascii_only: bool, target: EsVersion) -> String {


Test patch:
diff --git a/crates/swc/tests/fixture/issues-7xxx/7805/output/1.js b/crates/swc/tests/fixture/issues-7xxx/7805/output/1.js
index 0ca9c927f0..69e95d3382 100644
--- a/crates/swc/tests/fixture/issues-7xxx/7805/output/1.js
+++ b/crates/swc/tests/fixture/issues-7xxx/7805/output/1.js
@@ -21,8 +21,8 @@ var SUPPORTED_LOCALE = {
             I: "i\u0307",
             J: "j\u0307",
             \u012E: "\u012F\u0307",
-            \u00CC: "i\u0307\u0300",
-            \u00CD: "i\u0307\u0301",
+            "\xcc": "i\u0307\u0300",
+            "\xcd": "i\u0307\u0301",
             \u0128: "i\u0307\u0303"
         }
     }
diff --git a/crates/swc/tests/fixture/issues-8xxx/8260/output/1.js b/crates/swc/tests/fixture/issues-8xxx/8260/output/1.js
index eb9aa7df1d..ec1e52900f 100644
--- a/crates/swc/tests/fixture/issues-8xxx/8260/output/1.js
+++ b/crates/swc/tests/fixture/issues-8xxx/8260/output/1.js
@@ -1,2 +1,2 @@
-function p(\u00B5, \u03C3) {}
+function p(\xb5, \u03C3) {}
 console.log(p);
diff --git a/crates/swc/tests/fixture/issues-8xxx/8491/input/.swcrc b/crates/swc/tests/fixture/issues-8xxx/8491/input/.swcrc
new file mode 100644
index 0000000000..98f55cc78c
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8491/input/.swcrc
@@ -0,0 +1,21 @@
+{
+    "jsc": {
+        "parser": {
+            "syntax": "ecmascript",
+            "jsx": false
+        },
+        "target": "es5",
+        "loose": false,
+        "minify": {
+            "compress": true,
+            "format": {
+                "asciiOnly": true
+            }
+        }
+    },
+    "module": {
+        "type": "es6"
+    },
+    "minify": false,
+    "isModule": true
+}
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-8xxx/8491/input/1.js b/crates/swc/tests/fixture/issues-8xxx/8491/input/1.js
new file mode 100644
index 0000000000..7d39d5e7d6
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8491/input/1.js
@@ -0,0 +1,3 @@
+const a = { aób: 'ó' }
+
+console.log(a)
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-8xxx/8491/output/1.js b/crates/swc/tests/fixture/issues-8xxx/8491/output/1.js
new file mode 100644
index 0000000000..17c8ecf6f4
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8491/output/1.js
@@ -0,0 +1,3 @@
+console.log({
+    "a\xf3b": "\xf3"
+});