Repository: swc-project/swc (rust)

Issue:
Transform optional chaining maybe incorrectly, inconsistent with babel
### Describe the bug

If a is `undefined`, this code can run in browser directly, and Babel transformed like this:

<img width="488" alt="image" src="https://user-images.githubusercontent.com/57202839/233581829-b08de93f-9d9d-4353-8e6d-1aeb2f4c8e59.png">

SWC output:
(a === null || a === void 0 ? void 0 : a.b())();

Is this an expected behavior

### Input code

```typescript
a?.b()()
```


### Config

```json
{
  "jsc": {
    "transform": {
      "react": {
        "runtime": "automatic"
      }
    },
    "parser": {
      "syntax": "typescript",
      "tsx": true
    },
    "target": "es5",
    "loose": false,
    "minify": {
      "compress": false,
      "mangle": false
    }
  },
  "module": {
    "type": "es6"
  },
  "minify": false,
  "isModule": true
}
```


### Playground link

https://play.swc.rs/?version=1.3.52&code=H4sIAAAAAAAAA0u010vS0NTQ5AIA1MkElQkAAAA%3D&config=H4sIAAAAAAAAA1VQyw6DMAy77ytQzly3w%2F5hHxF1AXWiDyWpNIT497WFMji1tR3b6XLrOviIgWe35Gt%2BKKOXIbA7oAwyodETUKDk1TrKIGDS4FCtgZ1e67n2m2NEFuKzncxe8VtGdY4khm1U6BurUijlRBcbRR6plACS%2B66GKQQpHQachHbMWW%2BH%2BZxngotMIldhkaIfp2P%2B1rrXSHDhnSrZfiZ33eIf8Be1sMMYrLzaZF1i%2FQEQs2upZQEAAA%3D%3D

### Expected behavior

if a is undefined, code can run successfully

### Actual behavior

Output code is `(a === null || a === void 0 ? void 0 : a.b())();`, an Error has been thrownÏ

### Version

1.3.52

### Additional context

_No response_

Pull request:
fix(es/compat): Transform curried function call with optional chaining
**Description:**

Fix transform of curried function call with option chain

close #7312 

**BREAKING CHANGE:**

<!--
If this PR introduces a breaking change, it must contain a notice for it to be included in the CHANGELOG. Add description or remove entirely if not breaking.

You may need to update `MIGRATION.md` for the breaking changes.
-->

**Related issue (if exists):**


Patch:
diff --git a/crates/swc_ecma_transforms_base/src/fixer.rs b/crates/swc_ecma_transforms_base/src/fixer.rs
index 17103d8c2c..6daa14d15a 100644
--- a/crates/swc_ecma_transforms_base/src/fixer.rs
+++ b/crates/swc_ecma_transforms_base/src/fixer.rs
@@ -1000,6 +1000,11 @@ impl Fixer<'_> {
                     return;
                 }
 
+                // `(a?.b.c)() !== a?.b.c()`
+                if Self::contain_opt_chain(expr) {
+                    return;
+                }
+
                 let expr_span = expr.span();
                 let paren_span = *paren_span;
                 self.unwrap_expr(expr);
@@ -1011,6 +1016,17 @@ impl Fixer<'_> {
         }
     }
 
+    fn contain_opt_chain(e: &Expr) -> bool {
+        match e {
+            Expr::Member(member) => Self::contain_opt_chain(&member.obj),
+            Expr::OptChain(_) => true,
+            Expr::Call(CallExpr { callee, .. }) if callee.is_expr() => {
+                Self::contain_opt_chain(callee.as_expr().unwrap())
+            }
+            _ => false,
+        }
+    }
+
     fn handle_expr_stmt(&mut self, expr: &mut Expr) {
         match expr {
             // It's important for arrow pass to work properly.
diff --git a/crates/swc_ecma_transforms_compat/src/es2020/opt_chaining.rs b/crates/swc_ecma_transforms_compat/src/es2020/opt_chaining.rs
index 2fa09f7b05..31fe89a08d 100644
--- a/crates/swc_ecma_transforms_compat/src/es2020/opt_chaining.rs
+++ b/crates/swc_ecma_transforms_compat/src/es2020/opt_chaining.rs
@@ -235,6 +235,27 @@ impl OptChaining {
                     })),
                 };
             }
