diff --git a/packages/babel-helper-create-class-features-plugin/src/fields.js b/packages/babel-helper-create-class-features-plugin/src/fields.js index 76bc96b6d662..848b0033b675 100644 --- a/packages/babel-helper-create-class-features-plugin/src/fields.js +++ b/packages/babel-helper-create-class-features-plugin/src/fields.js @@ -241,6 +241,15 @@ const privateNameHandlerSpec = { ]); }, + boundGet(member) { + this.memoise(member, 1); + + return t.callExpression( + t.memberExpression(this.get(member), t.identifier("bind")), + [this.receiver(member)], + ); + }, + set(member, value) { const { classRef, privateNamesMap, file } = this; const { name } = member.node.property.id; @@ -323,6 +332,13 @@ const privateNameHandlerLoose = { }); }, + boundGet(member) { + return t.callExpression( + t.memberExpression(this.get(member), t.identifier("bind")), + [t.cloneNode(member.node.object)], + ); + }, + simpleSet(member) { return this.get(member); }, 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 67d3621debc6..1d8114a94d85 100644 --- a/packages/babel-helper-member-expression-to-functions/src/index.js +++ b/packages/babel-helper-member-expression-to-functions/src/index.js @@ -167,6 +167,9 @@ const handle = { const parentIsOptionalCall = parentPath.isOptionalCallExpression({ callee: node, }); + const isParenthesizedMemberCall = + parentPath.isCallExpression({ callee: node }) && + node.extra?.parenthesized; startingOptional.replaceWith(toNonOptional(startingOptional, baseRef)); if (parentIsOptionalCall) { if (parent.optional) { @@ -174,6 +177,9 @@ const handle = { } else { parentPath.replaceWith(this.call(member, parent.arguments)); } + } else if (isParenthesizedMemberCall) { + // `(a?.#b)()` to `(a == null ? void 0 : a.#b.bind(a))()` + member.replaceWith(this.boundGet(member)); } else { member.replaceWith(this.get(member)); } diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/parenthesized-optional-member-call-with-transform/exec.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/parenthesized-optional-member-call-with-transform/exec.js new file mode 100644 index 000000000000..ae7bf5d636e3 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/parenthesized-optional-member-call-with-transform/exec.js @@ -0,0 +1,48 @@ +class Foo { + static #x = 1; + + static self = Foo; + static #m = function() { return this.#x; }; + static getSelf() { return Foo } + + test() { + const o = { Foo: Foo }; + const fn = function () { + return o; + }; + + expect((Foo?.#m)()).toEqual(1); + expect((Foo?.#m)().toString).toEqual(1..toString); + expect((Foo?.#m)().toString()).toEqual('1'); + + expect((o?.Foo.#m)()).toEqual(1); + expect((o?.Foo.#m)().toString).toEqual(1..toString); + expect((o?.Foo.#m)().toString()).toEqual('1'); + + expect((((o.Foo?.self.getSelf)())?.#m)()).toEqual(1); + expect((((o.Foo.self?.getSelf)())?.#m)()).toEqual(1); + + expect((((fn()?.Foo?.self.getSelf)())?.#m)()).toEqual(1); + expect((((fn?.().Foo.self?.getSelf)())?.#m)()).toEqual(1); + } + + testNull() { + const o = null; + const fn = function () { + return { o }; + } + + expect(() => { (o?.Foo.#m)() }).toThrow(); + expect(() => { (o?.Foo.#m)().toString }).toThrow(); + expect(() => { (o?.Foo.#m)().toString() }).toThrow(); + + expect(() => { (((o.Foo?.self.getSelf)())?.#m)() }).toThrow(); + expect(() => { (((o.Foo.self?.getSelf)())?.#m)() }).toThrow(); + + expect(() => (((fn()?.Foo?.self.getSelf)())?.#m)()).toThrow(); + expect(() => (((fn?.().Foo.self?.getSelf)())?.#m)()).toThrow(); + } +} + +(new Foo).test(); +(new Foo).testNull(); diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/parenthesized-optional-member-call-with-transform/input.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/parenthesized-optional-member-call-with-transform/input.js new file mode 100644 index 000000000000..0d9a4d608b78 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/parenthesized-optional-member-call-with-transform/input.js @@ -0,0 +1,30 @@ +class Foo { + static #x = 1; + + static self = Foo; + static #m = function() { return this.#x; }; + static getSelf() { return Foo } + + test() { + const o = { Foo: Foo }; + const fn = function () { + return o; + }; + + (Foo?.#m)(); + (Foo?.#m)().toString; + (Foo?.#m)().toString(); + + (o?.Foo.#m)(); + (o?.Foo.#m)().toString; + (o?.Foo.#m)().toString(); + + (((o.Foo?.self.getSelf)())?.#m)(); + (((o.Foo.self?.getSelf)())?.#m)(); + + (((fn()?.Foo?.self.getSelf)())?.#m)(); + (((fn?.().Foo.self?.getSelf)())?.#m)(); + } +} + +(new Foo).test(); diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/parenthesized-optional-member-call-with-transform/options.json b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/parenthesized-optional-member-call-with-transform/options.json new file mode 100644 index 000000000000..14f4e86be551 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/parenthesized-optional-member-call-with-transform/options.json @@ -0,0 +1,7 @@ +{ + "plugins": [ + ["external-helpers", { "helperVersion": "7.100.0" }], + ["proposal-class-properties", { "loose": true }], + ["proposal-optional-chaining", { "loose": true }] + ] +} diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/parenthesized-optional-member-call-with-transform/output.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/parenthesized-optional-member-call-with-transform/output.js new file mode 100644 index 000000000000..30952e1dbb81 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/parenthesized-optional-member-call-with-transform/output.js @@ -0,0 +1,46 @@ +class Foo { + static getSelf() { + return Foo; + } + + test() { + var _o$Foo$self$getSelf, _o$Foo$self$getSelf2, _fn$Foo$self$getSelf, _fn$Foo$self$getSelf2, _o$Foo, _o$Foo$self, _fn, _fn$Foo, _fn$Foo$self, _fn$Foo$self2; + + const o = { + Foo: Foo + }; + + const fn = function () { + return o; + }; + + (Foo === null || Foo === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(Foo, _m)[_m].bind(Foo))(); + (Foo === null || Foo === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(Foo, _m)[_m].bind(Foo))().toString; + (Foo === null || Foo === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(Foo, _m)[_m].bind(Foo))().toString(); + (o === null || o === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(o.Foo, _m)[_m].bind(o.Foo))(); + (o === null || o === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(o.Foo, _m)[_m].bind(o.Foo))().toString; + (o === null || o === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(o.Foo, _m)[_m].bind(o.Foo))().toString(); + ((_o$Foo$self$getSelf = ((_o$Foo = o.Foo) == null ? void 0 : _o$Foo.self.getSelf.bind(_o$Foo.self))()) === null || _o$Foo$self$getSelf === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(_o$Foo$self$getSelf, _m)[_m].bind(_o$Foo$self$getSelf))(); + ((_o$Foo$self$getSelf2 = ((_o$Foo$self = o.Foo.self) == null ? void 0 : _o$Foo$self.getSelf.bind(_o$Foo$self))()) === null || _o$Foo$self$getSelf2 === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(_o$Foo$self$getSelf2, _m)[_m].bind(_o$Foo$self$getSelf2))(); + ((_fn$Foo$self$getSelf = ((_fn = fn()) == null ? void 0 : (_fn$Foo = _fn.Foo) == null ? void 0 : _fn$Foo.self.getSelf.bind(_fn.Foo.self))()) === null || _fn$Foo$self$getSelf === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(_fn$Foo$self$getSelf, _m)[_m].bind(_fn$Foo$self$getSelf))(); + ((_fn$Foo$self$getSelf2 = (fn == null ? void 0 : (_fn$Foo$self2 = _fn$Foo$self = fn().Foo.self) == null ? void 0 : _fn$Foo$self2.getSelf.bind(_fn$Foo$self))()) === null || _fn$Foo$self$getSelf2 === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(_fn$Foo$self$getSelf2, _m)[_m].bind(_fn$Foo$self$getSelf2))(); + } + +} + +var _x = babelHelpers.classPrivateFieldLooseKey("x"); + +var _m = babelHelpers.classPrivateFieldLooseKey("m"); + +Object.defineProperty(Foo, _x, { + writable: true, + value: 1 +}); +Foo.self = Foo; +Object.defineProperty(Foo, _m, { + writable: true, + value: function () { + return babelHelpers.classPrivateFieldLooseBase(this, _x)[_x]; + } +}); +new Foo().test(); diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/parenthesized-optional-member-call/exec.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/parenthesized-optional-member-call/exec.js new file mode 100644 index 000000000000..ae7bf5d636e3 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/parenthesized-optional-member-call/exec.js @@ -0,0 +1,48 @@ +class Foo { + static #x = 1; + + static self = Foo; + static #m = function() { return this.#x; }; + static getSelf() { return Foo } + + test() { + const o = { Foo: Foo }; + const fn = function () { + return o; + }; + + expect((Foo?.#m)()).toEqual(1); + expect((Foo?.#m)().toString).toEqual(1..toString); + expect((Foo?.#m)().toString()).toEqual('1'); + + expect((o?.Foo.#m)()).toEqual(1); + expect((o?.Foo.#m)().toString).toEqual(1..toString); + expect((o?.Foo.#m)().toString()).toEqual('1'); + + expect((((o.Foo?.self.getSelf)())?.#m)()).toEqual(1); + expect((((o.Foo.self?.getSelf)())?.#m)()).toEqual(1); + + expect((((fn()?.Foo?.self.getSelf)())?.#m)()).toEqual(1); + expect((((fn?.().Foo.self?.getSelf)())?.#m)()).toEqual(1); + } + + testNull() { + const o = null; + const fn = function () { + return { o }; + } + + expect(() => { (o?.Foo.#m)() }).toThrow(); + expect(() => { (o?.Foo.#m)().toString }).toThrow(); + expect(() => { (o?.Foo.#m)().toString() }).toThrow(); + + expect(() => { (((o.Foo?.self.getSelf)())?.#m)() }).toThrow(); + expect(() => { (((o.Foo.self?.getSelf)())?.#m)() }).toThrow(); + + expect(() => (((fn()?.Foo?.self.getSelf)())?.#m)()).toThrow(); + expect(() => (((fn?.().Foo.self?.getSelf)())?.#m)()).toThrow(); + } +} + +(new Foo).test(); +(new Foo).testNull(); diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/parenthesized-optional-member-call/input.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/parenthesized-optional-member-call/input.js new file mode 100644 index 000000000000..0d9a4d608b78 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/parenthesized-optional-member-call/input.js @@ -0,0 +1,30 @@ +class Foo { + static #x = 1; + + static self = Foo; + static #m = function() { return this.#x; }; + static getSelf() { return Foo } + + test() { + const o = { Foo: Foo }; + const fn = function () { + return o; + }; + + (Foo?.#m)(); + (Foo?.#m)().toString; + (Foo?.#m)().toString(); + + (o?.Foo.#m)(); + (o?.Foo.#m)().toString; + (o?.Foo.#m)().toString(); + + (((o.Foo?.self.getSelf)())?.#m)(); + (((o.Foo.self?.getSelf)())?.#m)(); + + (((fn()?.Foo?.self.getSelf)())?.#m)(); + (((fn?.().Foo.self?.getSelf)())?.#m)(); + } +} + +(new Foo).test(); diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/parenthesized-optional-member-call/options.json b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/parenthesized-optional-member-call/options.json new file mode 100644 index 000000000000..88c37f66eeb8 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/parenthesized-optional-member-call/options.json @@ -0,0 +1,7 @@ +{ + "plugins": [ + ["external-helpers", { "helperVersion": "7.100.0" }], + "proposal-class-properties" + ], + "minNodeVersion": "14.0.0" +} diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/parenthesized-optional-member-call/output.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/parenthesized-optional-member-call/output.js new file mode 100644 index 000000000000..23b4f4b6d24f --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/parenthesized-optional-member-call/output.js @@ -0,0 +1,42 @@ +class Foo { + static getSelf() { + return Foo; + } + + test() { + var _o$Foo, _o$Foo2, _o$Foo3, _o$Foo$self$getSelf, _o$Foo$self$getSelf2, _fn$Foo$self$getSelf, _fn$Foo$self$getSelf2; + + const o = { + Foo: Foo + }; + + const fn = function () { + return o; + }; + + (Foo === null || Foo === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _m).bind(Foo))(); + (Foo === null || Foo === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _m).bind(Foo))().toString; + (Foo === null || Foo === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _m).bind(Foo))().toString(); + (o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo = o.Foo, Foo, _m).bind(_o$Foo))(); + (o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo2 = o.Foo, Foo, _m).bind(_o$Foo2))().toString; + (o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo3 = o.Foo, Foo, _m).bind(_o$Foo3))().toString(); + ((_o$Foo$self$getSelf = (o.Foo?.self.getSelf)()) === null || _o$Foo$self$getSelf === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo$self$getSelf, Foo, _m).bind(_o$Foo$self$getSelf))(); + ((_o$Foo$self$getSelf2 = (o.Foo.self?.getSelf)()) === null || _o$Foo$self$getSelf2 === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo$self$getSelf2, Foo, _m).bind(_o$Foo$self$getSelf2))(); + ((_fn$Foo$self$getSelf = (fn()?.Foo?.self.getSelf)()) === null || _fn$Foo$self$getSelf === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_fn$Foo$self$getSelf, Foo, _m).bind(_fn$Foo$self$getSelf))(); + ((_fn$Foo$self$getSelf2 = (fn?.().Foo.self?.getSelf)()) === null || _fn$Foo$self$getSelf2 === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_fn$Foo$self$getSelf2, Foo, _m).bind(_fn$Foo$self$getSelf2))(); + } + +} + +var _x = { + writable: true, + value: 1 +}; +babelHelpers.defineProperty(Foo, "self", Foo); +var _m = { + writable: true, + value: function () { + return babelHelpers.classStaticPrivateFieldSpecGet(this, Foo, _x); + } +}; +new Foo().test(); diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private/parenthesized-optional-member-call-with-transform/exec.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/parenthesized-optional-member-call-with-transform/exec.js new file mode 100644 index 000000000000..ae7bf5d636e3 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/parenthesized-optional-member-call-with-transform/exec.js @@ -0,0 +1,48 @@ +class Foo { + static #x = 1; + + static self = Foo; + static #m = function() { return this.#x; }; + static getSelf() { return Foo } + + test() { + const o = { Foo: Foo }; + const fn = function () { + return o; + }; + + expect((Foo?.#m)()).toEqual(1); + expect((Foo?.#m)().toString).toEqual(1..toString); + expect((Foo?.#m)().toString()).toEqual('1'); + + expect((o?.Foo.#m)()).toEqual(1); + expect((o?.Foo.#m)().toString).toEqual(1..toString); + expect((o?.Foo.#m)().toString()).toEqual('1'); + + expect((((o.Foo?.self.getSelf)())?.#m)()).toEqual(1); + expect((((o.Foo.self?.getSelf)())?.#m)()).toEqual(1); + + expect((((fn()?.Foo?.self.getSelf)())?.#m)()).toEqual(1); + expect((((fn?.().Foo.self?.getSelf)())?.#m)()).toEqual(1); + } + + testNull() { + const o = null; + const fn = function () { + return { o }; + } + + expect(() => { (o?.Foo.#m)() }).toThrow(); + expect(() => { (o?.Foo.#m)().toString }).toThrow(); + expect(() => { (o?.Foo.#m)().toString() }).toThrow(); + + expect(() => { (((o.Foo?.self.getSelf)())?.#m)() }).toThrow(); + expect(() => { (((o.Foo.self?.getSelf)())?.#m)() }).toThrow(); + + expect(() => (((fn()?.Foo?.self.getSelf)())?.#m)()).toThrow(); + expect(() => (((fn?.().Foo.self?.getSelf)())?.#m)()).toThrow(); + } +} + +(new Foo).test(); +(new Foo).testNull(); diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private/parenthesized-optional-member-call-with-transform/input.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/parenthesized-optional-member-call-with-transform/input.js new file mode 100644 index 000000000000..0d9a4d608b78 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/parenthesized-optional-member-call-with-transform/input.js @@ -0,0 +1,30 @@ +class Foo { + static #x = 1; + + static self = Foo; + static #m = function() { return this.#x; }; + static getSelf() { return Foo } + + test() { + const o = { Foo: Foo }; + const fn = function () { + return o; + }; + + (Foo?.#m)(); + (Foo?.#m)().toString; + (Foo?.#m)().toString(); + + (o?.Foo.#m)(); + (o?.Foo.#m)().toString; + (o?.Foo.#m)().toString(); + + (((o.Foo?.self.getSelf)())?.#m)(); + (((o.Foo.self?.getSelf)())?.#m)(); + + (((fn()?.Foo?.self.getSelf)())?.#m)(); + (((fn?.().Foo.self?.getSelf)())?.#m)(); + } +} + +(new Foo).test(); diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private/parenthesized-optional-member-call-with-transform/options.json b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/parenthesized-optional-member-call-with-transform/options.json new file mode 100644 index 000000000000..6dbfa5c4c844 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/parenthesized-optional-member-call-with-transform/options.json @@ -0,0 +1,7 @@ +{ + "plugins": [ + ["external-helpers", { "helperVersion": "7.100.0" }], + "proposal-class-properties", + "proposal-optional-chaining" + ] +} diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private/parenthesized-optional-member-call-with-transform/output.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/parenthesized-optional-member-call-with-transform/output.js new file mode 100644 index 000000000000..7f51bfebb3cb --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/parenthesized-optional-member-call-with-transform/output.js @@ -0,0 +1,42 @@ +class Foo { + static getSelf() { + return Foo; + } + + test() { + var _o$Foo, _o$Foo2, _o$Foo3, _o$Foo$self$getSelf, _o$Foo$self$getSelf2, _fn$Foo$self$getSelf, _fn$Foo$self$getSelf2, _o$Foo4, _o$Foo4$self, _o$Foo$self, _fn, _fn$Foo$self, _fn$Foo, _fn$Foo$self2, _fn$Foo$self3; + + const o = { + Foo: Foo + }; + + const fn = function () { + return o; + }; + + (Foo === null || Foo === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _m).bind(Foo))(); + (Foo === null || Foo === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _m).bind(Foo))().toString; + (Foo === null || Foo === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _m).bind(Foo))().toString(); + (o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo = o.Foo, Foo, _m).bind(_o$Foo))(); + (o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo2 = o.Foo, Foo, _m).bind(_o$Foo2))().toString; + (o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo3 = o.Foo, Foo, _m).bind(_o$Foo3))().toString(); + ((_o$Foo$self$getSelf = ((_o$Foo4 = o.Foo) === null || _o$Foo4 === void 0 ? void 0 : (_o$Foo4$self = _o$Foo4.self).getSelf.bind(_o$Foo4$self))()) === null || _o$Foo$self$getSelf === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo$self$getSelf, Foo, _m).bind(_o$Foo$self$getSelf))(); + ((_o$Foo$self$getSelf2 = ((_o$Foo$self = o.Foo.self) === null || _o$Foo$self === void 0 ? void 0 : _o$Foo$self.getSelf.bind(_o$Foo$self))()) === null || _o$Foo$self$getSelf2 === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo$self$getSelf2, Foo, _m).bind(_o$Foo$self$getSelf2))(); + ((_fn$Foo$self$getSelf = ((_fn = fn()) === null || _fn === void 0 ? void 0 : (_fn$Foo = _fn.Foo) === null || _fn$Foo === void 0 ? void 0 : (_fn$Foo$self = _fn$Foo.self).getSelf.bind(_fn$Foo$self))()) === null || _fn$Foo$self$getSelf === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_fn$Foo$self$getSelf, Foo, _m).bind(_fn$Foo$self$getSelf))(); + ((_fn$Foo$self$getSelf2 = (fn === null || fn === void 0 ? void 0 : (_fn$Foo$self3 = _fn$Foo$self2 = fn().Foo.self) === null || _fn$Foo$self3 === void 0 ? void 0 : _fn$Foo$self3.getSelf.bind(_fn$Foo$self2))()) === null || _fn$Foo$self$getSelf2 === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_fn$Foo$self$getSelf2, Foo, _m).bind(_fn$Foo$self$getSelf2))(); + } + +} + +var _x = { + writable: true, + value: 1 +}; +babelHelpers.defineProperty(Foo, "self", Foo); +var _m = { + writable: true, + value: function () { + return babelHelpers.classStaticPrivateFieldSpecGet(this, Foo, _x); + } +}; +new Foo().test(); diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private/parenthesized-optional-member-call/exec.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/parenthesized-optional-member-call/exec.js new file mode 100644 index 000000000000..ae7bf5d636e3 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/parenthesized-optional-member-call/exec.js @@ -0,0 +1,48 @@ +class Foo { + static #x = 1; + + static self = Foo; + static #m = function() { return this.#x; }; + static getSelf() { return Foo } + + test() { + const o = { Foo: Foo }; + const fn = function () { + return o; + }; + + expect((Foo?.#m)()).toEqual(1); + expect((Foo?.#m)().toString).toEqual(1..toString); + expect((Foo?.#m)().toString()).toEqual('1'); + + expect((o?.Foo.#m)()).toEqual(1); + expect((o?.Foo.#m)().toString).toEqual(1..toString); + expect((o?.Foo.#m)().toString()).toEqual('1'); + + expect((((o.Foo?.self.getSelf)())?.#m)()).toEqual(1); + expect((((o.Foo.self?.getSelf)())?.#m)()).toEqual(1); + + expect((((fn()?.Foo?.self.getSelf)())?.#m)()).toEqual(1); + expect((((fn?.().Foo.self?.getSelf)())?.#m)()).toEqual(1); + } + + testNull() { + const o = null; + const fn = function () { + return { o }; + } + + expect(() => { (o?.Foo.#m)() }).toThrow(); + expect(() => { (o?.Foo.#m)().toString }).toThrow(); + expect(() => { (o?.Foo.#m)().toString() }).toThrow(); + + expect(() => { (((o.Foo?.self.getSelf)())?.#m)() }).toThrow(); + expect(() => { (((o.Foo.self?.getSelf)())?.#m)() }).toThrow(); + + expect(() => (((fn()?.Foo?.self.getSelf)())?.#m)()).toThrow(); + expect(() => (((fn?.().Foo.self?.getSelf)())?.#m)()).toThrow(); + } +} + +(new Foo).test(); +(new Foo).testNull(); diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private/parenthesized-optional-member-call/input.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/parenthesized-optional-member-call/input.js new file mode 100644 index 000000000000..0d9a4d608b78 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/parenthesized-optional-member-call/input.js @@ -0,0 +1,30 @@ +class Foo { + static #x = 1; + + static self = Foo; + static #m = function() { return this.#x; }; + static getSelf() { return Foo } + + test() { + const o = { Foo: Foo }; + const fn = function () { + return o; + }; + + (Foo?.#m)(); + (Foo?.#m)().toString; + (Foo?.#m)().toString(); + + (o?.Foo.#m)(); + (o?.Foo.#m)().toString; + (o?.Foo.#m)().toString(); + + (((o.Foo?.self.getSelf)())?.#m)(); + (((o.Foo.self?.getSelf)())?.#m)(); + + (((fn()?.Foo?.self.getSelf)())?.#m)(); + (((fn?.().Foo.self?.getSelf)())?.#m)(); + } +} + +(new Foo).test(); diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private/parenthesized-optional-member-call/options.json b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/parenthesized-optional-member-call/options.json new file mode 100644 index 000000000000..88c37f66eeb8 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/parenthesized-optional-member-call/options.json @@ -0,0 +1,7 @@ +{ + "plugins": [ + ["external-helpers", { "helperVersion": "7.100.0" }], + "proposal-class-properties" + ], + "minNodeVersion": "14.0.0" +} diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private/parenthesized-optional-member-call/output.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/parenthesized-optional-member-call/output.js new file mode 100644 index 000000000000..23b4f4b6d24f --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/parenthesized-optional-member-call/output.js @@ -0,0 +1,42 @@ +class Foo { + static getSelf() { + return Foo; + } + + test() { + var _o$Foo, _o$Foo2, _o$Foo3, _o$Foo$self$getSelf, _o$Foo$self$getSelf2, _fn$Foo$self$getSelf, _fn$Foo$self$getSelf2; + + const o = { + Foo: Foo + }; + + const fn = function () { + return o; + }; + + (Foo === null || Foo === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _m).bind(Foo))(); + (Foo === null || Foo === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _m).bind(Foo))().toString; + (Foo === null || Foo === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _m).bind(Foo))().toString(); + (o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo = o.Foo, Foo, _m).bind(_o$Foo))(); + (o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo2 = o.Foo, Foo, _m).bind(_o$Foo2))().toString; + (o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo3 = o.Foo, Foo, _m).bind(_o$Foo3))().toString(); + ((_o$Foo$self$getSelf = (o.Foo?.self.getSelf)()) === null || _o$Foo$self$getSelf === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo$self$getSelf, Foo, _m).bind(_o$Foo$self$getSelf))(); + ((_o$Foo$self$getSelf2 = (o.Foo.self?.getSelf)()) === null || _o$Foo$self$getSelf2 === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$Foo$self$getSelf2, Foo, _m).bind(_o$Foo$self$getSelf2))(); + ((_fn$Foo$self$getSelf = (fn()?.Foo?.self.getSelf)()) === null || _fn$Foo$self$getSelf === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_fn$Foo$self$getSelf, Foo, _m).bind(_fn$Foo$self$getSelf))(); + ((_fn$Foo$self$getSelf2 = (fn?.().Foo.self?.getSelf)()) === null || _fn$Foo$self$getSelf2 === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_fn$Foo$self$getSelf2, Foo, _m).bind(_fn$Foo$self$getSelf2))(); + } + +} + +var _x = { + writable: true, + value: 1 +}; +babelHelpers.defineProperty(Foo, "self", Foo); +var _m = { + writable: true, + value: function () { + return babelHelpers.classStaticPrivateFieldSpecGet(this, Foo, _x); + } +}; +new Foo().test(); diff --git a/packages/babel-plugin-proposal-optional-chaining/src/index.js b/packages/babel-plugin-proposal-optional-chaining/src/index.js index 604f5a4fe8f4..c7346891244a 100644 --- a/packages/babel-plugin-proposal-optional-chaining/src/index.js +++ b/packages/babel-plugin-proposal-optional-chaining/src/index.js @@ -23,14 +23,17 @@ export default declare((api, options) => { visitor: { "OptionalCallExpression|OptionalMemberExpression"(path) { - const { parentPath, scope } = path; + const { scope } = path; + const parentPath = path.findParent(p => !p.isParenthesizedExpression()); let isDeleteOperation = false; const optionals = []; let optionalPath = path; while ( optionalPath.isOptionalMemberExpression() || - optionalPath.isOptionalCallExpression() + optionalPath.isOptionalCallExpression() || + optionalPath.isParenthesizedExpression() || + optionalPath.isTSNonNullExpression() ) { const { node } = optionalPath; if (node.optional) { @@ -43,10 +46,8 @@ export default declare((api, options) => { } else if (optionalPath.isOptionalCallExpression()) { optionalPath.node.type = "CallExpression"; optionalPath = optionalPath.get("callee"); - } - - // unwrap a TSNonNullExpression if need - if (optionalPath.isTSNonNullExpression()) { + } else { + // unwrap TSNonNullExpression/ParenthesizedExpression if needed optionalPath = optionalPath.get("expression"); } } @@ -113,7 +114,36 @@ export default declare((api, options) => { ); } } - + let replacement = replacementPath.node; + // Ensure (a?.b)() has proper `this` + if ( + t.isMemberExpression(replacement) && + (replacement.extra?.parenthesized || + // if replacementPath.parentPath does not equal parentPath, + // it must be unwrapped from parenthesized expression. + replacementPath.parentPath !== parentPath) && + parentPath.isCallExpression() + ) { + // `(a?.b)()` to `(a == null ? undefined : a.b.bind(a))()` + const { object } = replacement; + let baseRef; + if (!loose || !isSimpleMemberExpression(object)) { + // memoize the context object in non-loose mode + // `(a?.b.c)()` to `(a == null ? undefined : (_a$b = a.b).c.bind(_a$b))()` + baseRef = scope.maybeGenerateMemoised(object); + if (baseRef) { + replacement.object = t.assignmentExpression( + "=", + baseRef, + object, + ); + } + } + replacement = t.callExpression( + t.memberExpression(replacement, t.identifier("bind")), + [t.cloneNode(baseRef ?? object)], + ); + } replacementPath.replaceWith( t.conditionalExpression( loose @@ -134,7 +164,7 @@ export default declare((api, options) => { isDeleteOperation ? t.booleanLiteral(true) : scope.buildUndefinedNode(), - replacementPath.node, + replacement, ), ); diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-expression-member-call-loose/exec.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-expression-member-call-loose/exec.js new file mode 100644 index 000000000000..2af236e11a8b --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-expression-member-call-loose/exec.js @@ -0,0 +1,51 @@ +class Foo { + constructor() { + this.x = 1; + this.self = this; + } + m() { return this.x; }; + getSelf() { return this } + + test() { + const Foo = this; + const o = { Foo: Foo }; + const fn = function () { + return o; + }; + + expect((Foo?.["m"])()).toEqual(1); + expect((Foo?.["m"])().toString).toEqual(1..toString); + expect((Foo?.["m"])().toString()).toEqual('1'); + + expect(((Foo?.["m"]))()).toEqual(1); + expect(((Foo?.["m"]))().toString).toEqual(1..toString); + expect(((Foo?.["m"]))().toString()).toEqual('1'); + + expect((o?.Foo.m)()).toEqual(1); + expect((o?.Foo.m)().toString).toEqual(1..toString); + expect((o?.Foo.m)().toString()).toEqual('1'); + + expect((((o.Foo?.self.getSelf)())?.m)()).toEqual(1); + expect((((o.Foo.self?.getSelf)())?.m)()).toEqual(1); + + expect((((fn()?.Foo?.self.getSelf)())?.m)()).toEqual(1); + expect((((fn?.().Foo.self?.getSelf)())?.m)()).toEqual(1); + } + + testNull() { + const o = null; + + expect(() => { (o?.Foo.m)() }).toThrow(); + expect(() => { (o?.Foo.m)().toString }).toThrow(); + expect(() => { (o?.Foo.m)().toString() }).toThrow(); + + expect(() => { (((o.Foo?.self.getSelf)())?.m)() }).toThrow(); + expect(() => { (((o.Foo.self?.getSelf)())?.m)() }).toThrow(); + + expect(() => (((fn()?.Foo?.self.getSelf)())?.m)()).toThrow(); + expect(() => (((fn?.().Foo.self?.getSelf)())?.m)()).toThrow(); + } +} + +(new Foo).test(); +(new Foo).testNull(); diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-expression-member-call-loose/options.json b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-expression-member-call-loose/options.json new file mode 100644 index 000000000000..fe05dbf3f58c --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-expression-member-call-loose/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [["proposal-optional-chaining", { "loose": true }]], + "parserOpts": { + "createParenthesizedExpressions": true + } +} diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-expression-member-call/exec.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-expression-member-call/exec.js new file mode 100644 index 000000000000..2af236e11a8b --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-expression-member-call/exec.js @@ -0,0 +1,51 @@ +class Foo { + constructor() { + this.x = 1; + this.self = this; + } + m() { return this.x; }; + getSelf() { return this } + + test() { + const Foo = this; + const o = { Foo: Foo }; + const fn = function () { + return o; + }; + + expect((Foo?.["m"])()).toEqual(1); + expect((Foo?.["m"])().toString).toEqual(1..toString); + expect((Foo?.["m"])().toString()).toEqual('1'); + + expect(((Foo?.["m"]))()).toEqual(1); + expect(((Foo?.["m"]))().toString).toEqual(1..toString); + expect(((Foo?.["m"]))().toString()).toEqual('1'); + + expect((o?.Foo.m)()).toEqual(1); + expect((o?.Foo.m)().toString).toEqual(1..toString); + expect((o?.Foo.m)().toString()).toEqual('1'); + + expect((((o.Foo?.self.getSelf)())?.m)()).toEqual(1); + expect((((o.Foo.self?.getSelf)())?.m)()).toEqual(1); + + expect((((fn()?.Foo?.self.getSelf)())?.m)()).toEqual(1); + expect((((fn?.().Foo.self?.getSelf)())?.m)()).toEqual(1); + } + + testNull() { + const o = null; + + expect(() => { (o?.Foo.m)() }).toThrow(); + expect(() => { (o?.Foo.m)().toString }).toThrow(); + expect(() => { (o?.Foo.m)().toString() }).toThrow(); + + expect(() => { (((o.Foo?.self.getSelf)())?.m)() }).toThrow(); + expect(() => { (((o.Foo.self?.getSelf)())?.m)() }).toThrow(); + + expect(() => (((fn()?.Foo?.self.getSelf)())?.m)()).toThrow(); + expect(() => (((fn?.().Foo.self?.getSelf)())?.m)()).toThrow(); + } +} + +(new Foo).test(); +(new Foo).testNull(); diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-expression-member-call/options.json b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-expression-member-call/options.json new file mode 100644 index 000000000000..eae39736acf4 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-expression-member-call/options.json @@ -0,0 +1,6 @@ +{ + "plugins": ["proposal-optional-chaining"], + "parserOpts": { + "createParenthesizedExpressions": true + } +} diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-member-call-loose/exec.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-member-call-loose/exec.js new file mode 100644 index 000000000000..e6728cdc3119 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-member-call-loose/exec.js @@ -0,0 +1,47 @@ +class Foo { + constructor() { + this.x = 1; + this.self = this; + } + m() { return this.x; }; + getSelf() { return this } + + test() { + const Foo = this; + const o = { Foo: Foo }; + const fn = function () { + return o; + }; + + expect((Foo?.["m"])()).toEqual(1); + expect((Foo?.["m"])().toString).toEqual(1..toString); + expect((Foo?.["m"])().toString()).toEqual('1'); + + expect((o?.Foo.m)()).toEqual(1); + expect((o?.Foo.m)().toString).toEqual(1..toString); + expect((o?.Foo.m)().toString()).toEqual('1'); + + expect((((o.Foo?.self.getSelf)())?.m)()).toEqual(1); + expect((((o.Foo.self?.getSelf)())?.m)()).toEqual(1); + + expect((((fn()?.Foo?.self.getSelf)())?.m)()).toEqual(1); + expect((((fn?.().Foo.self?.getSelf)())?.m)()).toEqual(1); + } + + testNull() { + const o = null; + + expect(() => { (o?.Foo.m)() }).toThrow(); + expect(() => { (o?.Foo.m)().toString }).toThrow(); + expect(() => { (o?.Foo.m)().toString() }).toThrow(); + + expect(() => { (((o.Foo?.self.getSelf)())?.m)() }).toThrow(); + expect(() => { (((o.Foo.self?.getSelf)())?.m)() }).toThrow(); + + expect(() => (((fn()?.Foo?.self.getSelf)())?.m)()).toThrow(); + expect(() => (((fn?.().Foo.self?.getSelf)())?.m)()).toThrow(); + } +} + +(new Foo).test(); +(new Foo).testNull(); diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-member-call-loose/input.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-member-call-loose/input.js new file mode 100644 index 000000000000..d552bd7d544a --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-member-call-loose/input.js @@ -0,0 +1,32 @@ +class Foo { + constructor() { + this.x = 1; + this.self = this; + } + m() { return this.x; }; + getSelf() { return this } + + test() { + const Foo = this; + const o = { Foo: Foo }; + const fn = function () { + return o; + }; + + (Foo?.["m"])(); + (Foo?.["m"])().toString; + (Foo?.["m"])().toString(); + + (o?.Foo.m)(); + (o?.Foo.m)().toString; + (o?.Foo.m)().toString(); + + (((o.Foo?.self.getSelf)())?.m)(); + (((o.Foo.self?.getSelf)())?.m)(); + + (((fn()?.Foo?.self.getSelf)())?.m)(); + (((fn?.().Foo.self?.getSelf)())?.m)(); + } +} + +(new Foo).test(); diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-member-call-loose/options.json b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-member-call-loose/options.json new file mode 100644 index 000000000000..39ea3f99c7ff --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-member-call-loose/options.json @@ -0,0 +1,3 @@ +{ + "plugins": [["proposal-optional-chaining", { "loose": true }]] +} diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-member-call-loose/output.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-member-call-loose/output.js new file mode 100644 index 000000000000..80c198711db7 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-member-call-loose/output.js @@ -0,0 +1,41 @@ +class Foo { + constructor() { + this.x = 1; + this.self = this; + } + + m() { + return this.x; + } + + getSelf() { + return this; + } + + test() { + var _o$Foo$self$getSelf, _o$Foo, _o$Foo$self$getSelf2, _o$Foo$self, _fn$Foo$self$getSelf, _fn, _fn$Foo, _fn$Foo$self$getSelf2, _fn$Foo$self, _fn$Foo$self2; + + const Foo = this; + const o = { + Foo: Foo + }; + + const fn = function () { + return o; + }; + + (Foo == null ? void 0 : Foo["m"].bind(Foo))(); + (Foo == null ? void 0 : Foo["m"].bind(Foo))().toString; + (Foo == null ? void 0 : Foo["m"].bind(Foo))().toString(); + (o == null ? void 0 : o.Foo.m.bind(o.Foo))(); + (o == null ? void 0 : o.Foo.m.bind(o.Foo))().toString; + (o == null ? void 0 : o.Foo.m.bind(o.Foo))().toString(); + ((_o$Foo$self$getSelf = ((_o$Foo = o.Foo) == null ? void 0 : _o$Foo.self.getSelf.bind(_o$Foo.self))()) == null ? void 0 : _o$Foo$self$getSelf.m.bind(_o$Foo$self$getSelf))(); + ((_o$Foo$self$getSelf2 = ((_o$Foo$self = o.Foo.self) == null ? void 0 : _o$Foo$self.getSelf.bind(_o$Foo$self))()) == null ? void 0 : _o$Foo$self$getSelf2.m.bind(_o$Foo$self$getSelf2))(); + ((_fn$Foo$self$getSelf = ((_fn = fn()) == null ? void 0 : (_fn$Foo = _fn.Foo) == null ? void 0 : _fn$Foo.self.getSelf.bind(_fn.Foo.self))()) == null ? void 0 : _fn$Foo$self$getSelf.m.bind(_fn$Foo$self$getSelf))(); + ((_fn$Foo$self$getSelf2 = (fn == null ? void 0 : (_fn$Foo$self2 = _fn$Foo$self = fn().Foo.self) == null ? void 0 : _fn$Foo$self2.getSelf.bind(_fn$Foo$self))()) == null ? void 0 : _fn$Foo$self$getSelf2.m.bind(_fn$Foo$self$getSelf2))(); + } + +} + +new Foo().test(); diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-member-call/exec.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-member-call/exec.js new file mode 100644 index 000000000000..e6728cdc3119 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-member-call/exec.js @@ -0,0 +1,47 @@ +class Foo { + constructor() { + this.x = 1; + this.self = this; + } + m() { return this.x; }; + getSelf() { return this } + + test() { + const Foo = this; + const o = { Foo: Foo }; + const fn = function () { + return o; + }; + + expect((Foo?.["m"])()).toEqual(1); + expect((Foo?.["m"])().toString).toEqual(1..toString); + expect((Foo?.["m"])().toString()).toEqual('1'); + + expect((o?.Foo.m)()).toEqual(1); + expect((o?.Foo.m)().toString).toEqual(1..toString); + expect((o?.Foo.m)().toString()).toEqual('1'); + + expect((((o.Foo?.self.getSelf)())?.m)()).toEqual(1); + expect((((o.Foo.self?.getSelf)())?.m)()).toEqual(1); + + expect((((fn()?.Foo?.self.getSelf)())?.m)()).toEqual(1); + expect((((fn?.().Foo.self?.getSelf)())?.m)()).toEqual(1); + } + + testNull() { + const o = null; + + expect(() => { (o?.Foo.m)() }).toThrow(); + expect(() => { (o?.Foo.m)().toString }).toThrow(); + expect(() => { (o?.Foo.m)().toString() }).toThrow(); + + expect(() => { (((o.Foo?.self.getSelf)())?.m)() }).toThrow(); + expect(() => { (((o.Foo.self?.getSelf)())?.m)() }).toThrow(); + + expect(() => (((fn()?.Foo?.self.getSelf)())?.m)()).toThrow(); + expect(() => (((fn?.().Foo.self?.getSelf)())?.m)()).toThrow(); + } +} + +(new Foo).test(); +(new Foo).testNull(); diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-member-call/input.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-member-call/input.js new file mode 100644 index 000000000000..d552bd7d544a --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-member-call/input.js @@ -0,0 +1,32 @@ +class Foo { + constructor() { + this.x = 1; + this.self = this; + } + m() { return this.x; }; + getSelf() { return this } + + test() { + const Foo = this; + const o = { Foo: Foo }; + const fn = function () { + return o; + }; + + (Foo?.["m"])(); + (Foo?.["m"])().toString; + (Foo?.["m"])().toString(); + + (o?.Foo.m)(); + (o?.Foo.m)().toString; + (o?.Foo.m)().toString(); + + (((o.Foo?.self.getSelf)())?.m)(); + (((o.Foo.self?.getSelf)())?.m)(); + + (((fn()?.Foo?.self.getSelf)())?.m)(); + (((fn?.().Foo.self?.getSelf)())?.m)(); + } +} + +(new Foo).test(); diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-member-call/output.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-member-call/output.js new file mode 100644 index 000000000000..a41ae311d626 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-member-call/output.js @@ -0,0 +1,41 @@ +class Foo { + constructor() { + this.x = 1; + this.self = this; + } + + m() { + return this.x; + } + + getSelf() { + return this; + } + + test() { + var _o$Foo, _o$Foo2, _o$Foo3, _o$Foo$self$getSelf, _o$Foo4, _o$Foo4$self, _o$Foo$self$getSelf2, _o$Foo$self, _fn$Foo$self$getSelf, _fn, _fn$Foo$self, _fn$Foo, _fn$Foo$self$getSelf2, _fn$Foo$self2, _fn$Foo$self3; + + const Foo = this; + const o = { + Foo: Foo + }; + + const fn = function () { + return o; + }; + + (Foo === null || Foo === void 0 ? void 0 : Foo["m"].bind(Foo))(); + (Foo === null || Foo === void 0 ? void 0 : Foo["m"].bind(Foo))().toString; + (Foo === null || Foo === void 0 ? void 0 : Foo["m"].bind(Foo))().toString(); + (o === null || o === void 0 ? void 0 : (_o$Foo = o.Foo).m.bind(_o$Foo))(); + (o === null || o === void 0 ? void 0 : (_o$Foo2 = o.Foo).m.bind(_o$Foo2))().toString; + (o === null || o === void 0 ? void 0 : (_o$Foo3 = o.Foo).m.bind(_o$Foo3))().toString(); + ((_o$Foo$self$getSelf = ((_o$Foo4 = o.Foo) === null || _o$Foo4 === void 0 ? void 0 : (_o$Foo4$self = _o$Foo4.self).getSelf.bind(_o$Foo4$self))()) === null || _o$Foo$self$getSelf === void 0 ? void 0 : _o$Foo$self$getSelf.m.bind(_o$Foo$self$getSelf))(); + ((_o$Foo$self$getSelf2 = ((_o$Foo$self = o.Foo.self) === null || _o$Foo$self === void 0 ? void 0 : _o$Foo$self.getSelf.bind(_o$Foo$self))()) === null || _o$Foo$self$getSelf2 === void 0 ? void 0 : _o$Foo$self$getSelf2.m.bind(_o$Foo$self$getSelf2))(); + ((_fn$Foo$self$getSelf = ((_fn = fn()) === null || _fn === void 0 ? void 0 : (_fn$Foo = _fn.Foo) === null || _fn$Foo === void 0 ? void 0 : (_fn$Foo$self = _fn$Foo.self).getSelf.bind(_fn$Foo$self))()) === null || _fn$Foo$self$getSelf === void 0 ? void 0 : _fn$Foo$self$getSelf.m.bind(_fn$Foo$self$getSelf))(); + ((_fn$Foo$self$getSelf2 = (fn === null || fn === void 0 ? void 0 : (_fn$Foo$self3 = _fn$Foo$self2 = fn().Foo.self) === null || _fn$Foo$self3 === void 0 ? void 0 : _fn$Foo$self3.getSelf.bind(_fn$Foo$self2))()) === null || _fn$Foo$self$getSelf2 === void 0 ? void 0 : _fn$Foo$self$getSelf2.m.bind(_fn$Foo$self$getSelf2))(); + } + +} + +new Foo().test();