* 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
150 lines
4.4 KiB
JavaScript
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(),
|
|
});
|
|
}
|