+            Callee::Expr(callee) if callee.is_call() => {
+                let mut callee = callee.take().call().unwrap();
+                let callee = self.handle_call(&mut callee);
+
+                return match callee {
+                    Ok(expr) => Ok(CondExpr {
+                        span: e.span,
+                        alt: Box::new(Expr::Call(CallExpr {
+                            span: DUMMY_SP,
+                            callee: expr.alt.as_callee(),
+                            args: e.args.take(),
+                            type_args: e.type_args.take(),
+                        })),
+                        ..expr
+                    }),
+                    Err(callee) => Err(Expr::Call(CallExpr {
+                        callee: callee.as_callee(),
+                        ..e.take()
+                    })),
+                };
+            }
             _ => {}
         }
 


Test patch:
diff --git a/crates/swc/tests/fixture/issues-6xxx/6459/1/output/index.ts b/crates/swc/tests/fixture/issues-6xxx/6459/1/output/index.ts
index ec4e5fe191..fd534b9f81 100644
--- a/crates/swc/tests/fixture/issues-6xxx/6459/1/output/index.ts
+++ b/crates/swc/tests/fixture/issues-6xxx/6459/1/output/index.ts
@@ -1,1 +1,1 @@
-(myValue.children?.children)[0].value = 'My Value';
+(myValue.children?.children[0]).value = 'My Value';
diff --git a/crates/swc/tests/fixture/issues-6xxx/6459/3/output/index.ts b/crates/swc/tests/fixture/issues-6xxx/6459/3/output/index.ts
index b464686388..a2d71573f2 100644
--- a/crates/swc/tests/fixture/issues-6xxx/6459/3/output/index.ts
+++ b/crates/swc/tests/fixture/issues-6xxx/6459/3/output/index.ts
@@ -1,1 +1,1 @@
-(foo?.bar.bar.bar?.baz).baz.baz.foo.baz = 1;
+(foo?.bar.bar.bar?.baz.baz.baz).foo.baz = 1;
diff --git a/crates/swc/tests/tsc-references/deleteChain.1.normal.js b/crates/swc/tests/tsc-references/deleteChain.1.normal.js
index 03736e44e3..812a768b6f 100644
--- a/crates/swc/tests/tsc-references/deleteChain.1.normal.js
+++ b/crates/swc/tests/tsc-references/deleteChain.1.normal.js
@@ -1,14 +1,14 @@
 //// [deleteChain.ts]
-var _o3_b, _o3_b1, _o4_b_c_d, _o4_b, _o4_b_c_d1, _o4_b1, _o4_b_c_d2, _o4_b2, _o5_b_c_d, _o5_b, _o5_b_c_d1, _o5_b1, _o6_b_c_d, _o6_b, _o6_b_c_d1, _o6_b1;
+var _o3_b, _o3_b1, _o4_b_c_d, _o4_b, _ref, _o4_b1, _o4_b_c_d1, _o4_b2, _o5_b_c_d, _o5_b, _o5_b_c_d1, _o5_b1, _o6_b_c_d, _o6_b, _o6_b_c_d1, _o6_b1;
 o1 === null || o1 === void 0 ? void 0 : delete o1.b;
 delete (o1 === null || o1 === void 0 ? void 0 : o1.b);
 o2 === null || o2 === void 0 ? void 0 : delete o2.b.c;
-o2 === null || o2 === void 0 ? void 0 : delete o2.b.c;
+delete (o2 === null || o2 === void 0 ? void 0 : o2.b.c);
 (_o3_b = o3.b) === null || _o3_b === void 0 ? void 0 : delete _o3_b.c;
 delete ((_o3_b1 = o3.b) === null || _o3_b1 === void 0 ? void 0 : _o3_b1.c);
 (_o4_b_c_d = (_o4_b = o4.b) === null || _o4_b === void 0 ? void 0 : _o4_b.c.d) === null || _o4_b_c_d === void 0 ? void 0 : delete _o4_b_c_d.e;
