Repository: swc-project/swc (rust)

Issue:
Calling a private optional chained field fails with TypeError is not a function
### Describe the bug

- [x] I've read [if you think transpilation is wrong](https://github.com/swc-project/swc/issues/7435).

When target is `es2017` (< es2022), the optional chained private field in method `search` becomes `undefined()` causing this runtime error:
```
: _class_private_field_get1.call)(this)

TypeError: (intermediate value)(intermediate value)(intermediate value) is not a function
```

### Input code

```typescript
class Foo {
  #priv
  search() {
    this.#priv?.()
  }
}

console.log(new Foo().search())
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "tsx": false
    },
    "target": "es2017",
    "loose": false,
    "minify": {
      "compress": false,
      "mangle": false
    }
  },
  "module": {
    "type": "es6"
  },
  "minify": false,
  "isModule": true
}
```


### Playground link

https://play.swc.rs/?version=1.3.74&code=H4sIAAAAAAAAA0vOSSwuVnDLz1eo5lJQUC4oyiwD0sWpiUXJGRqaYEEFhZKMzGI9sJy9noYmUKiWq5aLKzk%2Frzg%2FJ1UvJz9dIy%2B1HGSIhqYeTKumAgCEDmZWWwAAAA%3D%3D&config=H4sIAAAAAAAAA1VPSQ7CMAy85xWRzxyAA0j8gUdEwa2Csil2JaKqfydpk0Jv9iye8SykhDdpeMi5jGWJKhGmfS8IZc%2FqUxDgHJF0MpHh1FmmSg3KEq7QsjHAKo3I1YV0PV%2FuzQE2BMLuaJgz3gz5P1MHFxMSHYVVqvxo8ZgoWiq48JpWsv1S%2B24NbvAT9bD9MBh6dienCcXyBZ3DefIXAQAA

### SWC Info output

_No response_

### Expected behavior

I would expect that the function is not invoked:
```js
search() {
  var _class_private_field_get1;
  (_class_private_field_get1 = _class_private_field_get(this, _priv)) === null || _class_private_field_get1 === void 0 ? void 0 : _class_private_field_get1.call(this);
}
```

Related, [tsc](https://www.typescriptlang.org/play?#code/MYGwhgzhAEBiD29oG8BQ1oGIAOAnAlgG7rQQCmYuwAFgBQCUKJGALtfhAHQ4GED8nBiQC+qUamDwAdhHggynEPADmtKWQDucRA07lKNBoyA) generates:
```js
search() {
  var _a;
  (_a = __classPrivateFieldGet(this, _Foo_priv, "f")) === null || _a === void 0 ? void 0 : _a.call(this);
}
```

### Actual behavior

The search method is transpiled as:

```js
search() {
  var _class_private_field_get1;
  ((_class_private_field_get1 = _class_private_field_get(this, _priv)) === null || _class_private_field_get1 === void 0 ? void 0 : _class_private_field_get1.call)(this);
}
```

notice the `_class_private_field_get1.call)(this)`.

### Version

1.3.74

### Additional context

Issue related to private optional chaining: https://github.com/swc-project/swc/issues/3610 (but that issue does not call the property's value)

Pull request:
fix(es/compat): Generate `OptCall` for `OptCall` for private fields
**Related issue:**

 - Closes #8003 

Patch:
diff --git a/crates/swc_ecma_transforms_base/src/fixer.rs b/crates/swc_ecma_transforms_base/src/fixer.rs
index c585c11162..62b296687c 100644
--- a/crates/swc_ecma_transforms_base/src/fixer.rs
+++ b/crates/swc_ecma_transforms_base/src/fixer.rs
@@ -101,18 +101,16 @@ impl Fixer<'_> {
     }
 
     fn wrap_callee(&mut self, e: &mut Expr) {
-        if match e {
-            Expr::Lit(Lit::Num(..) | Lit::Str(..)) => false,
+        match e {
+            Expr::Lit(Lit::Num(..) | Lit::Str(..)) => (),
             Expr::Cond(..)
             | Expr::Bin(..)
             | Expr::Lit(..)
             | Expr::Unary(..)
             | Expr::Object(..)
             | Expr::Await(..)
-            | Expr::Yield(..) => true,
-            _ => false,
-        } {
-            self.wrap(e)
+            | Expr::Yield(..) => self.wrap(e),
+            _ => (),
         }
     }
 }
