Repository: swc-project/swc (rust)

Issue:
Class properties pass should handle readonly on write-only fields
### Describe the bug

Currently the class properties pass swallows an error.

### Input code

```typescript
class Cl {
  #privateField = 0;

  set #privateFieldValue(newValue) {
    this.#privateField = newValue;
  }

  constructor() {
    expect(() => this.#privateFieldValue).toThrow(TypeError);
  }
}

const cl = new Cl();
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "ecmascript"
    },
    "target": "es2021",
    "minify": {
      "compress": false,
      "mangle": false
    },
    "loose": false
  },
  "minify": false
}
```


### Playground link

https://play.swc.rs/?version=1.2.241&code=H4sIAAAAAAAAA22PwQrCQAxE7%2F2KgJf0UryXehH9guJ9WQNdWLpLklpF%2FHfjLj0o3pLMzBvioxOBY4RnA7DLHG5O6RwoXmGAfd%2FYVUi%2FlYuLC%2BFMaxnaEgXQKUj3S9hMvVleH5hPsygvXhPjlqR7Jq9o%2B3D4g6ktnaZx4rTi%2BMh0Yk7cVqhhCxR8rIX2DZr2Bt0BZ5baAAAA&config=H4sIAAAAAAAAA02LywqEMBAE%2F6XPHtRj%2FmYIE8mSF9M5rMj%2B%2BxpU8NbVRR340MMdaGJUG4t76fKFg%2Fos9BZbx29CF9u0j5vrvC6YkGOJYR%2BJr7mZknBBEvVUUrakN55xqpUvfMqL%2F63bUfaEAAAA

### Expected behavior

It should report an error.

babel output:
```js
"use strict";

function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedeclaration(obj, privateMap); privateMap.set(obj, value); }

function _checkPrivateRedeclaration(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }

function _writeOnlyError(name) { throw new TypeError("\"" + name + "\" is write-only"); }

function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "set"); _classApplyDescriptorSet(receiver, descriptor, value); return value; }

function _classExtractFieldDescriptor(receiver, privateMap, action) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to " + action + " private field on non-instance"); } return privateMap.get(receiver); }

function _classApplyDescriptorSet(receiver, descriptor, value) { if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } }

var _privateField = /*#__PURE__*/new WeakMap();

var _privateFieldValue = /*#__PURE__*/new WeakMap();

class Cl {
  constructor() {
    _classPrivateFieldInitSpec(this, _privateFieldValue, {
      get: void 0,
      set: _set_privateFieldValue
    });

    _classPrivateFieldInitSpec(this, _privateField, {
      writable: true,
      value: 0
    });

    expect(() => (this, _writeOnlyError("#privateFieldValue"))).toThrow(TypeError);
  }

}

function _set_privateFieldValue(newValue) {
  _classPrivateFieldSet(this, _privateField, newValue);
}

const cl = new Cl();
```


### Actual behavior

_No response_

### Version

1.2.241

### Additional context

_No response_

Pull request:
feat(es/compat): Readonly and writeonly private field
<!-- Note: CI script will automatically rebase your PR so please do not rebase unless required -->

<!-- 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? -->

This is the most useless feature I've ever wrote.

**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):**
closes #5599

