From 1de0bb83be104dd03df5afd3799ffc7cf4f475f6 Mon Sep 17 00:00:00 2001 From: Timothy McClure Date: Sun, 16 Jun 2019 17:06:45 +0100 Subject: [PATCH] [WIP] Private static accessors strict --- .../src/fields.js | 61 ++++++++++++++++--- packages/babel-helpers/src/helpers.js | 20 ++++-- .../private-static-method/this/output.js | 8 +-- .../fixtures/static-accessors/basic/exec.js | 23 +++++++ .../fixtures/static-accessors/basic/input.js | 19 ++++++ .../fixtures/static-accessors/basic/output.js | 27 ++++++++ .../static-accessors/get-only-setter/exec.js | 13 ++++ .../static-accessors/get-only-setter/input.js | 11 ++++ .../get-only-setter/output.js | 18 ++++++ .../fixtures/static-accessors/options.json | 14 +++++ .../static-accessors/set-only-getter/exec.js | 13 ++++ .../static-accessors/set-only-getter/input.js | 11 ++++ .../set-only-getter/output.js | 18 ++++++ .../fixtures/static-accessors/updates/exec.js | 46 ++++++++++++++ .../static-accessors/updates/input.js | 44 +++++++++++++ .../static-accessors/updates/output.js | 51 ++++++++++++++++ 16 files changed, 379 insertions(+), 18 deletions(-) create mode 100644 packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/basic/exec.js create mode 100644 packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/basic/input.js create mode 100644 packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/basic/output.js create mode 100644 packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/get-only-setter/exec.js create mode 100644 packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/get-only-setter/input.js create mode 100644 packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/get-only-setter/output.js create mode 100644 packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/options.json create mode 100644 packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/set-only-getter/exec.js create mode 100644 packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/set-only-getter/input.js create mode 100644 packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/set-only-getter/output.js create mode 100644 packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/updates/exec.js create mode 100644 packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/updates/input.js create mode 100644 packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/updates/output.js 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 b3d2ccff0ebd..8168bae78439 100644 --- a/packages/babel-helper-create-class-features-plugin/src/fields.js +++ b/packages/babel-helper-create-class-features-plugin/src/fields.js @@ -142,9 +142,10 @@ const privateNameHandlerSpec = { } = privateNamesMap.get(name); if (isStatic) { - const helperName = isMethod - ? "classStaticPrivateMethodGet" - : "classStaticPrivateFieldSpecGet"; + const helperName = + isMethod && !(getId || setId) + ? "classStaticPrivateMethodGet" + : "classStaticPrivateFieldSpecGet"; return t.callExpression(file.addHelper(helperName), [ this.receiver(member), @@ -180,12 +181,14 @@ const privateNameHandlerSpec = { static: isStatic, method: isMethod, setId, + getId, } = privateNamesMap.get(name); if (isStatic) { - const helperName = isMethod - ? "classStaticPrivateMethodSet" - : "classStaticPrivateFieldSpecSet"; + const helperName = + isMethod && !(getId || setId) + ? "classStaticPrivateMethodSet" + : "classStaticPrivateFieldSpecSet"; return t.callExpression(file.addHelper(helperName), [ this.receiver(member), @@ -287,8 +290,47 @@ function buildPrivateInstanceFieldInitSpec(ref, prop, privateNamesMap) { } function buildPrivateStaticFieldInitSpec(prop, privateNamesMap) { - const { id } = privateNamesMap.get(prop.node.key.id.name); + const privateName = privateNamesMap.get(prop.node.key.id.name); + const { id, getId, setId, initAdded } = privateName; const value = prop.node.value || prop.scope.buildUndefinedNode(); + if (!prop.isProperty() && (initAdded || !(getId || setId))) return; + + if (getId || setId) { + privateNamesMap.set(prop.node.key.id.name, { + ...privateName, + initAdded: true, + }); + + if (getId && setId) { + return template.statement.ast` + var ${id.name} = { + // configurable is false by default + // enumerable is false by default + // writable is false by default + get: ${getId.name}, + set: ${setId.name} + } + `; + } else if (getId && !setId) { + return template.statement.ast` + var ${id.name} = { + // configurable is false by default + // enumerable is false by default + // writable is false by default + get: ${getId.name} + } + `; + } else if (!getId && setId) { + return template.statement.ast` + var ${id.name} = { + // configurable is false by default + // enumerable is false by default + // writable is false by default + set: ${setId.name} + } + `; + } + } return template.statement.ast` var ${id} = { @@ -602,6 +644,9 @@ export function buildFieldsInitNodes( case isStatic && isPrivate && isMethod && !loose: needsClassRef = true; staticNodes.push( + buildPrivateStaticFieldInitSpec(prop, privateNamesMap), + ); + staticNodes.unshift( buildPrivateMethodDeclaration(prop, privateNamesMap, loose), ); break; @@ -633,7 +678,7 @@ export function buildFieldsInitNodes( } return { - staticNodes, + staticNodes: staticNodes.filter(Boolean), instanceNodes: instanceNodes.filter(Boolean), wrapClass(path) { for (const prop of props) { diff --git a/packages/babel-helpers/src/helpers.js b/packages/babel-helpers/src/helpers.js index f7be38ce1e43..bd787c8d5a3c 100644 --- a/packages/babel-helpers/src/helpers.js +++ b/packages/babel-helpers/src/helpers.js @@ -1128,6 +1128,9 @@ helpers.classStaticPrivateFieldSpecGet = helper("7.0.2")` if (receiver !== classConstructor) { throw new TypeError("Private static access of wrong provenance"); } + if (descriptor.get) { + return descriptor.get.call(receiver); + } return descriptor.value; } `; @@ -1137,13 +1140,18 @@ helpers.classStaticPrivateFieldSpecSet = helper("7.0.2")` if (receiver !== classConstructor) { throw new TypeError("Private static access of wrong provenance"); } - if (!descriptor.writable) { - // This should only throw in strict mode, but class bodies are - // always strict and private fields can only be used inside - // class bodies. - throw new TypeError("attempted to set read only private field"); + if (descriptor.set) { + descriptor.set.call(receiver, value); + } else { + if (!descriptor.writable) { + // This should only throw in strict mode, but class bodies are + // always strict and private fields can only be used inside + // class bodies. + throw new TypeError("attempted to set read only private field"); + } + descriptor.value = value; } - descriptor.value = value; + return value; } `; diff --git a/packages/babel-plugin-proposal-private-methods/test/fixtures/private-static-method/this/output.js b/packages/babel-plugin-proposal-private-methods/test/fixtures/private-static-method/this/output.js index e440955ecc99..49a4f4a7c600 100644 --- a/packages/babel-plugin-proposal-private-methods/test/fixtures/private-static-method/this/output.js +++ b/packages/babel-plugin-proposal-private-methods/test/fixtures/private-static-method/this/output.js @@ -16,12 +16,12 @@ class B extends A { } -var _getA = function _getA() { - return babelHelpers.get(babelHelpers.getPrototypeOf(B), "a", this); -}; - var _getB = function _getB() { return this.b; }; +var _getA = function _getA() { + return babelHelpers.get(babelHelpers.getPrototypeOf(B), "a", this); +}; + var [getA, getB] = B.extract(); diff --git a/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/basic/exec.js b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/basic/exec.js new file mode 100644 index 000000000000..791a59034af9 --- /dev/null +++ b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/basic/exec.js @@ -0,0 +1,23 @@ +class Cl { + static #PRIVATE_STATIC_FIELD = "top secret string"; + + static get #privateStaticFieldValue() { + return Cl.#PRIVATE_STATIC_FIELD; + } + + static set #privateStaticFieldValue(newValue) { + Cl.#PRIVATE_STATIC_FIELD = `Updated: ${newValue}`; + } + + static getValue() { + return Cl.#privateStaticFieldValue; + } + + static setValue() { + Cl.#privateStaticFieldValue = "dank"; + } +} + +expect(Cl.getValue()).toEqual("top secret string"); +Cl.setValue(); +expect(Cl.getValue()).toEqual("Updated: dank"); \ No newline at end of file diff --git a/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/basic/input.js b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/basic/input.js new file mode 100644 index 000000000000..c79cdbe5ff0e --- /dev/null +++ b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/basic/input.js @@ -0,0 +1,19 @@ +class Cl { + static #PRIVATE_STATIC_FIELD = "top secret string"; + + static get #privateStaticFieldValue() { + return Cl.#PRIVATE_STATIC_FIELD; + } + + static set #privateStaticFieldValue(newValue) { + Cl.#PRIVATE_STATIC_FIELD = `Updated: ${newValue}`; + } + + static getValue() { + return Cl.#privateStaticFieldValue; + } + + static setValue() { + Cl.#privateStaticFieldValue = "dank"; + } +} \ No newline at end of file diff --git a/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/basic/output.js b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/basic/output.js new file mode 100644 index 000000000000..7dc0b26977eb --- /dev/null +++ b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/basic/output.js @@ -0,0 +1,27 @@ +class Cl { + static getValue() { + return babelHelpers.classStaticPrivateFieldSpecGet(Cl, Cl, _privateStaticFieldValue); + } + + static setValue() { + babelHelpers.classStaticPrivateFieldSpecSet(Cl, Cl, _privateStaticFieldValue, "dank"); + } + +} + +var _set_privateStaticFieldValue = function (newValue) { + babelHelpers.classStaticPrivateFieldSpecSet(Cl, Cl, _PRIVATE_STATIC_FIELD, `Updated: ${newValue}`); +}; + +var _get_privateStaticFieldValue = function () { + return babelHelpers.classStaticPrivateFieldSpecGet(Cl, Cl, _PRIVATE_STATIC_FIELD); +}; + +var _PRIVATE_STATIC_FIELD = { + writable: true, + value: "top secret string" +}; +var _privateStaticFieldValue = { + get: _get_privateStaticFieldValue, + set: _set_privateStaticFieldValue +}; diff --git a/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/get-only-setter/exec.js b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/get-only-setter/exec.js new file mode 100644 index 000000000000..0c14f25c0938 --- /dev/null +++ b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/get-only-setter/exec.js @@ -0,0 +1,13 @@ +class Cl { + static #PRIVATE_STATIC_FIELD = 0; + + static set #privateStaticFieldValue(newValue) { + Cl.#PRIVATE_STATIC_FIELD = newValue; + } + + static getPrivateStaticFieldValue() { + return Cl.#privateStaticFieldValue; + } +} + +expect(Cl.getPrivateStaticFieldValue()).toBeUndefined(); \ No newline at end of file diff --git a/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/get-only-setter/input.js b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/get-only-setter/input.js new file mode 100644 index 000000000000..ab8771074ec7 --- /dev/null +++ b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/get-only-setter/input.js @@ -0,0 +1,11 @@ +class Cl { + static #PRIVATE_STATIC_FIELD = 0; + + static set #privateStaticFieldValue(newValue) { + Cl.#PRIVATE_STATIC_FIELD = newValue; + } + + static getPrivateStaticFieldValue() { + return Cl.#privateStaticFieldValue; + } +} \ No newline at end of file diff --git a/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/get-only-setter/output.js b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/get-only-setter/output.js new file mode 100644 index 000000000000..36075fd22e62 --- /dev/null +++ b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/get-only-setter/output.js @@ -0,0 +1,18 @@ +class Cl { + static getPrivateStaticFieldValue() { + return babelHelpers.classStaticPrivateFieldSpecGet(Cl, Cl, _privateStaticFieldValue); + } + +} + +var _set_privateStaticFieldValue = function (newValue) { + babelHelpers.classStaticPrivateFieldSpecSet(Cl, Cl, _PRIVATE_STATIC_FIELD, newValue); +}; + +var _PRIVATE_STATIC_FIELD = { + writable: true, + value: 0 +}; +var _privateStaticFieldValue = { + set: _set_privateStaticFieldValue +}; diff --git a/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/options.json b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/options.json new file mode 100644 index 000000000000..c5e6649e4ea8 --- /dev/null +++ b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/options.json @@ -0,0 +1,14 @@ +{ + "plugins": [ + [ + "external-helpers", + { + "helperVersion": "7.1000.0" + } + ], + "proposal-private-methods", + "proposal-class-properties", + "transform-block-scoping", + "syntax-class-properties" + ] +} diff --git a/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/set-only-getter/exec.js b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/set-only-getter/exec.js new file mode 100644 index 000000000000..7069abfadc9d --- /dev/null +++ b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/set-only-getter/exec.js @@ -0,0 +1,13 @@ +class Cl { + static #PRIVATE_STATIC_FIELD = 0; + + static get #privateStaticFieldValue() { + return Cl.#PRIVATE_STATIC_FIELD; + } + + static setPrivateStaticFieldValue() { + Cl.#privateStaticFieldValue = 1; + } +} + +expect(() => Cl.setPrivateStaticFieldValue()).toThrow(TypeError); \ No newline at end of file diff --git a/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/set-only-getter/input.js b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/set-only-getter/input.js new file mode 100644 index 000000000000..d54ce4e2c2d0 --- /dev/null +++ b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/set-only-getter/input.js @@ -0,0 +1,11 @@ +class Cl { + static #PRIVATE_STATIC_FIELD = 0; + + static get #privateStaticFieldValue() { + return Cl.#PRIVATE_STATIC_FIELD; + } + + static setPrivateStaticFieldValue() { + Cl.#privateStaticFieldValue = 1; + } +} \ No newline at end of file diff --git a/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/set-only-getter/output.js b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/set-only-getter/output.js new file mode 100644 index 000000000000..7160682ebe44 --- /dev/null +++ b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/set-only-getter/output.js @@ -0,0 +1,18 @@ +class Cl { + static setPrivateStaticFieldValue() { + babelHelpers.classStaticPrivateFieldSpecSet(Cl, Cl, _privateStaticFieldValue, 1); + } + +} + +var _get_privateStaticFieldValue = function () { + return babelHelpers.classStaticPrivateFieldSpecGet(Cl, Cl, _PRIVATE_STATIC_FIELD); +}; + +var _PRIVATE_STATIC_FIELD = { + writable: true, + value: 0 +}; +var _privateStaticFieldValue = { + get: _get_privateStaticFieldValue +}; diff --git a/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/updates/exec.js b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/updates/exec.js new file mode 100644 index 000000000000..05f70aecceec --- /dev/null +++ b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/updates/exec.js @@ -0,0 +1,46 @@ +class Cl { + static #privateField = "top secret string"; + static publicField = "not secret string"; + + static get #privateFieldValue() { + return Cl.#privateField; + } + + static set #privateFieldValue(newValue) { + Cl.#privateField = newValue; + } + + static publicGetPrivateField() { + return Cl.#privateFieldValue; + } + + static publicSetPrivateField(newValue) { + Cl.#privateFieldValue = newValue; + } + + static get publicFieldValue() { + return Cl.publicField; + } + + static set publicFieldValue(newValue) { + Cl.publicField = newValue; + } + + static testUpdates() { + Cl.#privateField = 0; + Cl.publicField = 0; + Cl.#privateFieldValue = Cl.#privateFieldValue++; + Cl.publicFieldValue = Cl.publicFieldValue++; + expect(Cl.#privateField).toEqual(Cl.publicField); + + ++Cl.#privateFieldValue; + ++Cl.publicFieldValue; + expect(Cl.#privateField).toEqual(Cl.publicField); + + Cl.#privateFieldValue += 1; + Cl.publicFieldValue += 1; + expect(Cl.#privateField).toEqual(Cl.publicField); + } +} + +Cl.testUpdates(); \ No newline at end of file diff --git a/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/updates/input.js b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/updates/input.js new file mode 100644 index 000000000000..33c7b39d9cb4 --- /dev/null +++ b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/updates/input.js @@ -0,0 +1,44 @@ +class Cl { + static #privateField = "top secret string"; + static publicField = "not secret string"; + + static get #privateFieldValue() { + return Cl.#privateField; + } + + static set #privateFieldValue(newValue) { + Cl.#privateField = newValue; + } + + static publicGetPrivateField() { + return Cl.#privateFieldValue; + } + + static publicSetPrivateField(newValue) { + Cl.#privateFieldValue = newValue; + } + + static get publicFieldValue() { + return Cl.publicField; + } + + static set publicFieldValue(newValue) { + Cl.publicField = newValue; + } + + static testUpdates() { + Cl.#privateField = 0; + Cl.publicField = 0; + Cl.#privateFieldValue = Cl.#privateFieldValue++; + Cl.publicFieldValue = Cl.publicFieldValue++; + + ++Cl.#privateFieldValue; + ++Cl.publicFieldValue; + + Cl.#privateFieldValue += 1; + Cl.publicFieldValue += 1; + + Cl.#privateFieldValue = -(Cl.#privateFieldValue ** Cl.#privateFieldValue); + Cl.publicFieldValue = -(Cl.publicFieldValue ** Cl.publicFieldValue); + } +} \ No newline at end of file diff --git a/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/updates/output.js b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/updates/output.js new file mode 100644 index 000000000000..1869c5b6ead8 --- /dev/null +++ b/packages/babel-plugin-proposal-private-methods/test/fixtures/static-accessors/updates/output.js @@ -0,0 +1,51 @@ +class Cl { + static publicGetPrivateField() { + return babelHelpers.classStaticPrivateFieldSpecGet(Cl, Cl, _privateFieldValue); + } + + static publicSetPrivateField(newValue) { + babelHelpers.classStaticPrivateFieldSpecSet(Cl, Cl, _privateFieldValue, newValue); + } + + static get publicFieldValue() { + return Cl.publicField; + } + + static set publicFieldValue(newValue) { + Cl.publicField = newValue; + } + + static testUpdates() { + var _Cl$privateFieldValue, _Cl$privateFieldValue2; + + babelHelpers.classStaticPrivateFieldSpecSet(Cl, Cl, _privateField, 0); + Cl.publicField = 0; + babelHelpers.classStaticPrivateFieldSpecSet(Cl, Cl, _privateFieldValue, (babelHelpers.classStaticPrivateFieldSpecSet(Cl, Cl, _privateFieldValue, (_Cl$privateFieldValue2 = +babelHelpers.classStaticPrivateFieldSpecGet(Cl, Cl, _privateFieldValue)) + 1), _Cl$privateFieldValue2)); + Cl.publicFieldValue = Cl.publicFieldValue++; + babelHelpers.classStaticPrivateFieldSpecSet(Cl, Cl, _privateFieldValue, +babelHelpers.classStaticPrivateFieldSpecGet(Cl, Cl, _privateFieldValue) + 1); + ++Cl.publicFieldValue; + babelHelpers.classStaticPrivateFieldSpecSet(Cl, Cl, _privateFieldValue, babelHelpers.classStaticPrivateFieldSpecGet(Cl, Cl, _privateFieldValue) + 1); + Cl.publicFieldValue += 1; + babelHelpers.classStaticPrivateFieldSpecSet(Cl, Cl, _privateFieldValue, -(babelHelpers.classStaticPrivateFieldSpecGet(Cl, Cl, _privateFieldValue) ** babelHelpers.classStaticPrivateFieldSpecGet(Cl, Cl, _privateFieldValue))); + Cl.publicFieldValue = -(Cl.publicFieldValue ** Cl.publicFieldValue); + } + +} + +var _set_privateFieldValue = function (newValue) { + babelHelpers.classStaticPrivateFieldSpecSet(Cl, Cl, _privateField, newValue); +}; + +var _get_privateFieldValue = function () { + return babelHelpers.classStaticPrivateFieldSpecGet(Cl, Cl, _privateField); +}; + +var _privateField = { + writable: true, + value: "top secret string" +}; +babelHelpers.defineProperty(Cl, "publicField", "not secret string"); +var _privateFieldValue = { + get: _get_privateFieldValue, + set: _set_privateFieldValue +};