Microbouji patch/8136 (#9073)
* Fix optional chaining bug regarding spread in function calls * Revamp optional-chain to be top down Instead of going both upwards and downwards from the first real optional expression, we can just start from the top down. * Add more tests
This commit is contained in:
parent
856edbf95f
commit
844dd33f3d
@ -7,136 +7,117 @@ export default declare((api, options) => {
|
|||||||
|
|
||||||
const { loose = false } = options;
|
const { loose = false } = options;
|
||||||
|
|
||||||
function optional(path, replacementPath) {
|
|
||||||
const { scope } = path;
|
|
||||||
const optionals = [];
|
|
||||||
|
|
||||||
let objectPath = path;
|
|
||||||
while (
|
|
||||||
objectPath.isOptionalMemberExpression() ||
|
|
||||||
objectPath.isOptionalCallExpression()
|
|
||||||
) {
|
|
||||||
const { node } = objectPath;
|
|
||||||
if (node.optional) {
|
|
||||||
optionals.push(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (objectPath.isOptionalMemberExpression()) {
|
|
||||||
objectPath.node.type = "MemberExpression";
|
|
||||||
objectPath = objectPath.get("object");
|
|
||||||
} else {
|
|
||||||
objectPath.node.type = "CallExpression";
|
|
||||||
objectPath = objectPath.get("callee");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = optionals.length - 1; i >= 0; i--) {
|
|
||||||
const node = optionals[i];
|
|
||||||
node.optional = false;
|
|
||||||
|
|
||||||
const isCall = t.isCallExpression(node);
|
|
||||||
const replaceKey = isCall ? "callee" : "object";
|
|
||||||
const chain = node[replaceKey];
|
|
||||||
|
|
||||||
let ref;
|
|
||||||
let check;
|
|
||||||
if (loose && isCall) {
|
|
||||||
// If we are using a loose transform (avoiding a Function#call) and we are at the call,
|
|
||||||
// we can avoid a needless memoize.
|
|
||||||
check = ref = chain;
|
|
||||||
} else {
|
|
||||||
ref = scope.maybeGenerateMemoised(chain);
|
|
||||||
if (ref) {
|
|
||||||
check = t.assignmentExpression(
|
|
||||||
"=",
|
|
||||||
t.cloneNode(ref),
|
|
||||||
// Here `chain` MUST NOT be cloned because it could be updated
|
|
||||||
// when generating the memoised context of a call espression
|
|
||||||
chain,
|
|
||||||
);
|
|
||||||
node[replaceKey] = ref;
|
|
||||||
} else {
|
|
||||||
check = ref = chain;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure call expressions have the proper `this`
|
|
||||||
// `foo.bar()` has context `foo`.
|
|
||||||
if (isCall && t.isMemberExpression(chain)) {
|
|
||||||
if (loose) {
|
|
||||||
// To avoid a Function#call, we can instead re-grab the property from the context object.
|
|
||||||
// `a.?b.?()` translates roughly to `_a.b != null && _a.b()`
|
|
||||||
node.callee = chain;
|
|
||||||
} else {
|
|
||||||
// Otherwise, we need to memoize the context object, and change the call into a Function#call.
|
|
||||||
// `a.?b.?()` translates roughly to `(_b = _a.b) != null && _b.call(_a)`
|
|
||||||
const { object } = chain;
|
|
||||||
let context = scope.maybeGenerateMemoised(object);
|
|
||||||
if (context) {
|
|
||||||
chain.object = t.assignmentExpression("=", context, object);
|
|
||||||
} else {
|
|
||||||
context = object;
|
|
||||||
}
|
|
||||||
|
|
||||||
node.arguments.unshift(t.cloneNode(context));
|
|
||||||
node.callee = t.memberExpression(node.callee, t.identifier("call"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
replacementPath.replaceWith(
|
|
||||||
t.conditionalExpression(
|
|
||||||
loose
|
|
||||||
? t.binaryExpression("==", t.cloneNode(check), t.nullLiteral())
|
|
||||||
: t.logicalExpression(
|
|
||||||
"||",
|
|
||||||
t.binaryExpression("===", t.cloneNode(check), t.nullLiteral()),
|
|
||||||
t.binaryExpression(
|
|
||||||
"===",
|
|
||||||
t.cloneNode(ref),
|
|
||||||
scope.buildUndefinedNode(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
scope.buildUndefinedNode(),
|
|
||||||
replacementPath.node,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
replacementPath = replacementPath.get("alternate");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function findReplacementPath(path) {
|
|
||||||
return path.find(path => {
|
|
||||||
const { parentPath } = path;
|
|
||||||
|
|
||||||
if (path.key == "object" && parentPath.isOptionalMemberExpression()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (path.key == "callee" && parentPath.isOptionalCallExpression()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
path.key == "argument" &&
|
|
||||||
parentPath.isUnaryExpression({ operator: "delete" })
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "proposal-optional-chaining",
|
name: "proposal-optional-chaining",
|
||||||
inherits: syntaxOptionalChaining,
|
inherits: syntaxOptionalChaining,
|
||||||
|
|
||||||
visitor: {
|
visitor: {
|
||||||
"OptionalCallExpression|OptionalMemberExpression"(path) {
|
"OptionalCallExpression|OptionalMemberExpression"(path) {
|
||||||
if (!path.node.optional) {
|
const { parentPath, scope } = path;
|
||||||
return;
|
const optionals = [];
|
||||||
|
|
||||||
|
let optionalPath = path;
|
||||||
|
while (
|
||||||
|
optionalPath.isOptionalMemberExpression() ||
|
||||||
|
optionalPath.isOptionalCallExpression()
|
||||||
|
) {
|
||||||
|
const { node } = optionalPath;
|
||||||
|
if (node.optional) {
|
||||||
|
optionals.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optionalPath.isOptionalMemberExpression()) {
|
||||||
|
optionalPath.node.type = "MemberExpression";
|
||||||
|
optionalPath = optionalPath.get("object");
|
||||||
|
} else if (optionalPath.isOptionalCallExpression()) {
|
||||||
|
optionalPath.node.type = "CallExpression";
|
||||||
|
optionalPath = optionalPath.get("callee");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
optional(path, findReplacementPath(path));
|
let replacementPath = path;
|
||||||
|
if (parentPath.isUnaryExpression({ operator: "delete" })) {
|
||||||
|
replacementPath = parentPath;
|
||||||
|
}
|
||||||
|
for (let i = optionals.length - 1; i >= 0; i--) {
|
||||||
|
const node = optionals[i];
|
||||||
|
|
||||||
|
const isCall = t.isCallExpression(node);
|
||||||
|
const replaceKey = isCall ? "callee" : "object";
|
||||||
|
const chain = node[replaceKey];
|
||||||
|
|
||||||
|
let ref;
|
||||||
|
let check;
|
||||||
|
if (loose && isCall) {
|
||||||
|
// If we are using a loose transform (avoiding a Function#call) and we are at the call,
|
||||||
|
// we can avoid a needless memoize.
|
||||||
|
check = ref = chain;
|
||||||
|
} else {
|
||||||
|
ref = scope.maybeGenerateMemoised(chain);
|
||||||
|
if (ref) {
|
||||||
|
check = t.assignmentExpression(
|
||||||
|
"=",
|
||||||
|
t.cloneNode(ref),
|
||||||
|
// Here `chain` MUST NOT be cloned because it could be updated
|
||||||
|
// when generating the memoised context of a call espression
|
||||||
|
chain,
|
||||||
|
);
|
||||||
|
node[replaceKey] = ref;
|
||||||
|
} else {
|
||||||
|
check = ref = chain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure call expressions have the proper `this`
|
||||||
|
// `foo.bar()` has context `foo`.
|
||||||
|
if (isCall && t.isMemberExpression(chain)) {
|
||||||
|
if (loose) {
|
||||||
|
// To avoid a Function#call, we can instead re-grab the property from the context object.
|
||||||
|
// `a.?b.?()` translates roughly to `_a.b != null && _a.b()`
|
||||||
|
node.callee = chain;
|
||||||
|
} else {
|
||||||
|
// Otherwise, we need to memoize the context object, and change the call into a Function#call.
|
||||||
|
// `a.?b.?()` translates roughly to `(_b = _a.b) != null && _b.call(_a)`
|
||||||
|
const { object } = chain;
|
||||||
|
let context = scope.maybeGenerateMemoised(object);
|
||||||
|
if (context) {
|
||||||
|
chain.object = t.assignmentExpression("=", context, object);
|
||||||
|
} else {
|
||||||
|
context = object;
|
||||||
|
}
|
||||||
|
|
||||||
|
node.arguments.unshift(t.cloneNode(context));
|
||||||
|
node.callee = t.memberExpression(
|
||||||
|
node.callee,
|
||||||
|
t.identifier("call"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
replacementPath.replaceWith(
|
||||||
|
t.conditionalExpression(
|
||||||
|
loose
|
||||||
|
? t.binaryExpression("==", t.cloneNode(check), t.nullLiteral())
|
||||||
|
: t.logicalExpression(
|
||||||
|
"||",
|
||||||
|
t.binaryExpression(
|
||||||
|
"===",
|
||||||
|
t.cloneNode(check),
|
||||||
|
t.nullLiteral(),
|
||||||
|
),
|
||||||
|
t.binaryExpression(
|
||||||
|
"===",
|
||||||
|
t.cloneNode(ref),
|
||||||
|
scope.buildUndefinedNode(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
scope.buildUndefinedNode(),
|
||||||
|
replacementPath.node,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
replacementPath = replacementPath.get("alternate");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,7 @@
|
|||||||
|
a?.(...args);
|
||||||
|
|
||||||
|
a?.b(...args);
|
||||||
|
|
||||||
|
a?.b(...args).c;
|
||||||
|
|
||||||
|
a?.b(...args).c(...args);
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"plugins": [
|
||||||
|
"external-helpers",
|
||||||
|
"proposal-optional-chaining",
|
||||||
|
"transform-spread"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
var _a, _a2, _a3, _a4, _a4$b;
|
||||||
|
|
||||||
|
(_a = a) === null || _a === void 0 ? void 0 : _a.apply(void 0, babelHelpers.toConsumableArray(args));
|
||||||
|
(_a2 = a) === null || _a2 === void 0 ? void 0 : _a2.b.apply(_a2, babelHelpers.toConsumableArray(args));
|
||||||
|
(_a3 = a) === null || _a3 === void 0 ? void 0 : _a3.b.apply(_a3, babelHelpers.toConsumableArray(args)).c;
|
||||||
|
(_a4 = a) === null || _a4 === void 0 ? void 0 : (_a4$b = _a4.b.apply(_a4, babelHelpers.toConsumableArray(args))).c.apply(_a4$b, babelHelpers.toConsumableArray(args));
|
||||||
Loading…
x
Reference in New Issue
Block a user