Patch:
diff --git a/crates/swc_ecma_transforms_base/src/helpers/_class_apply_descriptor_update.js b/crates/swc_ecma_transforms_base/src/helpers/_class_apply_descriptor_update.js
index db089b33ee..30c007f08f 100644
--- a/crates/swc_ecma_transforms_base/src/helpers/_class_apply_descriptor_update.js
+++ b/crates/swc_ecma_transforms_base/src/helpers/_class_apply_descriptor_update.js
@@ -1,5 +1,8 @@
 function _classApplyDescriptorUpdate(receiver, descriptor) {
     if (descriptor.set) {
+        if (!descriptor.get) {
+            throw new TypeError("attempted to read set only private field");
+        }
         if (!("__destrWrapper" in descriptor)) {
             descriptor.__destrWrapper = {
                 set value(v) {
diff --git a/crates/swc_ecma_transforms_base/src/helpers/_read_only_error.js b/crates/swc_ecma_transforms_base/src/helpers/_read_only_error.js
index 465c7c3bd7..0ac70dd5e1 100644
--- a/crates/swc_ecma_transforms_base/src/helpers/_read_only_error.js
+++ b/crates/swc_ecma_transforms_base/src/helpers/_read_only_error.js
@@ -1,3 +1,3 @@
 function _readOnlyError(name) {
-  throw new Error("\"" + name + "\" is read-only");
+  throw new TypeError("\"" + name + "\" is read-only");
 }
diff --git a/crates/swc_ecma_transforms_base/src/helpers/_write_only_error.js b/crates/swc_ecma_transforms_base/src/helpers/_write_only_error.js
new file mode 100644
index 0000000000..aa16305ca0
--- /dev/null
+++ b/crates/swc_ecma_transforms_base/src/helpers/_write_only_error.js
@@ -0,0 +1,3 @@
+function _writeOnlyError(name) {
+  throw new TypeError("\"" + name + "\" is write-only");
+}
diff --git a/crates/swc_ecma_transforms_base/src/helpers/mod.rs b/crates/swc_ecma_transforms_base/src/helpers/mod.rs
index a9bb77eab1..4a31fe7bc4 100644
--- a/crates/swc_ecma_transforms_base/src/helpers/mod.rs
+++ b/crates/swc_ecma_transforms_base/src/helpers/mod.rs
@@ -334,6 +334,7 @@ define_helpers!(Helpers {
         set_prototype_of,
         is_native_function
     ),
+    write_only_error: (),
 
     class_private_field_destructure: (
         class_extract_field_descriptor,
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 d041e33d87..b25cbefa21 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
@@ -70,6 +70,15 @@ pub(super) struct PrivateKind {
 }
 
 impl PrivateKind {
+    fn is_readonly(&self) -> bool {
+        self.is_method && !self.has_setter
+    }
+
+    fn is_writeonly(&self) -> bool {
+        // a private method can still be read
+        self.is_method && !self.has_getter && self.has_setter
+    }
+
     fn is_method(&self) -> bool {
         self.is_method && !self.has_getter && !self.has_setter
     }
@@ -274,9 +283,9 @@ impl<'a> VisitMut for PrivateAccessVisitor<'a> {
                 let var = alias_ident_for(&obj, "_ref");
 
                 let this = if matches!(*obj, Expr::This(..)) {
-                    ThisExpr { span: DUMMY_SP }.as_arg()
+                    Box::new(ThisExpr { span: DUMMY_SP }.into())
                 } else if *op == op!("=") {
-                    obj.as_arg()
+                    obj
                 } else {
                     self.vars.push(VarDeclarator {
                         span: DUMMY_SP,
@@ -284,27 +293,31 @@ impl<'a> VisitMut for PrivateAccessVisitor<'a> {
                         init: None,
                         definite: false,
                     });
-                    AssignExpr {
-                        span: obj.span(),
-                        left: PatOrExpr::Pat(var.clone().into()),
-                        op: op!("="),
-                        right: obj,
-                    }
-                    .as_arg()
+                    Box::new(
+                        AssignExpr {
+                            span: obj.span(),
+                            left: PatOrExpr::Pat(var.clone().into()),
+                            op: op!("="),
+                            right: obj,
+                        }
+                        .into(),
+                    )
                 };
 
                 let value = if *op == op!("=") {
-                    right.take().as_arg()
+                    right.take()
                 } else {
                     let left = Box::new(self.visit_mut_private_get(&mut left, Some(var)).0);
 
-                    BinExpr {
-                        span: DUMMY_SP,
-                        left,
-                        op: op.to_update().unwrap(),
-                        right: right.take(),
-                    }
-                    .as_arg()
+                    Box::new(
+                        BinExpr {
+                            span: DUMMY_SP,
+                            left,
+                            op: op.to_update().unwrap(),
+                            right: right.take(),
+                        }
+                        .into(),
+                    )
                 };
 
                 if kind.is_static {
@@ -314,17 +327,34 @@ impl<'a> VisitMut for PrivateAccessVisitor<'a> {
                             class_static_private_field_spec_set,
                             "classStaticPrivateFieldSpecSet"
                         ),
-                        args: vec![this, class_name.clone().as_arg(), ident.as_arg(), value],
+                        args: vec![
+                            this.as_arg(),
+                            class_name.clone().as_arg(),
+                            ident.as_arg(),
+                            value.as_arg(),
+                        ],
 
                         type_args: Default::default(),
                     });
+                } else if kind.is_readonly() {
+                    let err = Expr::Call(CallExpr {
+                        span: DUMMY_SP,
+                        callee: helper!(read_only_error, "readOnlyError"),
+                        args: vec![format!("#{}", n.id.sym).as_arg()],
+                        type_args: None,
+                    })
+                    .into();
+                    *e = Expr::Seq(SeqExpr {
+                        span: *span,
+                        exprs: vec![this, value, err],
+                    });
                 } else {
                     let set = helper!(class_private_field_set, "classPrivateFieldSet");
 
                     *e = Expr::Call(CallExpr {
                         span: DUMMY_SP,
                         callee: set,
-                        args: vec![this, ident.as_arg(), value],
+                        args: vec![this.as_arg(), ident.as_arg(), value.as_arg()],
 
                         type_args: Default::default(),
                     });
@@ -642,7 +672,7 @@ impl<'a> PrivateAccessVisitor<'a> {
                         "classPrivateFieldDestructureSet"
                     );
 
-                    return (
+                    (
                         CallExpr {
                             span: DUMMY_SP,
                             callee: set,
@@ -652,12 +682,12 @@ impl<'a> PrivateAccessVisitor<'a> {
                         }
                         .make_member(quote_ident!("value")),
                         Some(*obj),
-                    );
+                    )
                 }
                 PrivateAccessType::Update => {
                     let set = helper!(class_private_field_update, "classPrivateFieldUpdate");
 
-                    return (
+                    (
                         CallExpr {
                             span: DUMMY_SP,
                             callee: set,
@@ -667,87 +697,113 @@ impl<'a> PrivateAccessVisitor<'a> {
                         }
                         .make_member(quote_ident!("value")),
                         Some(*obj),
-                    );
+                    )
                 }
-                _ => {}
-            }
 
-            let get = if self.c.private_as_properties {
-                helper!(class_private_field_loose_base, "classPrivateFieldLooseBase")
-            } else if kind.is_method() {
-                helper!(class_private_method_get, "classPrivateMethodGet")
-            } else {
-                helper!(class_private_field_get, "classPrivateFieldGet")
-            };
-
-            match &*obj {
-                Expr::This(this) => (
-                    if kind.is_method() && !self.c.private_as_properties {
+                PrivateAccessType::Get if kind.is_writeonly() => {
+                    let helper = helper!(write_only_error, "writeOnlyError");
+                    let expr = Box::new(
                         CallExpr {
                             span: DUMMY_SP,
-                            callee: get,
-                            args: vec![obj.clone().as_arg(), ident.as_arg(), method_name.as_arg()],
-                            type_args: Default::default(),
+                            callee: helper,
+                            args: vec![format!("#{}", n.id.sym).as_arg()],
+                            type_args: None,
                         }
-                        .into()
-                    } else {
-                        CallExpr {
+                        .into(),
+                    );
+                    (
+                        SeqExpr {
                             span: DUMMY_SP,
-                            callee: get,
-                            args: vec![this.as_arg(), ident.as_arg()],
-
-                            type_args: Default::default(),
-                        }
-                        .into()
-                    },
-                    Some(Expr::This(*this)),
-                ),
-                _ => {
-                    let mut aliased = false;
-                    let var = obj_alias.unwrap_or_else(|| {
-                        let (var, a) = alias_if_required(&obj, "_ref");
-                        if a {
-                            aliased = true;
-                            self.vars.push(VarDeclarator {
-                                span: DUMMY_SP,
-                                name: var.clone().into(),
-                                init: None,
-                                definite: false,
-                            });
+                            exprs: vec![obj.clone(), expr],
                         }
-                        var
-                    });
+                        .into(),
+                        Some(*obj),
+                    )
+                }
 
-                    let first_arg = if is_alias_initialized {
-                        var.clone().as_arg()
-                    } else if aliased {
-                        AssignExpr {
-                            span: DUMMY_SP,
-                            left: PatOrExpr::Pat(var.clone().into()),
-                            op: op!("="),
-                            right: obj.take(),
-                        }
-                        .as_arg()
+                PrivateAccessType::Get => {
+                    let get = if self.c.private_as_properties {
+                        helper!(class_private_field_loose_base, "classPrivateFieldLooseBase")
+                    } else if kind.is_method() {
+                        helper!(class_private_method_get, "classPrivateMethodGet")
                     } else {
-                        var.clone().as_arg()
+                        helper!(class_private_field_get, "classPrivateFieldGet")
                     };
 
-                    let args = if kind.is_method() {
-                        vec![first_arg, ident.as_arg(), method_name.as_arg()]
-                    } else {
-                        vec![first_arg, ident.as_arg()]
-                    };
+                    match &*obj {
+                        Expr::This(this) => (
+                            if kind.is_method() && !self.c.private_as_properties {
+                                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()
+                            },
+                            Some(Expr::This(*this)),
+                        ),
+                        _ => {
+                            let mut aliased = false;
+                            let var = obj_alias.unwrap_or_else(|| {
+                                let (var, a) = alias_if_required(&obj, "_ref");
+                                if a {
+                                    aliased = true;
+                                    self.vars.push(VarDeclarator {
+                                        span: DUMMY_SP,
+                                        name: var.clone().into(),
+                                        init: None,
+                                        definite: false,
+                                    });
+                                }
+                                var
+                            });
 
-                    (
-                        CallExpr {
-                            span: DUMMY_SP,
-                            callee: get,
-                            args,
-                            type_args: Default::default(),
+                            let first_arg = if is_alias_initialized {
+                                var.clone().as_arg()
+                            } else if aliased {
+                                AssignExpr {
+                                    span: DUMMY_SP,
+                                    left: PatOrExpr::Pat(var.clone().into()),
+                                    op: op!("="),
+                                    right: obj.take(),
+                                }
+                                .as_arg()
+                            } else {
+                                var.clone().as_arg()
+                            };
+
+                            let args = if kind.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,
+                                    type_args: Default::default(),
+                                }
+                                .into(),
+                                Some(Expr::Ident(var)),
+                            )
                         }
-                        .into(),
-                        Some(Expr::Ident(var)),
-                    )
+                    }
                 }
             }
         }
diff --git a/packages/swc-helpers/src/_class_apply_descriptor_update.mjs b/packages/swc-helpers/src/_class_apply_descriptor_update.mjs
index 67385d05af..082d460e80 100644
--- a/packages/swc-helpers/src/_class_apply_descriptor_update.mjs
+++ b/packages/swc-helpers/src/_class_apply_descriptor_update.mjs
@@ -1,5 +1,8 @@
 export default function _classApplyDescriptorUpdate(receiver, descriptor) {
     if (descriptor.set) {
+        if (!descriptor.get) {
+            throw new TypeError("attempted to read set only private field");
+        }
         if (!("__destrWrapper" in descriptor)) {
             descriptor.__destrWrapper = {
                 set value(v) {
diff --git a/packages/swc-helpers/src/_read_only_error.mjs b/packages/swc-helpers/src/_read_only_error.mjs
index b7ab3308b7..f98941b69c 100644
--- a/packages/swc-helpers/src/_read_only_error.mjs
+++ b/packages/swc-helpers/src/_read_only_error.mjs
@@ -1,3 +1,3 @@
 export default function _readOnlyError(name) {
-  throw new Error("\"" + name + "\" is read-only");
+  throw new TypeError("\"" + name + "\" is read-only");
 }
diff --git a/packages/swc-helpers/src/_write_only_error.mjs b/packages/swc-helpers/src/_write_only_error.mjs
new file mode 100644
index 0000000000..6e5c8927e6
--- /dev/null
+++ b/packages/swc-helpers/src/_write_only_error.mjs
@@ -0,0 +1,3 @@
+export default function _writeOnlyError(name) {
+  throw new TypeError("\"" + name + "\" is write-only");
+}
diff --git a/packages/swc-helpers/src/index.mjs b/packages/swc-helpers/src/index.mjs
index 3849211575..53c46d5bb7 100644
--- a/packages/swc-helpers/src/index.mjs
+++ b/packages/swc-helpers/src/index.mjs
@@ -78,4 +78,5 @@ export { default as typeOf } from './_type_of.mjs';
 export { default as unsupportedIterableToArray } from './_unsupported_iterable_to_array.mjs';
 export { default as wrapAsyncGenerator } from './_wrap_async_generator.mjs';
 export { default as wrapNativeSuper } from './_wrap_native_super.mjs';
+export { default as writeOnlyError } from './_write_only_error.mjs';
 export { __decorate, __generator, __metadata, __param, __values } from 'tslib'
\ No newline at end of file


Test patch:
diff --git a/crates/swc/tests/babel-exec/packages/babel-plugin-proposal-private-methods/test/fixtures/accessors/.get-only-setter/exec.js b/crates/swc/tests/babel-exec/packages/babel-plugin-proposal-private-methods/test/fixtures/accessors/get-only-setter/exec.js
similarity index 100%
rename from crates/swc/tests/babel-exec/packages/babel-plugin-proposal-private-methods/test/fixtures/accessors/.get-only-setter/exec.js
rename to crates/swc/tests/babel-exec/packages/babel-plugin-proposal-private-methods/test/fixtures/accessors/get-only-setter/exec.js
diff --git a/crates/swc/tests/babel-exec/packages/babel-plugin-proposal-private-methods/test/fixtures/accessors/.reassignment/exec.js b/crates/swc/tests/babel-exec/packages/babel-plugin-proposal-private-methods/test/fixtures/accessors/reassignment/exec.js
similarity index 100%
rename from crates/swc/tests/babel-exec/packages/babel-plugin-proposal-private-methods/test/fixtures/accessors/.reassignment/exec.js
rename to crates/swc/tests/babel-exec/packages/babel-plugin-proposal-private-methods/test/fixtures/accessors/reassignment/exec.js
diff --git a/crates/swc/tests/babel-exec/packages/babel-plugin-proposal-private-methods/test/fixtures/accessors/.set-only-getter/exec.js b/crates/swc/tests/babel-exec/packages/babel-plugin-proposal-private-methods/test/fixtures/accessors/set-only-getter/exec.js
similarity index 100%
rename from crates/swc/tests/babel-exec/packages/babel-plugin-proposal-private-methods/test/fixtures/accessors/.set-only-getter/exec.js
rename to crates/swc/tests/babel-exec/packages/babel-plugin-proposal-private-methods/test/fixtures/accessors/set-only-getter/exec.js
diff --git a/crates/swc/tests/exec.rs b/crates/swc/tests/exec.rs
index ea38d5da0b..44713de26d 100644
--- a/crates/swc/tests/exec.rs
+++ b/crates/swc/tests/exec.rs
@@ -57,7 +57,7 @@ fn init_helpers() -> Arc<PathBuf> {
         let helper_dir = project_root.join("packages").join("swc-helpers");
 
         let yarn = find_executable("yarn").expect("failed to find yarn");
-        let npm = find_executable("npm").expect("failed to find yarn");
+        let npm = find_executable("npm").expect("failed to find npm");
         {
             let mut cmd = if cfg!(target_os = "windows") {
                 let mut c = Command::new("cmd");
diff --git a/crates/swc/tests/tsc-references/privateNameAccessors.1.normal.js b/crates/swc/tests/tsc-references/privateNameAccessors.1.normal.js
index 07cf90c781..ccf844eb75 100644
--- a/crates/swc/tests/tsc-references/privateNameAccessors.1.normal.js
+++ b/crates/swc/tests/tsc-references/privateNameAccessors.1.normal.js
@@ -2,6 +2,7 @@
 import _class_private_field_get from "@swc/helpers/src/_class_private_field_get.mjs";
 import _class_private_field_init from "@swc/helpers/src/_class_private_field_init.mjs";
 import _class_private_field_set from "@swc/helpers/src/_class_private_field_set.mjs";
+import _read_only_error from "@swc/helpers/src/_read_only_error.mjs";
 var _prop = /*#__PURE__*/ new WeakMap(), _roProp = /*#__PURE__*/ new WeakMap();
 class A1 {
     constructor(name){
@@ -14,7 +15,7 @@ class A1 {
             set: void 0
         });
         _class_private_field_set(this, _prop, "");
-        _class_private_field_set(this, _roProp, ""); // Error
+        this, _read_only_error("#roProp"); // Error
         console.log(_class_private_field_get(this, _prop));
         console.log(_class_private_field_get(this, _roProp));
     }
diff --git a/crates/swc/tests/tsc-references/privateNameMethodAssignment.1.normal.js b/crates/swc/tests/tsc-references/privateNameMethodAssignment.1.normal.js
index 03a7577520..d1c5e1813c 100644
--- a/crates/swc/tests/tsc-references/privateNameMethodAssignment.1.normal.js
+++ b/crates/swc/tests/tsc-references/privateNameMethodAssignment.1.normal.js
@@ -1,18 +1,18 @@
 //// [privateNameMethodAssignment.ts]
-import _class_private_field_set from "@swc/helpers/src/_class_private_field_set.mjs";
 import _class_private_field_update from "@swc/helpers/src/_class_private_field_update.mjs";
 import _class_private_method_get from "@swc/helpers/src/_class_private_method_get.mjs";
 import _class_private_method_init from "@swc/helpers/src/_class_private_method_init.mjs";
+import _read_only_error from "@swc/helpers/src/_read_only_error.mjs";
 import _class_private_field_destructure from "@swc/helpers/src/_class_private_field_destructure.mjs";
 var _method = /*#__PURE__*/ new WeakSet();
 class A3 {
     constructor(a, b){
         _class_private_method_init(this, _method);
-        _class_private_field_set(this, _method, ()=>{} // Error, not writable 
-        );
-        _class_private_field_set(a, _method, ()=>{}); // Error, not writable 
-        _class_private_field_set(b, _method, ()=>{} //Error, not writable 
-        );
+        this, _read_only_error("#method") // Error, not writable 
+        ;
+        a, _read_only_error("#method"); // Error, not writable 
+        b, _read_only_error("#method") //Error, not writable 
+        ;
         ({ x: _class_private_field_destructure(this, _method).value  } = {
             x: ()=>{}
         }); //Error, not writable 
diff --git a/crates/swc/tests/tsc-references/privateNameReadonly.1.normal.js b/crates/swc/tests/tsc-references/privateNameReadonly.1.normal.js
index 51a52d40cf..fe0e31874c 100644
--- a/crates/swc/tests/tsc-references/privateNameReadonly.1.normal.js
+++ b/crates/swc/tests/tsc-references/privateNameReadonly.1.normal.js
@@ -1,10 +1,10 @@
 //// [privateNameReadonly.ts]
-import _class_private_field_set from "@swc/helpers/src/_class_private_field_set.mjs";
 import _class_private_method_init from "@swc/helpers/src/_class_private_method_init.mjs";
+import _read_only_error from "@swc/helpers/src/_read_only_error.mjs";
 var _bar, _class;
 const C = (_bar = /*#__PURE__*/ new WeakSet(), _class = class {
     foo() {
-        _class_private_field_set(this, _bar, console.log("should log this then throw"));
+        this, console.log("should log this then throw"), _read_only_error("#bar");
     }
     constructor(){
         _class_private_method_init(this, _bar);
diff --git a/crates/swc/tests/tsc-references/privateNameReadonly.2.minified.js b/crates/swc/tests/tsc-references/privateNameReadonly.2.minified.js
index 8daf07be51..c785fbc02f 100644
--- a/crates/swc/tests/tsc-references/privateNameReadonly.2.minified.js
+++ b/crates/swc/tests/tsc-references/privateNameReadonly.2.minified.js
@@ -1,10 +1,10 @@
 //// [privateNameReadonly.ts]
 var _bar;
-import _class_private_field_set from "@swc/helpers/src/_class_private_field_set.mjs";
 import _class_private_method_init from "@swc/helpers/src/_class_private_method_init.mjs";
+import _read_only_error from "@swc/helpers/src/_read_only_error.mjs";
 let C = (_bar = new WeakSet(), class {
     foo() {
-        _class_private_field_set(this, _bar, console.log("should log this then throw"));
+        console.log("should log this then throw"), _read_only_error("#bar");
     }
     constructor(){
         _class_private_method_init(this, _bar);
diff --git a/crates/swc/tests/tsc-references/privateNameSetterNoGetter.1.normal.js b/crates/swc/tests/tsc-references/privateNameSetterNoGetter.1.normal.js
index 5c271e349c..0256bb2e1b 100644
--- a/crates/swc/tests/tsc-references/privateNameSetterNoGetter.1.normal.js
+++ b/crates/swc/tests/tsc-references/privateNameSetterNoGetter.1.normal.js
@@ -1,11 +1,11 @@
 //// [privateNameSetterNoGetter.ts]
-import _class_private_field_get from "@swc/helpers/src/_class_private_field_get.mjs";
 import _class_private_field_init from "@swc/helpers/src/_class_private_field_init.mjs";
 import _class_private_field_set from "@swc/helpers/src/_class_private_field_set.mjs";
+import _write_only_error from "@swc/helpers/src/_write_only_error.mjs";
 var _x, _class;
 const C = (_x = /*#__PURE__*/ new WeakMap(), _class = class {
     m() {
-        _class_private_field_set(this, _x, _class_private_field_get(this, _x) + 2); // Error
+        _class_private_field_set(this, _x, (this, _write_only_error("#x")) + 2); // Error
     }
     constructor(){
         _class_private_field_init(this, _x, {
diff --git a/crates/swc/tests/tsc-references/privateNameSetterNoGetter.2.minified.js b/crates/swc/tests/tsc-references/privateNameSetterNoGetter.2.minified.js
index 0defd0f8ef..e792162bda 100644
--- a/crates/swc/tests/tsc-references/privateNameSetterNoGetter.2.minified.js
+++ b/crates/swc/tests/tsc-references/privateNameSetterNoGetter.2.minified.js
@@ -1,11 +1,11 @@
 //// [privateNameSetterNoGetter.ts]
 var _x;
-import _class_private_field_get from "@swc/helpers/src/_class_private_field_get.mjs";
 import _class_private_field_init from "@swc/helpers/src/_class_private_field_init.mjs";
 import _class_private_field_set from "@swc/helpers/src/_class_private_field_set.mjs";
+import _write_only_error from "@swc/helpers/src/_write_only_error.mjs";
 let C = (_x = new WeakMap(), class {
     m() {
-        _class_private_field_set(this, _x, _class_private_field_get(this, _x) + 2);
+        _class_private_field_set(this, _x, _write_only_error("#x") + 2);
     }
     constructor(){
         _class_private_field_init(this, _x, {
diff --git a/crates/swc/tests/tsc-references/privateWriteOnlyAccessorRead.1.normal.js b/crates/swc/tests/tsc-references/privateWriteOnlyAccessorRead.1.normal.js
index 42587975c4..a4572599ed 100644
--- a/crates/swc/tests/tsc-references/privateWriteOnlyAccessorRead.1.normal.js
+++ b/crates/swc/tests/tsc-references/privateWriteOnlyAccessorRead.1.normal.js
@@ -1,8 +1,8 @@
 //// [privateWriteOnlyAccessorRead.ts]
-import _class_private_field_get from "@swc/helpers/src/_class_private_field_get.mjs";
 import _class_private_field_init from "@swc/helpers/src/_class_private_field_init.mjs";
 import _class_private_field_set from "@swc/helpers/src/_class_private_field_set.mjs";
 import _extends from "@swc/helpers/src/_extends.mjs";
+import _write_only_error from "@swc/helpers/src/_write_only_error.mjs";
 import _class_private_field_destructure from "@swc/helpers/src/_class_private_field_destructure.mjs";
 var _value = /*#__PURE__*/ new WeakMap(), _valueRest = /*#__PURE__*/ new WeakMap(), _valueOne = /*#__PURE__*/ new WeakMap(), _valueCompound = /*#__PURE__*/ new WeakMap();
 class Test {
@@ -10,14 +10,14 @@ class Test {
         const foo = {
             bar: 1
         };
-        console.log(_class_private_field_get(this, _value)); // error
+        console.log((this, _write_only_error("#value"))); // error
         _class_private_field_set(this, _value, {
             foo
         }); // ok
         _class_private_field_set(this, _value, {
             foo
         }); // ok
-        _class_private_field_get(this, _value).foo = foo; // error
+        (this, _write_only_error("#value")).foo = foo; // error
         ({ o: _class_private_field_destructure(this, _value).value  } = {
             o: {
                 foo
@@ -27,15 +27,15 @@ class Test {
         _tmp = {
             foo
         }, _class_private_field_destructure(this, _value).value = _extends({}, _tmp), _tmp; //ok
-        ({ foo: _class_private_field_get(this, _value).foo  } = {
+        ({ foo: (this, _write_only_error("#value")).foo  } = {
             foo
         }); //error
         var _tmp1;
         _tmp1 = {
             foo
-        }, _class_private_field_get(this, _value).foo = _extends({}, _tmp1.foo), ({ foo: {}  } = _tmp1), _tmp1; //error
+        }, (this, _write_only_error("#value")).foo = _extends({}, _tmp1.foo), ({ foo: {}  } = _tmp1), _tmp1; //error
         let r = {
-            o: _class_private_field_get(this, _value)
+            o: (this, _write_only_error("#value"))
         }; //error
         [_class_private_field_destructure(this, _valueOne).value, ..._class_private_field_destructure(this, _valueRest).value] = [
             1,
@@ -43,10 +43,10 @@ class Test {
             3
         ];
         let arr = [
-            _class_private_field_get(this, _valueOne),
-            ..._class_private_field_get(this, _valueRest)
+            (this, _write_only_error("#valueOne")),
+            ...(this, _write_only_error("#valueRest"))
         ];
-        _class_private_field_set(this, _valueCompound, _class_private_field_get(this, _valueCompound) + 3);
+        _class_private_field_set(this, _valueCompound, (this, _write_only_error("#valueCompound")) + 3);
     }
     constructor(){
         _class_private_field_init(this, _value, {
diff --git a/crates/swc/tests/tsc-references/privateWriteOnlyAccessorRead.2.minified.js b/crates/swc/tests/tsc-references/privateWriteOnlyAccessorRead.2.minified.js
index 304196d3bd..ca07dfdb8b 100644
--- a/crates/swc/tests/tsc-references/privateWriteOnlyAccessorRead.2.minified.js
+++ b/crates/swc/tests/tsc-references/privateWriteOnlyAccessorRead.2.minified.js
@@ -1,8 +1,8 @@
 //// [privateWriteOnlyAccessorRead.ts]
-import _class_private_field_get from "@swc/helpers/src/_class_private_field_get.mjs";
 import _class_private_field_init from "@swc/helpers/src/_class_private_field_init.mjs";
 import _class_private_field_set from "@swc/helpers/src/_class_private_field_set.mjs";
 import _extends from "@swc/helpers/src/_extends.mjs";
+import _write_only_error from "@swc/helpers/src/_write_only_error.mjs";
 import _class_private_field_destructure from "@swc/helpers/src/_class_private_field_destructure.mjs";
 var _value = new WeakMap(), _valueRest = new WeakMap(), _valueOne = new WeakMap(), _valueCompound = new WeakMap();
 function set_value(v) {}
@@ -15,28 +15,28 @@ new class {
         let foo = {
             bar: 1
         };
-        console.log(_class_private_field_get(this, _value)), _class_private_field_set(this, _value, {
+        console.log(_write_only_error("#value")), _class_private_field_set(this, _value, {
             foo
         }), _class_private_field_set(this, _value, {
             foo
-        }), _class_private_field_get(this, _value).foo = foo, ({ o: _class_private_field_destructure(this, _value).value  } = {
+        }), _write_only_error("#value").foo = foo, ({ o: _class_private_field_destructure(this, _value).value  } = {
             o: {
                 foo
             }
         }), _class_private_field_destructure(this, _value).value = _extends({}, {
             foo
-        }), ({ foo: _class_private_field_get(this, _value).foo  } = {
+        }), ({ foo: _write_only_error("#value").foo  } = {
             foo
         }), _tmp = {
             foo
-        }, _class_private_field_get(this, _value).foo = _extends({}, _tmp.foo), _class_private_field_get(this, _value), [_class_private_field_destructure(this, _valueOne).value, ..._class_private_field_destructure(this, _valueRest).value] = [
+        }, _write_only_error("#value").foo = _extends({}, _tmp.foo), _write_only_error("#value"), [_class_private_field_destructure(this, _valueOne).value, ..._class_private_field_destructure(this, _valueRest).value] = [
             1,
             2,
             3
         ], [
-            _class_private_field_get(this, _valueOne),
-            ..._class_private_field_get(this, _valueRest)
-        ], _class_private_field_set(this, _valueCompound, _class_private_field_get(this, _valueCompound) + 3);
+            _write_only_error("#valueOne"),
+            ..._write_only_error("#valueRest")
+        ], _class_private_field_set(this, _valueCompound, _write_only_error("#valueCompound") + 3);
     }
     constructor(){
         _class_private_field_init(this, _value, {
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 15a508729e..9afd597dff 100644
--- a/crates/swc_ecma_transforms_compat/tests/es2022_class_properties.rs
+++ b/crates/swc_ecma_transforms_compat/tests/es2022_class_properties.rs
@@ -6549,6 +6549,100 @@ function set_privateFieldValue(newValue) {
 "#
 );
 
+test!(
+    syntax(),
+    |t| class_properties(Some(t.comments.clone()), Default::default()),
+    set_only_getter,
+    r#"
+class Cl {
+  #privateField = 0;
+  counter = 0;
+
+  get #privateFieldValue() {
+    return this.#privateField;
+  }
+
+  get self() {
+    this.counter++;
+    return this;
+  }
+
+  constructor() {
+    this.self.#privateFieldValue = 1;
+    ([this.self.#privateFieldValue] = [1]);
+  }
+}
+
+const cl = new Cl();
+"#,
+    r##"
+var _privateField = new WeakMap(), _privateFieldValue = new WeakMap();
+class Cl {
+    get self() {
+        this.counter++;
+        return this;
+    }
+    constructor(){
+        _classPrivateFieldInit(this, _privateFieldValue, {
+            get: get_privateFieldValue,
+            set: void 0
+        });
+        _classPrivateFieldInit(this, _privateField, {
+            writable: true,
+            value: 0
+        });
+        _defineProperty(this, "counter", 0);
+        this.self, _readOnlyError("#privateFieldValue");
+        [_classPrivateFieldDestructureSet(this.self, _privateFieldValue).value] = [
+            1
+        ];
+    }
+}
+function get_privateFieldValue() {
+    return _classPrivateFieldGet(this, _privateField);
+}
+const cl = new Cl();
+"##
+);
+
+test!(
+    syntax(),
+    |t| class_properties(Some(t.comments.clone()), Default::default()),
+    get_only_setter,
+    r#"
+class Cl {
+  #privateField = 0;
+
+  set #privateFieldValue(newValue) {
+    this.#privateField = newValue;
+  }
+
+  constructor() {
+    this.publicField = this.#privateFieldValue;
+  }
+}
+"#,
+    r##"
+var _privateField = new WeakMap(), _privateFieldValue = new WeakMap();
+class Cl {
+    constructor(){
+        _classPrivateFieldInit(this, _privateFieldValue, {
+            get: void 0,
+            set: set_privateFieldValue
+        });
+        _classPrivateFieldInit(this, _privateField, {
+            writable: true,
+            value: 0
+        });
+        this.publicField = (this, _writeOnlyError("#privateFieldValue"));
+    }
+}
+function set_privateFieldValue(newValue) {
+    _classPrivateFieldSet(this, _privateField, newValue);
+}
+"##
+);
+
 #[testing::fixture("tests/classes/**/exec.js")]
 fn exec(input: PathBuf) {
     let src = read_to_string(&input).unwrap();