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 Jul 10, 2018
1 parent 19a1705 commit e9c8e56
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 46 deletions.
65 changes: 28 additions & 37 deletions packages/babel-helpers/src/helpers.js
Expand Up @@ -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();
});
};
}
Expand Down Expand Up @@ -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 = {};
Expand All @@ -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++) {
Expand Down Expand Up @@ -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;
}
`;
139 changes: 130 additions & 9 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 @@ -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()) {
Expand Down Expand Up @@ -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);

Expand All @@ -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]);
Expand Down

0 comments on commit e9c8e56

Please sign in to comment.