Repository: swc-project/swc (rust)

Issue:
Error in compilation of private method calls (TypeScript)
### Describe the bug

Calling a private method on another instance of its own class does not work. The `thisArg` for `Function.prototype.call()` seems to be hardcoded to `this`.

### Input code

```typescript
export class Node {
  childNodes: Node[] = [];
  parent?: Node;

  link(): void {
    this.#link(this);
  }

  #link(parent: Node): void {
    this.parent = parent;

    for (const childNode of this.childNodes) {
      childNode.#link(this);
    }
  }
}
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "typescript"
    },
    "target": "es2019"
  }
}
```


### Playground link

_No response_

### Expected behavior

Actual output:

```js
function _classPrivateMethodGet(receiver, privateSet, fn) {
    if (!privateSet.has(receiver)) {
        throw new TypeError("attempted to get private field on non-instance");
    }
    return fn;
}
var _link = new WeakSet();
export class Node {
    link() {
        _classPrivateMethodGet(this, _link, link).call(this, this); // OK
    }
    constructor(){
        _link.add(this);
        this.childNodes = [];
    }
}
function link(parent) {
    this.parent = parent;
    for (const childNode of this.childNodes){
        _classPrivateMethodGet(childNode, _link, link).call(this, this); // ERROR
    }
}
```

Expected output:

```js
function _classPrivateMethodGet(receiver, privateSet, fn) {
    if (!privateSet.has(receiver)) {
        throw new TypeError("attempted to get private field on non-instance");
    }
    return fn;
}
var _link = new WeakSet();
export class Node {
    link() {
        _classPrivateMethodGet(this, _link, link).call(this, this); // OK
    }
    constructor(){
        _link.add(this);
        this.childNodes = [];
    }
}
function link(parent) {
    this.parent = parent;
    for (const childNode of this.childNodes){
        _classPrivateMethodGet(childNode, _link, link).call(childNode, this); // OK
    }
}
```

### Version

1.2.120

### Additional context

_No response_

Pull request:
fix(es/transforms): class private method
<!-- Thanks for submitting a pull request! Please provide enough information so that others can review your pull request. -->

**Description:**

<!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? -->

**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):**
- fix #3055 

Patch:
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 15c7af66af..74b558b267 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
@@ -624,33 +624,32 @@ impl<'a> FieldAccessFolder<'a> {
                 };
             }
 
