Skip to content

Commit

Permalink
Handle private access chained on an optional chain
Browse files Browse the repository at this point in the history
See the resolution from tc39/proposal-class-fields#301.
  • Loading branch information
jridgewell committed Mar 12, 2020
1 parent f9d4b79 commit d3ee602
Show file tree
Hide file tree
Showing 116 changed files with 2,935 additions and 178 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
{
"args": [
"src",
"--out-dir",
"lib",
"--copy-files",
"--verbose"
]
"args": ["src", "--out-dir", "lib", "--copy-files", "--verbose"]
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
{
"args": [
"src",
"--out-dir",
"lib",
"--copy-files",
"--verbose"
]
"args": ["src", "--out-dir", "lib", "--copy-files", "--verbose"]
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
{
"args": ["--out-file", "script2.js", "--no-comments", "--minified", "script.js"]
"args": [
"--out-file",
"script2.js",
"--no-comments",
"--minified",
"script.js"
]
}
54 changes: 31 additions & 23 deletions packages/babel-helper-create-class-features-plugin/src/fields.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,12 @@ const privateNameVisitor = {
const { privateNamesMap } = this;
const { node, parentPath } = path;

if (!parentPath.isMemberExpression({ property: node })) return;
if (
!parentPath.isMemberExpression({ property: node }) &&
!parentPath.isOptionalMemberExpression({ property: node })
) {
return;
}
if (!privateNamesMap.has(node.id.name)) return;

this.handle(parentPath);
Expand Down Expand Up @@ -241,18 +246,28 @@ const privateNameHandlerSpec = {
};

const privateNameHandlerLoose = {
handle(member) {
get(member) {
const { privateNamesMap, file } = this;
const { object } = member.node;
const { name } = member.node.property.id;

member.replaceWith(
template.expression`BASE(REF, PROP)[PROP]`({
BASE: file.addHelper("classPrivateFieldLooseBase"),
REF: object,
PROP: privateNamesMap.get(name).id,
}),
);
return template.expression`BASE(REF, PROP)[PROP]`({
BASE: file.addHelper("classPrivateFieldLooseBase"),
REF: object,
PROP: privateNamesMap.get(name).id,
});
},

simpleSet(member) {
return this.get(member);
},

destructureSet(member) {
return this.get(member);
},

call(member, args) {
return t.callExpression(this.get(member), args);
},
};

Expand All @@ -264,21 +279,14 @@ export function transformPrivateNamesUsage(
state,
) {
const body = path.get("body");
const handler = loose ? privateNameHandlerLoose : privateNameHandlerSpec;

if (loose) {
body.traverse(privateNameVisitor, {
privateNamesMap,
file: state,
...privateNameHandlerLoose,
});
} else {
memberExpressionToFunctions(body, privateNameVisitor, {
privateNamesMap,
classRef: ref,
file: state,
...privateNameHandlerSpec,
});
}
memberExpressionToFunctions(body, privateNameVisitor, {
privateNamesMap,
classRef: ref,
file: state,
...handler,
});
}

function buildPrivateFieldInitLoose(ref, prop, privateNamesMap) {
Expand Down
115 changes: 111 additions & 4 deletions packages/babel-helper-member-expression-to-functions/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,113 @@ const handle = {
handle(member) {
const { node, parent, parentPath } = member;

if (member.isOptionalMemberExpression()) {
const root = member.find(({ node, parent, parentPath }) => {
if (parentPath.isOptionalMemberExpression()) {
return parent.optional || parent.object !== node;
}
if (parentPath.isOptionalCallExpression()) {
return parent.optional || parent.callee !== node;
}
return true;
});

const rootParentPath = root.parentPath;
if (
rootParentPath.isUpdateExpression({ argument: node }) ||
rootParentPath.isAssignmentExpression({ left: node })
) {
throw member.buildCodeFrameError(`can't handle assignment`);
}
if (rootParentPath.isUnaryExpression({ operator: "delete" })) {
throw member.buildCodeFrameError(`can't handle delete`);
}

if (node.optional) {
throw member.buildCodeFrameError(
`can't handle '?.' directly before ${node.property.type}`,
);
}

let nearestOptional = member;
for (;;) {
if (nearestOptional.isOptionalMemberExpression()) {
if (nearestOptional.node.optional) break;
nearestOptional = nearestOptional.get("object");
continue;
} else if (nearestOptional.isOptionalCallExpression()) {
if (nearestOptional.node.optional) break;
nearestOptional = nearestOptional.get("object");
continue;
}

throw nearestOptional.buildCodeFrameError(
"failed to find nearest optional",
);
}

const { scope } = member;
const { object } = node;
const baseRef = scope.generateUidIdentifierBasedOnNode(
nearestOptional.node.object,
);
const valueRef = scope.generateUidIdentifierBasedOnNode(object);
scope.push({ id: baseRef });
scope.push({ id: valueRef });

nearestOptional
.get("object")
.replaceWith(
t.assignmentExpression("=", baseRef, nearestOptional.node.object),
);
member.replaceWith(t.memberExpression(valueRef, node.property));

if (parentPath.isOptionalCallExpression({ callee: node })) {
parentPath.replaceWith(this.call(member, parent.arguments));
} else {
member.replaceWith(this.get(member));
}

let regular = member.node;
for (let current = member; current !== root; ) {
const { parentPath, parent } = current;
if (parentPath.isOptionalMemberExpression()) {
regular = t.memberExpression(
regular,
parent.property,
parent.computed,
);
} else {
regular = t.callExpression(regular, parent.arguments);
}
current = parentPath;
}

root.replaceWith(
t.conditionalExpression(
t.sequenceExpression([
t.assignmentExpression("=", valueRef, object),
t.logicalExpression(
"||",
t.binaryExpression("===", baseRef, scope.buildUndefinedNode()),
t.binaryExpression("===", baseRef, t.nullLiteral()),
),
]),
scope.buildUndefinedNode(),
regular,
),
);
return;
}

// MEMBER++ -> _set(MEMBER, (_ref = (+_get(MEMBER))) + 1), _ref
// ++MEMBER -> _set(MEMBER, (+_get(MEMBER)) + 1)
if (parentPath.isUpdateExpression({ argument: node })) {
if (this.simpleSet) {
member.replaceWith(this.simpleSet(member));
return;
}

const { operator, prefix } = parent;

// Give the state handler a chance to memoise the member, since we'll
Expand Down Expand Up @@ -72,6 +176,11 @@ const handle = {
// MEMBER = VALUE -> _set(MEMBER, VALUE)
// MEMBER += VALUE -> _set(MEMBER, _get(MEMBER) + VALUE)
if (parentPath.isAssignmentExpression({ left: node })) {
if (this.simpleSet) {
member.replaceWith(this.simpleSet(member));
return;
}

const { operator, right } = parent;
let value = right;

Expand All @@ -92,11 +201,9 @@ const handle = {
return;
}

// MEMBER(ARGS) -> _call(MEMBER, ARGS)
// MEMBER(ARGS) -> _call(MEMBER, ARGS)
if (parentPath.isCallExpression({ callee: node })) {
const { arguments: args } = parent;

parentPath.replaceWith(this.call(member, args));
parentPath.replaceWith(this.call(member, parent.arguments));
return;
}

Expand Down
5 changes: 3 additions & 2 deletions packages/babel-parser/src/parser/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -610,15 +610,16 @@ export default class ExpressionParser extends LValParser {
node.object = base;
node.property = computed
? this.parseExpression()
: optional
? this.parseIdentifier(true)
: this.parseMaybePrivateName(true);
node.computed = computed;

if (node.property.type === "PrivateName") {
if (node.object.type === "Super") {
this.raise(startPos, Errors.SuperPrivateField);
}
if (optional) {
this.raise(node.property.start, Errors.OptionalChainingNoPrivate);
}
this.classScope.usePrivateName(
node.property.id.name,
node.property.start,
Expand Down
2 changes: 2 additions & 0 deletions packages/babel-parser/src/parser/location.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ export const Errors = Object.freeze({
"await* has been removed from the async functions proposal. Use Promise.all() instead.",
OptionalChainingNoNew:
"constructors in/after an Optional Chain are not allowed",
OptionalChainingNoPrivate:
"Private property access cannot immediately follow an optional chain's `?.`",
OptionalChainingNoTemplate:
"Tagged Template Literals are not allowed in optionalChain",
ParamDupe: "Argument name clash",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
{
"plugins": [
["pipelineOperator", { "proposal": "minimal" }]
]
"plugins": [["pipelineOperator", { "proposal": "minimal" }]]
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"plugins": ["classPrivateProperties", "classPrivateMethods"]
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"plugins": ["classPrivateProperties", "classPrivateMethods"]
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"plugins": ["classPrivateProperties", "classPrivateMethods"]
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"plugins": ["classPrivateProperties", "classPrivateMethods"]
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"plugins": ["classPrivateProperties", "classPrivateMethods"]
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"plugins": ["classPrivateProperties", "classPrivateMethods"]
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"plugins": ["classPrivateProperties", "classPrivateMethods"]
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"plugins": ["classPrivateProperties", "classPrivateMethods"]
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"plugins": ["classPrivateProperties", "classPrivateMethods"]
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"plugins": ["classPrivateProperties", "classPrivateMethods"]
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"plugins": ["classPrivateProperties", "classPrivateMethods"]
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"plugins": ["classPrivateProperties", "classPrivateMethods"]
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"plugins": ["classPrivateProperties", "classPrivateMethods"]
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"plugins": ["classPrivateProperties", "classPrivateMethods"]
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"plugins": ["classPrivateProperties", "classPrivateMethods"]
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"plugins": ["classPrivateProperties", "classPrivateMethods"]
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"plugins": ["classPrivateProperties", "classPrivateMethods"]
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"plugins": ["classPrivateProperties", "classPrivateMethods"]
}
}
Loading

0 comments on commit d3ee602

Please sign in to comment.