-(_o4_b_c_d1 = (_o4_b1 = o4.b) === null || _o4_b1 === void 0 ? void 0 : _o4_b1.c.d) === null || _o4_b_c_d1 === void 0 ? void 0 : delete _o4_b_c_d1.e;
-delete ((_o4_b_c_d2 = (_o4_b2 = o4.b) === null || _o4_b2 === void 0 ? void 0 : _o4_b2.c.d) === null || _o4_b_c_d2 === void 0 ? void 0 : _o4_b_c_d2.e);
+(_ref = (_o4_b1 = o4.b) === null || _o4_b1 === void 0 ? void 0 : _o4_b1.c.d) === null || _ref === void 0 ? void 0 : delete _ref.e;
+delete ((_o4_b_c_d1 = (_o4_b2 = o4.b) === null || _o4_b2 === void 0 ? void 0 : _o4_b2.c.d) === null || _o4_b_c_d1 === void 0 ? void 0 : _o4_b_c_d1.e);
 (_o5_b_c_d = (_o5_b = o5.b) === null || _o5_b === void 0 ? void 0 : _o5_b.call(o5).c.d) === null || _o5_b_c_d === void 0 ? void 0 : delete _o5_b_c_d.e;
 delete ((_o5_b_c_d1 = (_o5_b1 = o5.b) === null || _o5_b1 === void 0 ? void 0 : _o5_b1.call(o5).c.d) === null || _o5_b_c_d1 === void 0 ? void 0 : _o5_b_c_d1.e);
 (_o6_b_c_d = (_o6_b = o6.b) === null || _o6_b === void 0 ? void 0 : _o6_b["c"].d) === null || _o6_b_c_d === void 0 ? void 0 : delete _o6_b_c_d["e"];
diff --git a/crates/swc/tests/tsc-references/deleteChain.2.minified.js b/crates/swc/tests/tsc-references/deleteChain.2.minified.js
index a31b3d6f43..13549dc4bc 100644
--- a/crates/swc/tests/tsc-references/deleteChain.2.minified.js
+++ b/crates/swc/tests/tsc-references/deleteChain.2.minified.js
@@ -1,3 +1,3 @@
 //// [deleteChain.ts]
