Skip to content

Commit

Permalink
Private Static Class Fields Implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Robin Ricard committed Jun 21, 2018
1 parent 9283efa commit 2824e86
Show file tree
Hide file tree
Showing 51 changed files with 833 additions and 353 deletions.
9 changes: 9 additions & 0 deletions packages/babel-helpers/src/helpers.js
Expand Up @@ -1011,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;
}
`;
146 changes: 131 additions & 15 deletions packages/babel-plugin-proposal-class-properties/src/index.js
Expand Up @@ -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([
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -253,6 +340,10 @@ export default declare((api, options) => {
? buildClassPrivatePropertyLoose
: buildClassPrivatePropertySpec;

const buildClassStaticPrivateProperty = loose
? buildClassStaticPrivatePropertyLoose
: buildClassStaticPrivatePropertySpec;

return {
inherits: syntaxClassProperties,

Expand All @@ -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")) {
Expand All @@ -277,22 +369,21 @@ export default declare((api, options) => {
}

if (path.isClassPrivateProperty()) {
const {
static: isStatic,
key: {
id: { name },
},
} = path.node;
const { static: isStatic, key: { id: { name } } } = 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()) {
Expand Down Expand Up @@ -344,7 +435,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);

Expand All @@ -354,10 +445,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]);
Expand Down

This file was deleted.

@@ -1,18 +1,37 @@
var _foo, _bar;
var Foo =
/*#__PURE__*/
function () {
"use strict";

class Foo {
constructor() {
function Foo() {
babelHelpers.classCallCheck(this, Foo);
Object.defineProperty(this, _bar, {
writable: true,
value: "bar"
});
}

}
babelHelpers.createClass(Foo, [{
key: "test",
value: function test() {
return babelHelpers.classPrivateFieldLooseBase(this, _bar)[_bar];
}
}], [{
key: "test",
value: function test() {
return babelHelpers.classStaticPrivateFieldBase(Foo, Foo, _FooStatics)._foo;
}
}]);
return Foo;
}();

_foo = babelHelpers.classPrivateFieldKey("foo");
Object.defineProperty(Foo, _foo, {
writable: true,
value: "foo"
});
_bar = babelHelpers.classPrivateFieldKey("bar");
var _FooStatics = {};
_FooStatics._foo = "foo";

var _bar = babelHelpers.classPrivateFieldLooseKey("bar");

var f = new Foo();
expect("foo" in Foo).toBe(false);
expect("bar" in f).toBe(false);
expect(Foo.test()).toBe("foo");
expect(f.test()).toBe("bar");

This file was deleted.

@@ -1,25 +1,28 @@
export default (param => {
var _props, _class, _temp;
var _class, _temp;

return _temp = _class =
/*#__PURE__*/
function () {
function App() {
babelHelpers.classCallCheck(this, App);
}

babelHelpers.createClass(App, [{
key: "getParam",
value: function getParam() {
return param;
return function () {
_temp = _class =
/*#__PURE__*/
function () {
function App() {
babelHelpers.classCallCheck(this, App);
}
}]);
return App;
}(), _props = babelHelpers.classPrivateFieldKey("props"), Object.defineProperty(_class, _props, {
writable: true,
value: {

babelHelpers.createClass(App, [{
key: "getParam",
value: function getParam() {
return param;
}
}]);
return App;
}();

var _classStatics = {};
_classStatics._props = {
prop1: 'prop1',
prop2: 'prop2'
}
}), _temp;
};
return _temp;
}();
});

This file was deleted.

0 comments on commit 2824e86

Please sign in to comment.