diff --git a/packages/babel-helper-member-expression-to-functions/src/index.js b/packages/babel-helper-member-expression-to-functions/src/index.js index 4ea964fdb3bf..62d844b9f177 100644 --- a/packages/babel-helper-member-expression-to-functions/src/index.js +++ b/packages/babel-helper-member-expression-to-functions/src/index.js @@ -315,29 +315,42 @@ const handle = { // MEMBER = VALUE -> _set(MEMBER, VALUE) // MEMBER += VALUE -> _set(MEMBER, _get(MEMBER) + VALUE) + // MEMBER ??= VALUE -> _get(MEMBER) ?? _set(MEMBER, VALUE) if (parentPath.isAssignmentExpression({ left: node })) { if (this.simpleSet) { member.replaceWith(this.simpleSet(member)); return; } - const { operator, right } = parent; - let value = right; + const { operator, right: value } = parent; - if (operator !== "=") { - // Give the state handler a chance to memoise the member, since we'll - // reference it twice. The second access (the set) should do the memo - // assignment. - this.memoise(member, 2); - - value = t.binaryExpression( - operator.slice(0, -1), - this.get(member), - value, - ); + if (operator === "=") { + parentPath.replaceWith(this.set(member, value)); + } else { + const operatorTrunc = operator.slice(0, -1); + if (t.LOGICAL_OPERATORS.includes(operatorTrunc)) { + // Give the state handler a chance to memoise the member, since we'll + // reference it twice. The first access (the get) should do the memo + // assignment. + this.memoise(member, 1); + parentPath.replaceWith( + t.logicalExpression( + operatorTrunc, + this.get(member), + this.set(member, value), + ), + ); + } else { + // Here, the second access (the set) is evaluated first. + this.memoise(member, 2); + parentPath.replaceWith( + this.set( + member, + t.binaryExpression(operatorTrunc, this.get(member), value), + ), + ); + } } - - parentPath.replaceWith(this.set(member, value)); return; } diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/logical-assignment/input.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/logical-assignment/input.js new file mode 100644 index 000000000000..ea0daffff766 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/logical-assignment/input.js @@ -0,0 +1,17 @@ +class Foo { + #nullish = 0; + #and = 0; + #or = 0; + + self() { + return this; + } + + test() { + this.#nullish ??= 42; + this.#and &&= 0; + this.#or ||= 0; + + this.self().#nullish ??= 42; + } +} diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/logical-assignment/options.json b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/logical-assignment/options.json new file mode 100644 index 000000000000..25a0eb9883c4 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/logical-assignment/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + ["proposal-logical-assignment-operators", { "loose": true }], + ["proposal-class-properties", { "loose": true }] + ] +} diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/logical-assignment/output.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/logical-assignment/output.js new file mode 100644 index 000000000000..e7d19cbdcdf6 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/logical-assignment/output.js @@ -0,0 +1,42 @@ +function _classPrivateFieldLooseBase(receiver, privateKey) { if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) { throw new TypeError("attempted to use private field on non-instance"); } return receiver; } + +var id = 0; + +function _classPrivateFieldLooseKey(name) { return "__private_" + id++ + "_" + name; } + +var _nullish = _classPrivateFieldLooseKey("nullish"); + +var _and = _classPrivateFieldLooseKey("and"); + +var _or = _classPrivateFieldLooseKey("or"); + +class Foo { + constructor() { + Object.defineProperty(this, _nullish, { + writable: true, + value: 0 + }); + Object.defineProperty(this, _and, { + writable: true, + value: 0 + }); + Object.defineProperty(this, _or, { + writable: true, + value: 0 + }); + } + + self() { + return this; + } + + test() { + var _classPrivateFieldLoo, _classPrivateFieldLoo2, _classPrivateFieldLoo3, _classPrivateFieldLoo4; + + (_classPrivateFieldLoo = _classPrivateFieldLooseBase(this, _nullish))[_nullish] ?? (_classPrivateFieldLoo[_nullish] = 42); + (_classPrivateFieldLoo2 = _classPrivateFieldLooseBase(this, _and))[_and] && (_classPrivateFieldLoo2[_and] = 0); + (_classPrivateFieldLoo3 = _classPrivateFieldLooseBase(this, _or))[_or] || (_classPrivateFieldLoo3[_or] = 0); + (_classPrivateFieldLoo4 = _classPrivateFieldLooseBase(this.self(), _nullish))[_nullish] ?? (_classPrivateFieldLoo4[_nullish] = 42); + } + +} diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private/logical-assignment/input.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/logical-assignment/input.js new file mode 100644 index 000000000000..ea0daffff766 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/logical-assignment/input.js @@ -0,0 +1,17 @@ +class Foo { + #nullish = 0; + #and = 0; + #or = 0; + + self() { + return this; + } + + test() { + this.#nullish ??= 42; + this.#and &&= 0; + this.#or ||= 0; + + this.self().#nullish ??= 42; + } +} diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private/logical-assignment/options.json b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/logical-assignment/options.json new file mode 100644 index 000000000000..26f2ddc95852 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/logical-assignment/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + "proposal-logical-assignment-operators", + "proposal-class-properties" + ] +} diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private/logical-assignment/output.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/logical-assignment/output.js new file mode 100644 index 000000000000..0ae51ebc8087 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/logical-assignment/output.js @@ -0,0 +1,42 @@ +function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = privateMap.get(receiver); if (!descriptor) { throw new TypeError("attempted to set private field on non-instance"); } 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; } return value; } + +function _classPrivateFieldGet(receiver, privateMap) { var descriptor = privateMap.get(receiver); if (!descriptor) { throw new TypeError("attempted to get private field on non-instance"); } if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; } + +var _nullish = new WeakMap(); + +var _and = new WeakMap(); + +var _or = new WeakMap(); + +class Foo { + constructor() { + _nullish.set(this, { + writable: true, + value: 0 + }); + + _and.set(this, { + writable: true, + value: 0 + }); + + _or.set(this, { + writable: true, + value: 0 + }); + } + + self() { + return this; + } + + test() { + var _this$self; + + _classPrivateFieldGet(this, _nullish) ?? _classPrivateFieldSet(this, _nullish, 42); + _classPrivateFieldGet(this, _and) && _classPrivateFieldSet(this, _and, 0); + _classPrivateFieldGet(this, _or) || _classPrivateFieldSet(this, _or, 0); + _classPrivateFieldGet(_this$self = this.self(), _nullish) ?? _classPrivateFieldSet(_this$self, _nullish, 42); + } + +} diff --git a/packages/babel-plugin-proposal-logical-assignment-operators/src/index.js b/packages/babel-plugin-proposal-logical-assignment-operators/src/index.js index 813dcd761f7c..65b0f3caeb2e 100644 --- a/packages/babel-plugin-proposal-logical-assignment-operators/src/index.js +++ b/packages/babel-plugin-proposal-logical-assignment-operators/src/index.js @@ -13,7 +13,8 @@ export default declare(api => { AssignmentExpression(path) { const { node, scope } = path; const { operator, left, right } = node; - if (operator !== "||=" && operator !== "&&=" && operator !== "??=") { + const operatorTrunc = operator.slice(0, -1); + if (!t.LOGICAL_OPERATORS.includes(operatorTrunc)) { return; } @@ -41,7 +42,7 @@ export default declare(api => { path.replaceWith( t.logicalExpression( - operator.slice(0, -1), + operatorTrunc, lhs, t.assignmentExpression("=", left, right), ),