-var _o3_b, _o3_b1, _o4_b_c_d, _o4_b, _o4_b_c_d1, _o4_b1, _o4_b_c_d2, _o4_b2, _o5_b_c_d, _o5_b, _o5_b_c_d1, _o5_b1, _o6_b_c_d, _o6_b, _o6_b_c_d1, _o6_b1;
-null == o1 || delete o1.b, delete (null == o1 ? void 0 : o1.b), null == o2 || delete o2.b.c, null == o2 || delete o2.b.c, null === (_o3_b = o3.b) || void 0 === _o3_b || delete _o3_b.c, delete (null === (_o3_b1 = o3.b) || void 0 === _o3_b1 ? void 0 : _o3_b1.c), null === (_o4_b_c_d = null === (_o4_b = o4.b) || void 0 === _o4_b ? void 0 : _o4_b.c.d) || void 0 === _o4_b_c_d || delete _o4_b_c_d.e, null === (_o4_b_c_d1 = null === (_o4_b1 = o4.b) || void 0 === _o4_b1 ? void 0 : _o4_b1.c.d) || void 0 === _o4_b_c_d1 || delete _o4_b_c_d1.e, delete (null === (_o4_b_c_d2 = null === (_o4_b2 = o4.b) || void 0 === _o4_b2 ? void 0 : _o4_b2.c.d) || void 0 === _o4_b_c_d2 ? void 0 : _o4_b_c_d2.e), null === (_o5_b_c_d = null === (_o5_b = o5.b) || void 0 === _o5_b ? void 0 : _o5_b.call(o5).c.d) || void 0 === _o5_b_c_d || delete _o5_b_c_d.e, delete (null === (_o5_b_c_d1 = null === (_o5_b1 = o5.b) || void 0 === _o5_b1 ? void 0 : _o5_b1.call(o5).c.d) || void 0 === _o5_b_c_d1 ? void 0 : _o5_b_c_d1.e), null === (_o6_b_c_d = null === (_o6_b = o6.b) || void 0 === _o6_b ? void 0 : _o6_b.c.d) || void 0 === _o6_b_c_d || delete _o6_b_c_d.e, delete (null === (_o6_b_c_d1 = null === (_o6_b1 = o6.b) || void 0 === _o6_b1 ? void 0 : _o6_b1.c.d) || void 0 === _o6_b_c_d1 ? void 0 : _o6_b_c_d1.e);
+var _o3_b, _o3_b1, _o4_b_c_d, _o4_b, _ref, _o4_b1, _o4_b_c_d1, _o4_b2, _o5_b_c_d, _o5_b, _o5_b_c_d1, _o5_b1, _o6_b_c_d, _o6_b, _o6_b_c_d1, _o6_b1;
+null == o1 || delete o1.b, delete (null == o1 ? void 0 : o1.b), null == o2 || delete o2.b.c, delete (null == o2 ? void 0 : o2.b.c), null === (_o3_b = o3.b) || void 0 === _o3_b || delete _o3_b.c, delete (null === (_o3_b1 = o3.b) || void 0 === _o3_b1 ? void 0 : _o3_b1.c), null === (_o4_b_c_d = null === (_o4_b = o4.b) || void 0 === _o4_b ? void 0 : _o4_b.c.d) || void 0 === _o4_b_c_d || delete _o4_b_c_d.e, null === (_ref = null === (_o4_b1 = o4.b) || void 0 === _o4_b1 ? void 0 : _o4_b1.c.d) || void 0 === _ref || delete _ref.e, delete (null === (_o4_b_c_d1 = null === (_o4_b2 = o4.b) || void 0 === _o4_b2 ? void 0 : _o4_b2.c.d) || void 0 === _o4_b_c_d1 ? void 0 : _o4_b_c_d1.e), null === (_o5_b_c_d = null === (_o5_b = o5.b) || void 0 === _o5_b ? void 0 : _o5_b.call(o5).c.d) || void 0 === _o5_b_c_d || delete _o5_b_c_d.e, delete (null === (_o5_b_c_d1 = null === (_o5_b1 = o5.b) || void 0 === _o5_b1 ? void 0 : _o5_b1.call(o5).c.d) || void 0 === _o5_b_c_d1 ? void 0 : _o5_b_c_d1.e), null === (_o6_b_c_d = null === (_o6_b = o6.b) || void 0 === _o6_b ? void 0 : _o6_b.c.d) || void 0 === _o6_b_c_d || delete _o6_b_c_d.e, delete (null === (_o6_b_c_d1 = null === (_o6_b1 = o6.b) || void 0 === _o6_b1 ? void 0 : _o6_b1.c.d) || void 0 === _o6_b_c_d1 ? void 0 : _o6_b_c_d1.e);
diff --git a/crates/swc/tests/tsc-references/parentheses.1.normal.js b/crates/swc/tests/tsc-references/parentheses.1.normal.js
index 952836f56c..f58ca270b5 100644
--- a/crates/swc/tests/tsc-references/parentheses.1.normal.js
+++ b/crates/swc/tests/tsc-references/parentheses.1.normal.js
@@ -2,4 +2,4 @@
 o1(o1 !== null && o1 !== void 0 ? o1 : 1);
 (o2 === null || o2 === void 0 ? void 0 : o2.b)(o1 !== null && o1 !== void 0 ? o1 : 1);
 (o3 === null || o3 === void 0 ? void 0 : o3.b())(o1 !== null && o1 !== void 0 ? o1 : 1);
