From e9c8e56b457ec08d70a68c85dff7981dd570d32b Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Tue, 10 Jul 2018 10:24:12 -0400 Subject: [PATCH] Private Static Class Fields Implementation --- packages/babel-helpers/src/helpers.js | 65 ++++---- .../src/index.js | 139 ++++++++++++++++-- 2 files changed, 158 insertions(+), 46 deletions(-) diff --git a/packages/babel-helpers/src/helpers.js b/packages/babel-helpers/src/helpers.js index d25bdecb8e2d..a972027af172 100644 --- a/packages/babel-helpers/src/helpers.js +++ b/packages/babel-helpers/src/helpers.js @@ -238,35 +238,30 @@ helpers.asyncGeneratorDelegate = () => template.program.ast` `; helpers.asyncToGenerator = () => template.program.ast` - function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { - try { - var info = gen[key](arg); - var value = info.value; - } catch (error) { - reject(error); - return; - } - - if (info.done) { - resolve(value); - } else { - Promise.resolve(value).then(_next, _throw); - } - } - export default function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); - function _next(value) { - asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); - } - function _throw(err) { - asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); + function step(key, arg) { + try { + var info = gen[key](arg); + var value = info.value; + } catch (error) { + reject(error); + return; + } + + if (info.done) { + resolve(value); + } else { + Promise.resolve(value).then(_next, _throw); + } } + function _next(value) { step("next", value); } + function _throw(err) { step("throw", err); } - _next(undefined); + _next(); }); }; } @@ -584,8 +579,8 @@ helpers.objectDestructuringEmpty = () => template.program.ast` } `; -helpers.objectWithoutPropertiesLoose = () => template.program.ast` - export default function _objectWithoutPropertiesLoose(source, excluded) { +helpers.objectWithoutProperties = () => template.program.ast` + export default function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = {}; @@ -598,19 +593,6 @@ helpers.objectWithoutPropertiesLoose = () => template.program.ast` target[key] = source[key]; } - return target; - } -`; - -helpers.objectWithoutProperties = () => template.program.ast` - import objectWithoutPropertiesLoose from "objectWithoutPropertiesLoose"; - - export default function _objectWithoutProperties(source, excluded) { - if (source == null) return {}; - - var target = objectWithoutPropertiesLoose(source, excluded); - var key, i; - if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { @@ -1029,3 +1011,12 @@ helpers.classPrivateFieldSet = () => template.program.ast` return value; } `; + +helpers.classStaticPrivateFieldBase = () => template.program.ast` + export default function _classStaticPrivateFieldBase(receiver, classConstructor, privateClass) { + if (receiver !== classConstructor && receiver.constructor !== classConstructor) { + throw new TypeError("Private static access of wrong provenance"); + } + return privateClass; + } +`; diff --git a/packages/babel-plugin-proposal-class-properties/src/index.js b/packages/babel-plugin-proposal-class-properties/src/index.js index d3c833c5e580..ce05b239cb0e 100644 --- a/packages/babel-plugin-proposal-class-properties/src/index.js +++ b/packages/babel-plugin-proposal-class-properties/src/index.js @@ -83,6 +83,18 @@ export default declare((api, options) => { }, }; + // Traverses the class scope, handling private static name references. + const staticPrivatePropertyVisitor = { + PrivateName(path) { + const { name } = this; + const { node, parentPath } = path; + if (node.id.name !== name) return; + if (parentPath.isMemberExpression({ property: node })) { + this.handle(parentPath); + } + }, + }; + // Traverses the outer portion of a class, without touching the class's inner // scope, for private names. const privateNameInnerVisitor = traverse.visitors.merge([ @@ -157,6 +169,23 @@ export default declare((api, options) => { }, }; + const staticPrivatePropertyHandler = { + handle(member) { + const { file, privateId, privateClassId, classRef } = this; + member.replaceWith( + template.expression`BASE(RECEIVER, CLASS, PRIVATE_CLASS_ID).PRIVATE_ID`( + { + BASE: file.addHelper("classStaticPrivateFieldBase"), + RECEIVER: member.node.object, + CLASS: classRef, + PRIVATE_CLASS_ID: privateClassId, + PRIVATE_ID: privateId, + }, + ), + ); + }, + }; + function buildClassPropertySpec(ref, path, state) { const { scope } = path; const { key, value, computed } = path.node; @@ -245,6 +274,64 @@ export default declare((api, options) => { }); } + function buildClassStaticPrivatePropertySpec( + ref, + path, + privateClassId, + state, + ) { + const { scope, parentPath } = path; + const { key, value } = path.node; + const { name } = key.id; + const privateId = scope.generateUidIdentifier(name); + + parentPath.traverse(staticPrivatePropertyVisitor, { + name, + privateId, + privateClassId, + classRef: ref, + file: state, + ...staticPrivatePropertyHandler, + }); + + return t.expressionStatement( + t.callExpression(state.addHelper("defineProperty"), [ + privateClassId, + t.stringLiteral(privateId.name), + value || scope.buildUndefinedNode(), + ]), + ); + } + + function buildClassStaticPrivatePropertyLoose( + ref, + path, + privateClassId, + state, + ) { + const { scope, parentPath } = path; + const { key, value } = path.node; + const { name } = key.id; + const privateId = scope.generateUidIdentifier(name); + + parentPath.traverse(staticPrivatePropertyVisitor, { + name, + privateId, + privateClassId, + classRef: ref, + file: state, + ...staticPrivatePropertyHandler, + }); + + return t.expressionStatement( + t.assignmentExpression( + "=", + t.memberExpression(privateClassId, privateId), + value || scope.buildUndefinedNode(), + ), + ); + } + const buildClassProperty = loose ? buildClassPropertyLoose : buildClassPropertySpec; @@ -253,6 +340,10 @@ export default declare((api, options) => { ? buildClassPrivatePropertyLoose : buildClassPrivatePropertySpec; + const buildClassStaticPrivateProperty = loose + ? buildClassStaticPrivatePropertyLoose + : buildClassStaticPrivatePropertySpec; + return { inherits: syntaxClassProperties, @@ -263,6 +354,7 @@ export default declare((api, options) => { const props = []; const computedPaths = []; const privateNames = new Set(); + const privateStaticNames = new Set(); const body = path.get("body"); for (const path of body.get("body")) { @@ -285,14 +377,18 @@ export default declare((api, options) => { } = path.node; if (isStatic) { - throw path.buildCodeFrameError( - "Static class fields are not spec'ed yet.", - ); + if (privateStaticNames.has(name)) { + throw path.buildCodeFrameError( + "Duplicate static private field", + ); + } + privateStaticNames.add(name); + } else { + if (privateNames.has(name)) { + throw path.buildCodeFrameError("Duplicate private field"); + } + privateNames.add(name); } - if (privateNames.has(name)) { - throw path.buildCodeFrameError("Duplicate private field"); - } - privateNames.add(name); } if (path.isProperty()) { @@ -344,7 +440,7 @@ export default declare((api, options) => { const privateMaps = []; const privateMapInits = []; for (const prop of props) { - if (prop.isPrivate()) { + if (prop.isPrivate() && !prop.node.static) { const inits = []; privateMapInits.push(inits); @@ -354,10 +450,35 @@ export default declare((api, options) => { } } + // Create a private static "host" object + const privateClassId = path.scope.generateUidIdentifier( + ref.name + "Statics", + ); + if (privateStaticNames.size > 0) { + staticNodes.push( + template.statement`const PRIVATE_CLASS_ID = {};`({ + PRIVATE_CLASS_ID: privateClassId, + }), + ); + } + let p = 0; for (const prop of props) { if (prop.node.static) { - staticNodes.push(buildClassProperty(t.cloneNode(ref), prop, state)); + if (prop.isPrivate()) { + staticNodes.push( + buildClassStaticPrivateProperty( + t.cloneNode(ref), + prop, + privateClassId, + state, + ), + ); + } else { + staticNodes.push( + buildClassProperty(t.cloneNode(ref), prop, state), + ); + } } else if (prop.isPrivate()) { instanceBody.push(privateMaps[p]()); staticNodes.push(...privateMapInits[p]);