Skip to content

Commit

Permalink
fix: wrap member chains to IIFE when it is in parameter default
Browse files Browse the repository at this point in the history
  • Loading branch information
JLHwung committed Sep 3, 2020
1 parent 70e6e73 commit 2e08fd0
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 6 deletions.
24 changes: 22 additions & 2 deletions packages/babel-helper-member-expression-to-functions/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const handle = {
},

handle(member) {
const { node, parent, parentPath } = member;
const { node, parent, parentPath, scope } = member;

if (member.isOptionalMemberExpression()) {
// Transforming optional chaining requires we replace ancestors.
Expand Down Expand Up @@ -118,6 +118,17 @@ const handle = {
return true;
});

// Replace `function (a, x = a.b?.#c) {}` to `function (a, x = (() => a.b?.#c)() ){}`
// so the temporary variable can be injected in correct scope
// This can be further optimized to avoid unecessary IIFE
if (scope.path.isPattern()) {
endPath.replaceWith(
// The injected member will be queued and eventually transformed when visited
t.callExpression(t.arrowFunctionExpression([], endPath.node), []),
);
return;
}

const rootParentPath = endPath.parentPath;
if (
rootParentPath.isUpdateExpression({ argument: node }) ||
Expand Down Expand Up @@ -165,7 +176,6 @@ const handle = {
);
}

const { scope } = member;
const startingProp = startingOptional.isOptionalMemberExpression()
? "object"
: "callee";
Expand Down Expand Up @@ -362,6 +372,16 @@ const handle = {

// MEMBER?.(ARGS) -> _optionalCall(MEMBER, ARGS)
if (parentPath.isOptionalCallExpression({ callee: node })) {
// Replace `function (a, x = a.b.#c?.()) {}` to `function (a, x = (() => a.b.#c?.())() ){}`
// so the temporary variable can be injected in correct scope
// This can be further optimized to avoid unecessary IIFE
if (scope.path.isPattern()) {
parentPath.replaceWith(
// The injected member will be queued and eventually transformed when visited
t.callExpression(t.arrowFunctionExpression([], parentPath.node), []),
);
return;
}
parentPath.replaceWith(this.optionalCall(member, parent.arguments));
return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { declare } from "@babel/helper-plugin-utils";
import syntaxNullishCoalescingOperator from "@babel/plugin-syntax-nullish-coalescing-operator";
import { types as t } from "@babel/core";
import { types as t, template } from "@babel/core";

export default declare((api, { loose = false }) => {
api.assertVersion(7);
Expand All @@ -16,13 +16,21 @@ export default declare((api, { loose = false }) => {
return;
}

let ref = scope.maybeGenerateMemoised(node.left);
let ref;
let assignment;
// skip creating extra reference when `left` is static
if (ref === null) {
if (scope.isStatic(node.left)) {
ref = node.left;
assignment = t.cloneNode(node.left);
} else if (scope.path.isPattern()) {
// Replace `function (a, x = a.b ?? c) {}` to `function (a, x = (() => a.b ?? c)() ){}`
// so the temporary variable can be injected in correct scope
path.replaceWith(template.ast`(() => ${path.node})()`);
// The injected nullish expression will be queued and eventually transformed when visited
return;
} else {
ref = scope.generateUidIdentifierBasedOnNode(node.left);
scope.push({ id: t.cloneNode(ref) });
assignment = t.assignmentExpression("=", ref, node.left);
}

Expand Down
33 changes: 32 additions & 1 deletion packages/babel-plugin-proposal-optional-chaining/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
skipTransparentExprWrappers,
} from "@babel/helper-skip-transparent-expression-wrappers";
import syntaxOptionalChaining from "@babel/plugin-syntax-optional-chaining";
import { types as t } from "@babel/core";
import { types as t, template } from "@babel/core";

export default declare((api, options) => {
api.assertVersion(7);
Expand All @@ -22,6 +22,30 @@ export default declare((api, options) => {
);
}

/**
* Test if a given optional chain `path` needs to be memoized
* @param {NodePath} path
* @returns {boolean}
*/
function needsMemoize(path) {
let optionalPath = path;
const { scope } = path;
while (
optionalPath.isOptionalMemberExpression() ||
optionalPath.isOptionalCallExpression()
) {
const { node } = optionalPath;
const childPath = skipTransparentExprWrappers(
optionalPath.get("object") ?? optionalPath.get("callee"),
);
if (node.optional) {
return !scope.isStatic(childPath.node);
}

optionalPath = childPath;
}
}

return {
name: "proposal-optional-chaining",
inherits: syntaxOptionalChaining,
Expand All @@ -46,6 +70,13 @@ export default declare((api, options) => {
const optionals = [];

let optionalPath = path;
// Replace `function (a, x = a.b?.c) {}` to `function (a, x = (() => a.b?.c)() ){}`
// so the temporary variable can be injected in correct scope
if (scope.path.isPattern() && needsMemoize(optionalPath)) {
path.replaceWith(template.ast`(() => ${path.node})()`);
// The injected optional chain will be queued and eventually transformed when visited
return;
}
while (
optionalPath.isOptionalMemberExpression() ||
optionalPath.isOptionalCallExpression()
Expand Down

0 comments on commit 2e08fd0

Please sign in to comment.