@@ -400,7 +398,11 @@ impl VisitMut for Fixer<'_> {
     fn visit_mut_call_expr(&mut self, node: &mut CallExpr) {
         let old = self.visit_call(&mut node.args, &mut node.callee);
         if let Callee::Expr(e) = &mut node.callee {
-            self.wrap_callee(e)
+            if let Expr::OptChain(_) = &**e {
+                self.wrap(e)
+            } else {
+                self.wrap_callee(e)
+            }
         }
 
         self.ctx = old;
diff --git a/crates/swc_ecma_transforms_compat/src/es2022/class_properties/private_field.rs b/crates/swc_ecma_transforms_compat/src/es2022/class_properties/private_field.rs
index 57138f474f..a25c2c57a8 100644
--- a/crates/swc_ecma_transforms_compat/src/es2022/class_properties/private_field.rs
+++ b/crates/swc_ecma_transforms_compat/src/es2022/class_properties/private_field.rs
@@ -455,21 +455,28 @@ impl<'a> VisitMut for PrivateAccessVisitor<'a> {
                 let (expr, this) = self.visit_mut_private_get(&mut callee, None);
                 if let Some(this) = this {
                     let args = iter::once(this.as_arg()).chain(call.args.take()).collect();
-                    *e = Expr::Call(CallExpr {
+                    let call = OptCall {
                         span: *span,
-                        callee: OptChainExpr {
-                            span: *span,
-                            optional: *optional,
-                            base: Box::new(OptChainBase::Member(MemberExpr {
-                                span: call.span,
-                                obj: Box::new(expr),
-                                prop: MemberProp::Ident(quote_ident!("call")),
-                            })),
-                        }
-                        .as_callee(),
+                        callee: Box::new(
+                            OptChainExpr {
+                                span: *span,
+                                optional: *optional,
+                                base: Box::new(OptChainBase::Member(MemberExpr {
+                                    span: call.span,
+                                    obj: Box::new(expr),
+                                    prop: MemberProp::Ident(quote_ident!("call")),
+                                })),
+                            }
+                            .into(),
+                        ),
                         args,
                         type_args: call.type_args.take(),
-                    });
+                    };
+                    *e = Expr::OptChain(OptChainExpr {
+                        span: *span,
+                        optional: false,
+                        base: Box::new(OptChainBase::Call(call)),
+                    })
                 } else {
                     call.callee = Box::new(expr);
                 }


Test patch:
diff --git a/crates/swc/tests/tsc-references/privateNameFieldCallExpression.1.normal.js b/crates/swc/tests/tsc-references/privateNameFieldCallExpression.1.normal.js
index c26a155eda..7c272bb63a 100644
--- a/crates/swc/tests/tsc-references/privateNameFieldCallExpression.1.normal.js
+++ b/crates/swc/tests/tsc-references/privateNameFieldCallExpression.1.normal.js
@@ -8,7 +8,7 @@ class A {
         var _class_private_field_get1;
         var _this_getInstance;
         _class_private_field_get(this, _fieldFunc).call(this);
-        ((_class_private_field_get1 = _class_private_field_get(this, _fieldFunc)) === null || _class_private_field_get1 === void 0 ? void 0 : _class_private_field_get1.call)(this);
+        (_class_private_field_get1 = _class_private_field_get(this, _fieldFunc)) === null || _class_private_field_get1 === void 0 ? void 0 : _class_private_field_get1.call(this);
         const func = _class_private_field_get(this, _fieldFunc);
         func();
         new (_class_private_field_get(this, _fieldFunc))();
diff --git a/crates/swc/tests/tsc-references/privateNameStaticFieldCallExpression.1.normal.js b/crates/swc/tests/tsc-references/privateNameStaticFieldCallExpression.1.normal.js
index 6e8d7f4acb..11b16b4557 100644
--- a/crates/swc/tests/tsc-references/privateNameStaticFieldCallExpression.1.normal.js
+++ b/crates/swc/tests/tsc-references/privateNameStaticFieldCallExpression.1.normal.js
@@ -4,7 +4,7 @@ class A {
     test() {
         var _class_static_private_field_spec_get1;
         _class_static_private_field_spec_get(A, A, _fieldFunc).call(A);
-        ((_class_static_private_field_spec_get1 = _class_static_private_field_spec_get(A, A, _fieldFunc)) === null || _class_static_private_field_spec_get1 === void 0 ? void 0 : _class_static_private_field_spec_get1.call)(A);
+        (_class_static_private_field_spec_get1 = _class_static_private_field_spec_get(A, A, _fieldFunc)) === null || _class_static_private_field_spec_get1 === void 0 ? void 0 : _class_static_private_field_spec_get1.call(A);
         const func = _class_static_private_field_spec_get(A, A, _fieldFunc);
         func();
         new (_class_static_private_field_spec_get(A, A, _fieldFunc))();
diff --git a/crates/swc_ecma_transforms_compat/tests/es2022_class_properties.rs b/crates/swc_ecma_transforms_compat/tests/es2022_class_properties.rs
index 34f3840443..0d8abadc14 100644
--- a/crates/swc_ecma_transforms_compat/tests/es2022_class_properties.rs
+++ b/crates/swc_ecma_transforms_compat/tests/es2022_class_properties.rs
@@ -9,6 +9,7 @@ use swc_ecma_transforms_compat::{
     es2015::{arrow, block_scoping, classes, function_name, template_literal},
     es2016::exponentiation,
     es2017::async_to_generator,
+    es2020::optional_chaining,
     es2022::class_properties,
     es3::reserved_words,
 };
@@ -6669,3 +6670,41 @@ var _x = {
 };
 "#
 );
+
+test!(
+    syntax(),
+    |t| {
+        let unresolved = Mark::new();
+        chain!(
+            class_properties(Some(t.comments.clone()), Default::default()),
+            optional_chaining(Default::default(), unresolved)
+        )
+    },
+    issue_8003,
+    "
+class Foo {
+  #priv
+  search() {
+    this.#priv?.()
+  }
+}
+    
+console.log(new Foo().search())",
+    r#"
+var _priv = new WeakMap();
+class Foo {
+  search() {
+    var _class_private_field_get1;
+    (_class_private_field_get1 = _class_private_field_get(this, _priv)) === null || _class_private_field_get1 === void 0 ? void 0 : _class_private_field_get1.call(this);
+  }
+  constructor(){
+    _class_private_field_init(this, _priv, {
+      writable: true,
+      value: void 0
+      });
+  }
+}
+
+console.log(new Foo().search());
+"#
+);