-o4 === null || o4 === void 0 ? void 0 : o4.b().c(o1 !== null && o1 !== void 0 ? o1 : 1);
+(o4 === null || o4 === void 0 ? void 0 : o4.b().c)(o1 !== null && o1 !== void 0 ? o1 : 1);
diff --git a/crates/swc/tests/tsc-references/parentheses.2.minified.js b/crates/swc/tests/tsc-references/parentheses.2.minified.js
index d5530bf83a..ee0dd1315a 100644
--- a/crates/swc/tests/tsc-references/parentheses.2.minified.js
+++ b/crates/swc/tests/tsc-references/parentheses.2.minified.js
@@ -1,2 +1,2 @@
 //// [parentheses.ts]
-o1(null != o1 ? o1 : 1), (null == o2 ? void 0 : o2.b)(null != o1 ? o1 : 1), (null == o3 ? void 0 : o3.b())(null != o1 ? o1 : 1), null == o4 || o4.b().c(null != o1 ? o1 : 1);
+o1(null != o1 ? o1 : 1), (null == o2 ? void 0 : o2.b)(null != o1 ? o1 : 1), (null == o3 ? void 0 : o3.b())(null != o1 ? o1 : 1), (null == o4 ? void 0 : o4.b().c)(null != o1 ? o1 : 1);
diff --git a/crates/swc_ecma_transforms_compat/tests/es2020_optional_chaining.rs b/crates/swc_ecma_transforms_compat/tests/es2020_optional_chaining.rs
index 8566ad1d72..060fbea8be 100644
--- a/crates/swc_ecma_transforms_compat/tests/es2020_optional_chaining.rs
+++ b/crates/swc_ecma_transforms_compat/tests/es2020_optional_chaining.rs
@@ -105,6 +105,47 @@ foo == null ? void 0 : foo.bar == null ? void 0 : foo.bar(foo.bar, true);
 "#
 );
 
+// parentheses
+test!(
+    syntax(),
+    |_| tr(Default::default()),
+    parentheses,
+    r#"
+  (o1)(o1 ?? 1);
+  (o2?.b)(o1 ?? 1);
+  (o3?.b())(o1 ?? 1);
+  (o4?.b().c)(o1 ?? 1);
+"#,
+    r#"
+    o1(o1 ?? 1);
+    (o2 === null || o2 === void 0 ? void 0 : o2.b)(o1 ?? 1);
+    (o3 === null || o3 === void 0 ? void 0 : o3.b())(o1 ?? 1);
+    (o4 === null || o4 === void 0 ? void 0 : o4.b().c)(o1 ?? 1);
+"#
+);
+
+// curried_function_call
+test!(
+    syntax(),
+    |_| tr(Default::default()),
+    curried_function_call,
+    r#"
+    a?.b()();
+    a?.b()()();
+    a?.b()().c;
+    a?.b()()?.c;
+    a?.b()()?.c()();
+"#,
+    r#"
+    var _a_b, _a_b1;
+    a === null || a === void 0 ? void 0 : a.b()();
+    a === null || a === void 0 ? void 0 : a.b()()();
+    a === null || a === void 0 ? void 0 : a.b()().c;
+    (_a_b = a === null || a === void 0 ? void 0 : a.b()()) === null || _a_b === void 0 ? void 0 : _a_b.c;
+    (_a_b1 = a === null || a === void 0 ? void 0 : a.b()()) === null || _a_b1 === void 0 ? void 0 : _a_b1.c()();
+"#
+);
+
 // indirect_eval_call
 test!(
     syntax(),
diff --git a/crates/swc_ecma_transforms_compat/tests/opt-chain/issue-7312/exec.js b/crates/swc_ecma_transforms_compat/tests/opt-chain/issue-7312/exec.js
new file mode 100644
index 0000000000..d7160a5327
--- /dev/null
+++ b/crates/swc_ecma_transforms_compat/tests/opt-chain/issue-7312/exec.js
@@ -0,0 +1,3 @@
+var a = undefined;
+
+a?.a()()?.a()();