feat(transformer/class-properties): transform static/instance accessor methods (#8132)
This PR supports transforming private `getter` or `setter` methods, and the output is a little different from Babel's output, For example:
Input:
```js
class Cls {
#prop = 0;
get #accessor() {
return this.#prop;
}
set #accessor(v) {
console.log(arguments);
this.#prop = v;
}
constructor() {
console.log(this.#accessor)
[this.#accessor] = [1];
}
}
```
[Babel's output](https://babeljs.io/repl#?browsers=defaults%2C%20not%20ie%2011%2C%20not%20ie_mob%2011&build=&builtIns=false&corejs=3.21&spec=false&loose=false&code_lz=MYGwhgzhAEDCIwN4ChrQMQAcBOB7T0AvNAAwDcyq0A5gKYAuGYwwtUu2AFAJTQpppsDAK7YAdtHoALAJYQAdFjyYKaAL5UIDJizYQOnAG69-A4LjH6QteSFzVOYbNWEBbWmPoRuqgdLmKOPhE0Ia-GlTmlvTYwsD0BiZUaFFWNnYO_grozKzs2NzJ0ADaWYq5ehwAuiHFAIxV4cgaQA&debug=false&forceAllTransforms=false&modules=false&shippedProposals=false&evaluate=false&fileSize=false&timeTravel=false&sourceType=script&lineWrap=true&presets=&prettier=false&targets=&version=7.26.4&externalPlugins=%40babel%2Fplugin-transform-class-properties%407.25.9%2C%40babel%2Fplugin-transform-private-methods%407.25.9%2C%40babel%2Fplugin-external-helpers%407.25.9&assumptions=%7B%7D):
```js
var _prop = new WeakMap();
var _Cls_brand = new WeakSet();
class Cls {
constructor() {
babelHelpers.classPrivateMethodInitSpec(this, _Cls_brand);
babelHelpers.classPrivateFieldInitSpec(this, _prop, 0);
console.log(babelHelpers.classPrivateGetter(_Cls_brand, this, _get_accessor));
[babelHelpers.classPrivateGetter(_Cls_brand, this, _get_accessor)] = [1];
}
}
function _get_accessor(_this) {
return babelHelpers.classPrivateFieldGet(_prop, _this);
}
function _set_accessor(_this2, v) {
var _arguments = [].slice.call(arguments, 1);
console.log(_arguments);
babelHelpers.classPrivateFieldSet(_prop, _this2, v);
}
```
Oxc's output:
```js
var _prop = new WeakMap();
var _Cls_brand = new WeakSet();
class Cls {
constructor() {
babelHelpers.classPrivateMethodInitSpec(this, _Cls_brand);
babelHelpers.classPrivateFieldInitSpec(this, _prop, 0);
console.log(_get_accessor.call(babelHelpers.assertClassBrand(_Cls_brand, this)));
[babelHelpers.toSetter(_set_accessor.bind(babelHelpers.assertClassBrand(_Cls_brand, this)), [])._] = [1];
}
}
function _get_accessor() {
return babelHelpers.classPrivateFieldGet2(_prop, this);
}
function _set_accessor(v) {
console.log(arguments);
babelHelpers.classPrivateFieldSet2(_prop, this, v);
}
```
### Main difference
```diff
// Babel
+ console.log(babelHelpers.classPrivateGetter(_Cls_brand, this, _get_accessor));
+ [babelHelpers.classPrivateGetter(_Cls_brand, this, _get_accessor)] = [1];
+ function _get_accessor(_this) {
+ return babelHelpers.classPrivateFieldGet(_prop, _this);
+ }
+ function _set_accessor(_this2, v) {
+ var _arguments = [].slice.call(arguments, 1);
+ console.log(_arguments);
+ babelHelpers.classPrivateFieldSet(_prop, _this2, v);
+ }
// Oxc
- console.log(_get_accessor.call(babelHelpers.assertClassBrand(_Cls_brand, this)));- -
- [babelHelpers.toSetter(_set_accessor.bind(babelHelpers.assertClassBrand(_Cls_brand, this)), [])._] = [1];
- function _get_accessor() {
- return babelHelpers.classPrivateFieldGet2(_prop, this);
- }
- function _set_accessor(v) {
- console.log(arguments);
- babelHelpers.classPrivateFieldSet2(_prop, this, v);
- }
```
From the main differences, we can see that Babel handles `getter` and `setter` methods using `classPrivateGetter` and `classPrivateSetter` helper functions, which causes all use `this` and `arguments` needs to rewrite to use a temp var instead in `getter` and `setter` methods. This is unnecessary and is not an efficient transformation for us.
Instead, I adapt binding a `this` instead of passing in `this`, this way we don't need to rewrite anything. We can't control the `helper` library for now, so I just transformed the AST to bind `this`, in the future, we can create a helper function to do the same thing.