Tan Li Hau d3fe22f0e1 destructuring private fields with array pattern / object patte… (#10017)
* destructuring private fields with array pattern / object pattern

* wip: new test cases

* destrucuring and rest for private properties

* test case for loose private-loose

* add transform-desturcturing for exec

* update test case

* remove getPrototypeOf imports from get and set

* wip: destructure super assignment

* throw "Destructuring to a super field is not supported yet."

* fix tests and fix assignment pattern

* remove CallExpression from AssignmentPattern
2019-07-15 01:00:20 +02:00

150 lines
4.4 KiB
JavaScript

import * as t from "@babel/types";
class AssignmentMemoiser {
constructor() {
this._map = new WeakMap();
}
has(key) {
return this._map.has(key);
}
get(key) {
if (!this.has(key)) return;
const record = this._map.get(key);
const { value } = record;
record.count--;
if (record.count === 0) {
// The `count` access is the outermost function call (hopefully), so it
// does the assignment.
return t.assignmentExpression("=", value, key);
}
return value;
}
set(key, value, count) {
return this._map.set(key, { count, value });
}
}
const handle = {
memoise() {
// noop.
},
handle(member) {
const { node, parent, parentPath } = member;
// MEMBER++ -> _set(MEMBER, (_ref = (+_get(MEMBER))) + 1), _ref
// ++MEMBER -> _set(MEMBER, (+_get(MEMBER)) + 1)
if (parentPath.isUpdateExpression({ argument: node })) {
const { operator, prefix } = parent;
// Give the state handler a chance to memoise the member, since we'll
// reference it twice. The second access (the set) should do the memo
// assignment.
this.memoise(member, 2);
const value = t.binaryExpression(
operator[0],
t.unaryExpression("+", this.get(member)),
t.numericLiteral(1),
);
if (prefix) {
parentPath.replaceWith(this.set(member, value));
} else {
const { scope } = member;
const ref = scope.generateUidIdentifierBasedOnNode(node);
scope.push({ id: ref });
value.left = t.assignmentExpression("=", t.cloneNode(ref), value.left);
parentPath.replaceWith(
t.sequenceExpression([this.set(member, value), t.cloneNode(ref)]),
);
}
return;
}
// MEMBER = VALUE -> _set(MEMBER, VALUE)
// MEMBER += VALUE -> _set(MEMBER, _get(MEMBER) + VALUE)
if (parentPath.isAssignmentExpression({ left: node })) {
const { operator, right } = parent;
let value = right;
if (operator !== "=") {
// Give the state handler a chance to memoise the member, since we'll
// reference it twice. The second access (the set) should do the memo
// assignment.
this.memoise(member, 2);
value = t.binaryExpression(
operator.slice(0, -1),
this.get(member),
value,
);
}
parentPath.replaceWith(this.set(member, value));
return;
}
// MEMBER(ARGS) -> _call(MEMBER, ARGS)
if (parentPath.isCallExpression({ callee: node })) {
const { arguments: args } = parent;
parentPath.replaceWith(this.call(member, args));
return;
}
// { KEY: MEMBER } = OBJ -> { KEY: _destructureSet(MEMBER) } = OBJ
// { KEY: MEMBER = _VALUE } = OBJ -> { KEY: _destructureSet(MEMBER) = _VALUE } = OBJ
// {...MEMBER} -> {..._destructureSet(MEMBER)}
//
// [MEMBER] = ARR -> [_destructureSet(MEMBER)] = ARR
// [MEMBER = _VALUE] = ARR -> [_destructureSet(MEMBER) = _VALUE] = ARR
// [...MEMBER] -> [..._destructureSet(MEMBER)]
if (
// { KEY: MEMBER } = OBJ
(parentPath.isObjectProperty({ value: node }) &&
parentPath.parentPath.isObjectPattern()) ||
// { KEY: MEMBER = _VALUE } = OBJ
(parentPath.isAssignmentPattern({ left: node }) &&
parentPath.parentPath.isObjectProperty({ value: parent }) &&
parentPath.parentPath.parentPath.isObjectPattern()) ||
// [MEMBER] = ARR
parentPath.isArrayPattern() ||
// [MEMBER = _VALUE] = ARR
(parentPath.isAssignmentPattern({ left: node }) &&
parentPath.parentPath.isArrayPattern()) ||
// {...MEMBER}
// [...MEMBER]
parentPath.isRestElement()
) {
member.replaceWith(this.destructureSet(member));
return;
}
// MEMBER -> _get(MEMBER)
member.replaceWith(this.get(member));
},
};
// We do not provide a default traversal visitor
// Instead, caller passes one, and must call `state.handle` on the members
// it wishes to be transformed.
// Additionally, the caller must pass in a state object with at least
// get, set, and call methods.
// Optionally, a memoise method may be defined on the state, which will be
// called when the member is a self-referential update.
export default function memberExpressionToFunctions(path, visitor, state) {
path.traverse(visitor, {
...handle,
...state,
memoiser: new AssignmentMemoiser(),
});
}