diff --git a/packages/babel-plugin-class-features/src/fields.js b/packages/babel-plugin-class-features/src/fields.js index 648a4a743ce6..076d3a8925a0 100644 --- a/packages/babel-plugin-class-features/src/fields.js +++ b/packages/babel-plugin-class-features/src/fields.js @@ -257,6 +257,19 @@ function buildPrivateStaticFieldInitSpec(prop, privateNamesMap) { `; } +function buildPrivateMethodInitLoose(ref, prop, privateNamesMap) { + const { methodId, id } = privateNamesMap.get(prop.node.key.id.name); + + return template.statement.ast` + Object.defineProperty(${ref}, ${id}, { + // configurable is false by default + // enumerable is false by default + writable: false, + value: ${methodId.name} + }); + `; +} + function buildPrivateInstanceMethodInitSpec(ref, prop, privateNamesMap) { const { id } = privateNamesMap.get(prop.node.key.id.name); @@ -348,6 +361,17 @@ export function buildFieldsInitNodes( ); break; case !isStatic && isPrivateMethod && loose: + instanceNodes.push( + buildPrivateMethodInitLoose( + t.thisExpression(), + prop, + privateNamesMap, + ), + ); + staticNodes.push( + buildPrivateInstanceMethodDeclaration(prop, privateNamesMap), + ); + break; case !isStatic && isPrivateMethod && !loose: instanceNodes.push( buildPrivateInstanceMethodInitSpec( diff --git a/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/assignment/exec.js b/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/assignment/exec.js new file mode 100644 index 000000000000..4c1324aeeb62 --- /dev/null +++ b/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/assignment/exec.js @@ -0,0 +1,11 @@ +class Foo { + constructor() { + this.publicField = this.#privateMethod(); + } + + #privateMethod() { + return 42; + } + } + + expect((new Foo).publicField).toEqual(42); diff --git a/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/assignment/input.js b/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/assignment/input.js new file mode 100644 index 000000000000..e55a8afdd3ad --- /dev/null +++ b/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/assignment/input.js @@ -0,0 +1,9 @@ +class Foo { + constructor() { + this.publicField = this.#privateMethod(); + } + + #privateMethod() { + return 42; + } +} \ No newline at end of file diff --git a/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/assignment/output.js b/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/assignment/output.js new file mode 100644 index 000000000000..669940f94991 --- /dev/null +++ b/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/assignment/output.js @@ -0,0 +1,16 @@ +var Foo = function Foo() { + "use strict"; + + babelHelpers.classCallCheck(this, Foo); + Object.defineProperty(this, _privateMethod, { + writable: false, + value: _privateMethod2 + }); + this.publicField = babelHelpers.classPrivateFieldLooseBase(this, _privateMethod)[_privateMethod](); +}; + +var _privateMethod = babelHelpers.classPrivateFieldLooseKey("privateMethod"); + +var _privateMethod2 = function _privateMethod2() { + return 42; +}; diff --git a/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/context/exec.js b/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/context/exec.js new file mode 100644 index 000000000000..6c18bf53c193 --- /dev/null +++ b/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/context/exec.js @@ -0,0 +1,41 @@ +class Foo { + constructor(status) { + this.status = status; + expect(() => this.#getStatus = null).toThrow(TypeError); + } + + #getStatus() { + return this.status; + } + + getCurrentStatus() { + return this.#getStatus(); + } + + setCurrentStatus(newStatus) { + this.status = newStatus; + } + + getFakeStatus(fakeStatus) { + const getStatus = this.#getStatus; + return function () { + return getStatus.call({ status: fakeStatus }); + }; + } + + getFakeStatusFunc() { + return { + status: 'fake-status', + getFakeStatus: this.#getStatus, + }; + } + } + + const f = new Foo('inactive'); + expect(f.getCurrentStatus()).toBe('inactive'); + + f.setCurrentStatus('new-status'); + expect(f.getCurrentStatus()).toBe('new-status'); + + expect(f.getFakeStatus('fake')()).toBe('fake'); + expect(f.getFakeStatusFunc().getFakeStatus()).toBe('fake-status'); diff --git a/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/context/input.js b/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/context/input.js new file mode 100644 index 000000000000..67a8ab68a682 --- /dev/null +++ b/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/context/input.js @@ -0,0 +1,31 @@ +class Foo { + constructor(status) { + this.status = status; + } + + #getStatus() { + return this.status; + } + + getCurrentStatus() { + return this.#getStatus(); + } + + setCurrentStatus(newStatus) { + this.status = newStatus; + } + + getFakeStatus(fakeStatus) { + const fakeGetStatus = this.#getStatus; + return function() { + return fakeGetStatus.call({ status: fakeStatus }); + }; + } + + getFakeStatusFunc() { + return { + status: 'fake-status', + getFakeStatus: this.#getStatus, + }; + } +} \ No newline at end of file diff --git a/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/context/output.js b/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/context/output.js new file mode 100644 index 000000000000..e0b936f3d31a --- /dev/null +++ b/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/context/output.js @@ -0,0 +1,52 @@ +var Foo = +/*#__PURE__*/ +function () { + "use strict"; + + function Foo(status) { + babelHelpers.classCallCheck(this, Foo); + Object.defineProperty(this, _getStatus, { + writable: false, + value: _getStatus2 + }); + this.status = status; + } + + babelHelpers.createClass(Foo, [{ + key: "getCurrentStatus", + value: function getCurrentStatus() { + return babelHelpers.classPrivateFieldLooseBase(this, _getStatus)[_getStatus](); + } + }, { + key: "setCurrentStatus", + value: function setCurrentStatus(newStatus) { + this.status = newStatus; + } + }, { + key: "getFakeStatus", + value: function getFakeStatus(fakeStatus) { + var fakeGetStatus = babelHelpers.classPrivateFieldLooseBase(this, _getStatus)[_getStatus]; + + return function () { + return fakeGetStatus.call({ + status: fakeStatus + }); + }; + } + }, { + key: "getFakeStatusFunc", + value: function getFakeStatusFunc() { + return { + status: 'fake-status', + getFakeStatus: babelHelpers.classPrivateFieldLooseBase(this, _getStatus)[_getStatus] + }; + } + }]); + return Foo; +}(); + +var _getStatus = babelHelpers.classPrivateFieldLooseKey("getStatus"); + +var _getStatus2 = function _getStatus2() { + return this.status; +}; diff --git a/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/exfiltrated/exec.js b/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/exfiltrated/exec.js new file mode 100644 index 000000000000..d73ad144903f --- /dev/null +++ b/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/exfiltrated/exec.js @@ -0,0 +1,13 @@ +let exfiltrated; +class Foo { + #privateMethod() {} + + constructor() { + if (exfiltrated === undefined) { + exfiltrated = this.#privateMethod; + } + expect(exfiltrated).toStrictEqual(this.#privateMethod); + } +} + +new Foo(); \ No newline at end of file diff --git a/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/exfiltrated/input.js b/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/exfiltrated/input.js new file mode 100644 index 000000000000..91bf8510d814 --- /dev/null +++ b/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/exfiltrated/input.js @@ -0,0 +1,10 @@ +let exfiltrated; +class Foo { + #privateMethod() {} + + constructor() { + if (exfiltrated === undefined) { + exfiltrated = this.#privateMethod; + } + } +} \ No newline at end of file diff --git a/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/exfiltrated/output.js b/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/exfiltrated/output.js new file mode 100644 index 000000000000..c4cdb50d819e --- /dev/null +++ b/packages/babel-plugin-proposal-private-methods/test/fixtures/private-method-loose/exfiltrated/output.js @@ -0,0 +1,19 @@ +var exfiltrated; + +var Foo = function Foo() { + "use strict"; + + babelHelpers.classCallCheck(this, Foo); + Object.defineProperty(this, _privateMethod, { + writable: false, + value: _privateMethod2 + }); + + if (exfiltrated === undefined) { + exfiltrated = babelHelpers.classPrivateFieldLooseBase(this, _privateMethod)[_privateMethod]; + } +}; + +var _privateMethod = babelHelpers.classPrivateFieldLooseKey("privateMethod"); + +var _privateMethod2 = function _privateMethod2() {};