-            if is_method {
-                let h = helper!(class_private_method_get, "classPrivateMethodGet");
-
-                return (
-                    CallExpr {
-                        span: DUMMY_SP,
-                        callee: h,
-                        args: vec![obj.as_arg(), ident.as_arg(), method_name.as_arg()],
-                        type_args: Default::default(),
-                    }
-                    .into(),
-                    Some(Expr::This(ThisExpr { span: DUMMY_SP })),
-                );
-            }
-
-            let get = helper!(class_private_field_get, "classPrivateFieldGet");
+            let get = if is_method {
+                helper!(class_private_method_get, "classPrivateMethodGet")
+            } else {
+                helper!(class_private_field_get, "classPrivateFieldGet")
+            };
 
             match &*obj {
                 Expr::This(this) => (
-                    CallExpr {
-                        span: DUMMY_SP,
-                        callee: get,
-                        args: vec![this.as_arg(), ident.as_arg()],
+                    if is_method {
+                        CallExpr {
+                            span: DUMMY_SP,
+                            callee: get,
+                            args: vec![obj.clone().as_arg(), ident.as_arg(), method_name.as_arg()],
+                            type_args: Default::default(),
+                        }
+                        .into()
+                    } else {
+                        CallExpr {
+                            span: DUMMY_SP,
+                            callee: get,
+                            args: vec![this.as_arg(), ident.as_arg()],
 
-                        type_args: Default::default(),
-                    }
-                    .into(),
+                            type_args: Default::default(),
+                        }
+                        .into()
+                    },
                     Some(Expr::This(*this)),
                 ),
                 _ => {
@@ -669,31 +668,33 @@ impl<'a> FieldAccessFolder<'a> {
                         var
                     });
 
+                    let first_arg = if is_alias_initialized {
+                        var.clone().as_arg()
+                    } else {
+                        if aliased {
+                            AssignExpr {
+                                span: DUMMY_SP,
+                                left: PatOrExpr::Pat(Box::new(Pat::Ident(var.clone().into()))),
+                                op: op!("="),
+                                right: obj.take(),
+                            }
+                            .as_arg()
+                        } else {
+                            var.clone().as_arg()
+                        }
+                    };
+
+                    let args = if is_method {
+                        vec![first_arg, ident.as_arg(), method_name.as_arg()]
+                    } else {
+                        vec![first_arg, ident.as_arg()]
+                    };
+
                     (
                         CallExpr {
                             span: DUMMY_SP,
                             callee: get,
-                            args: vec![
-                                if is_alias_initialized {
-                                    var.clone().as_arg()
-                                } else {
-                                    if aliased {
-                                        AssignExpr {
-                                            span: DUMMY_SP,
-                                            left: PatOrExpr::Pat(Box::new(Pat::Ident(
-                                                var.clone().into(),
-                                            ))),
-                                            op: op!("="),
-                                            right: obj.take(),
-                                        }
-                                        .as_arg()
-                                    } else {
-                                        var.clone().as_arg()
-                                    }
-                                },
-                                ident.as_arg(),
-                            ],
-
+                            args,
                             type_args: Default::default(),
                         }
                         .into(),


Test patch:
diff --git a/crates/swc/tests/fixture/issue-3055/1/input/.swcrc b/crates/swc/tests/fixture/issue-3055/1/input/.swcrc
new file mode 100644
index 0000000000..1220976a2e
--- /dev/null
+++ b/crates/swc/tests/fixture/issue-3055/1/input/.swcrc
@@ -0,0 +1,8 @@
+{
+    "jsc": {
+        "parser": {
+            "syntax": "typescript"
+        },
+        "target": "es2019"
+    }
+}
diff --git a/crates/swc/tests/fixture/issue-3055/1/input/index.ts b/crates/swc/tests/fixture/issue-3055/1/input/index.ts
new file mode 100644
index 0000000000..71c4c6400d
--- /dev/null
+++ b/crates/swc/tests/fixture/issue-3055/1/input/index.ts
@@ -0,0 +1,16 @@
+export class Node {
+    childNodes: Node[] = [];
+    parent?: Node;
+
+    link(): void {
+        this.#link(this);
+    }
+
+    #link(parent: Node): void {
+        this.parent = parent;
+
+        for (const childNode of this.childNodes) {
+            childNode.#link(this);
+        }
+    }
+}
diff --git a/crates/swc/tests/fixture/issue-3055/1/output/index.ts b/crates/swc/tests/fixture/issue-3055/1/output/index.ts
new file mode 100644
index 0000000000..91e55788e6
--- /dev/null
+++ b/crates/swc/tests/fixture/issue-3055/1/output/index.ts
@@ -0,0 +1,22 @@
+function _classPrivateMethodGet(receiver, privateSet, fn) {
+    if (!privateSet.has(receiver)) {
+        throw new TypeError("attempted to get private field on non-instance");
+    }
+    return fn;
+}
+var _link = new WeakSet();
+export class Node {
+    link() {
+        _classPrivateMethodGet(this, _link, link).call(this, this);
+    }
+    constructor(){
+        _link.add(this);
+        this.childNodes = [];
+    }
+}
+function link(parent) {
+    this.parent = parent;
+    for (const childNode of this.childNodes){
+        _classPrivateMethodGet(childNode, _link, link).call(childNode, this);
+    }
+}
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 f23558a68f..0219b3bb0f 100644
--- a/crates/swc_ecma_transforms_compat/tests/es2022_class_properties.rs
+++ b/crates/swc_ecma_transforms_compat/tests/es2022_class_properties.rs
@@ -5236,6 +5236,45 @@ test!(
     "
 );
 
+test!(
+    syntax(),
+    |_| class_properties(class_properties::Config { loose: false }),
+    issue_3055_1,
+    "
+export class Node {
+    foo() {
+        this.#bar(this);
+    }
+
+    #bar(parent) {
+        parent.#baz(this);
+        parent.baz.#baz(this);
+    }
+
+    #baz(child) { }
+}
+",
+    "
+var _bar = new WeakSet(),
+    _baz = new WeakSet();
+export class Node {
+    foo() {
+        _classPrivateMethodGet(this, _bar, bar).call(this, this);
+    }
+    constructor() {
+        _bar.add(this);
+        _baz.add(this);
+    }
+}
+function bar(parent) {
+    var _baz1;
+    _classPrivateMethodGet(parent, _baz, baz).call(parent, this);
+    _classPrivateMethodGet(_baz1 = parent.baz, _baz, baz).call(_baz1, this);
+}
+function baz(child) {}
+"
+);
+
 test!(
     syntax(),
     |_| chain!(