mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(transformer/class-properties): apply a compound assignment to a static private field once (#28606)
If the target is `es2021` or lower, a compound assignment to a static
private field applies the operator twice, unless the left side of
`.#prop` is the class name. For example, `this.#count += k` is affected,
but `Counter.#count += k` is not.
```js
class Counter {
static #count = 10;
static add(k) {
this.#count += k;
return this.#count;
}
}
console.log(Counter.add(1)); // 11
```
The transformed code prints `21`, because it keeps `+=` and also adds
the old value on the right side:
```js
// before
_count._ += _assertClassBrand(Counter, this, _assertClassBrand(Counter, this, _count)._ + k);
// after
_count._ = _assertClassBrand(Counter, this, _assertClassBrand(Counter, this, _count)._ + k);
```
`Counter.#count += k` uses a separate branch that sets the operator to
`=`. The branch for `this.#count += k` did not set the operator. This PR
adds that line.
The new fixture `oxc/private-static-compound-assignment` covers `this`,
an object from a function call, and an object without the private field.
Its `exec.js` fails on main with `expected 21 to be 11`.
---
AI assistance was used for this change. I reviewed and tested it.
This commit is contained in:
6 files changed
+48
-3
No files matched your search
@@ -708,6 +708,7 @@ impl<'a> ClassProperties<'a> {
|
||||
let value =
|
||||
Expression::new_binary_expression(SPAN, get_expr, operator, value, ctx);
|
||||
// `_assertClassBrand(Class, object, _assertClassBrand(Class, object, _prop)._ + value)`
|
||||
assign_expr.operator = AssignmentOperator::Assign;
|
||||
assign_expr.right =
|
||||
self.create_assert_class_brand(class_ident2, object1, value, SPAN, ctx);
|
||||
} else if let Some(operator) = operator.to_logical_operator() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
commit: b835f0c6
|
||||
|
||||
Passed: 307/436
|
||||
Passed: 308/437
|
||||
|
||||
# All Passed:
|
||||
* babel-plugin-transform-class-static-block
|
||||
@@ -34,7 +34,7 @@ after transform: SymbolId(1) "C"
|
||||
rebuilt : SymbolId(3) "C"
|
||||
|
||||
|
||||
# babel-plugin-transform-class-properties (31/35)
|
||||
# babel-plugin-transform-class-properties (32/36)
|
||||
* private-field-resolve-to-method/input.js
|
||||
x Output mismatch
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ commit: b835f0c6
|
||||
|
||||
node: v26.12.1
|
||||
|
||||
Passed: 42 of 44 (95.45%)
|
||||
Passed: 43 of 45 (95.56%)
|
||||
|
||||
Failures:
|
||||
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
class Counter {
|
||||
static #count = 10;
|
||||
|
||||
static add(k) {
|
||||
return (this.#count += k);
|
||||
}
|
||||
|
||||
static subtract(getObject, k) {
|
||||
getObject().#count -= k;
|
||||
return this.#count;
|
||||
}
|
||||
|
||||
static addTo(obj) {
|
||||
obj.#count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let calls = 0;
|
||||
const getCounter = () => {
|
||||
calls++;
|
||||
return Counter;
|
||||
};
|
||||
|
||||
expect(Counter.add(1)).toBe(11);
|
||||
expect(Counter.subtract(getCounter, 2)).toBe(9);
|
||||
expect(calls).toBe(1);
|
||||
expect(() => Counter.addTo({})).toThrow(TypeError);
|
||||
expect(Counter.add(0)).toBe(9);
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
class Foo {
|
||||
static #prop = 1;
|
||||
|
||||
static test() {
|
||||
this.#prop += 1;
|
||||
getObj().#prop -= 2;
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
class Foo {
|
||||
static test() {
|
||||
var _getObj;
|
||||
_prop._ = babelHelpers.assertClassBrand(Foo, this, babelHelpers.assertClassBrand(Foo, this, _prop)._ + 1);
|
||||
_prop._ = babelHelpers.assertClassBrand(Foo, _getObj = getObj(), babelHelpers.assertClassBrand(Foo, _getObj, _prop)._ - 2);
|
||||
}
|
||||
}
|
||||
var _prop = { _: 1 };
|
||||
Reference in new issue
Block a user