diff --git a/packages/babel-plugin-transform-optional-chaining/src/index.js b/packages/babel-plugin-transform-optional-chaining/src/index.js index 584294ac54..287f14963d 100644 --- a/packages/babel-plugin-transform-optional-chaining/src/index.js +++ b/packages/babel-plugin-transform-optional-chaining/src/index.js @@ -1,161 +1,105 @@ export default function ({ types: t }) { - const optionalNodesTransformed = new WeakSet(); - const nilIdentifier = t.unaryExpression("void", t.numericLiteral(0)); - - function setOptionalTransformed(node) { - t.assertMemberExpression(node); // Dev - optionalNodesTransformed.add(node); - } - - function isOptionalTransformed(node) { - t.assertMemberExpression(node); // Dev - return optionalNodesTransformed.has(node); - } - - function createCondition(ref, access, nextProperty, bailout) { - - return t.conditionalExpression( - createCheckAgainstNull( - t.AssignmentExpression("=", ref, access) - ), - - t.memberExpression(ref, nextProperty), - bailout, - ); - } - - function createCheckAgainstNull(left) { - return t.BinaryExpression("!=", left, t.NullLiteral()); - } - - function isNodeOptional(node) { - return node.optional === true; - } - - return { - visitor: { - - AssignmentExpression(path, state) { - const { left } = path.node; - - if (!isNodeOptional(left) || isOptionalTransformed(left)) { - return; - } - - if (!state.optionalTemp) { - const id = path.scope.generateUidIdentifier(); - - state.optionalTemp = id; - path.scope.push({ id }); - } - - const { object, property } = left; - - const isChainNil = t.BinaryExpression( - "!=", - createCondition( - state.optionalTemp, - object, - property, - nilIdentifier, - ), - nilIdentifier, - ); - - // FIXME(sven): if will be a ConditionalExpression for childs, only top level will be ifStatement - const replacement = t.ifStatement(isChainNil, t.blockStatement([t.expressionStatement(path.node)])); - - setOptionalTransformed(left); - - path.traverse({ - MemberExpression({ node }) { - setOptionalTransformed(node); - }, - }); - - path.parentPath.replaceWith(replacement); - }, - - UnaryExpression(path, state) { - const { operator, argument } = path.node; - - if (operator !== "delete") { - return; - } - - if (!isNodeOptional(argument) || isOptionalTransformed(argument)) { - return; - } - - if (!state.optionalTemp) { - const id = path.scope.generateUidIdentifier(); - - state.optionalTemp = id; - path.scope.push({ id }); - } - - const { object, property } = argument; - - const isChainNil = t.BinaryExpression( - "!=", - createCondition( - state.optionalTemp, - object, - property, - nilIdentifier, - ), - nilIdentifier, - ); - - const replacement = t.ifStatement(isChainNil, t.blockStatement([t.expressionStatement(path.node)])); - - setOptionalTransformed(argument); - - path.parentPath.replaceWith(replacement); - }, - - MemberExpression(path, state) { - if (!isNodeOptional(path.node) || isOptionalTransformed(path.node)) { - return; - } - - const { object, property } = path.node; - - if (!state.optionalTemp) { - const id = path.scope.generateUidIdentifier(); - - state.optionalTemp = id; - path.scope.push({ id }); - } - - if (t.isAssignmentExpression(path.parent)) { - return; - } else if (t.isUnaryExpression(path.parent)) { - return; - } else if (t.isCallExpression(path.parent)) { - - const replacement = createCondition( - state.optionalTemp, - object, - property, - t.callExpression(t.identifier("Function"), []), - ); - - setOptionalTransformed(path.node); - path.replaceWith(replacement); - } else { - - const replacement = createCondition( - state.optionalTemp, - object, - property, - nilIdentifier, - ); - - setOptionalTransformed(path.node); + // DO NOT SUBMIT. This is until the parser is complete + const fixer = { + NewExpression: { + exit(path) { + const { callee } = path.node; + if (t.isCallExpression(callee)) { + const replacement = t.newExpression(callee.callee, callee.arguments); + replacement.optional = true; path.replaceWith(replacement); } }, }, + + CallExpression(path) { + const { node } = path; + if (!node.optional || node.callee) { + return; + } + + const callee = t.clone(node.arguments[0]); + if (t.isMemberExpression(callee)) { + callee.optional = node.arguments[1].value; + } + node.callee = callee; + }, + }; + // END DO NOT SUBMIT + + const visitor = { + Program(path) { + path.traverse(fixer); + }, + + MemberExpression(path) { + if (!path.node.optional) { + return; + } + + const { scope, node } = path; + const { object } = node; + + node.optional = false; + + const ref = scope.generateUidIdentifierBasedOnNode(object); + scope.push({ id: ref }); + node.object = ref; + + let parent = path; + let expression; + do { + expression = parent; + parent = parent.parentPath; + } while (!parent.container); + + const replace = parent.isExpression() ? parent : expression; + replace.replaceWith(t.conditionalExpression( + t.binaryExpression("==", t.assignmentExpression("=", ref, object), t.nullLiteral()), + scope.buildUndefinedNode(), + replace.node + )); + }, + + "NewExpression|CallExpression": { + exit(path) { + if (!path.node.optional) { + return; + } + + const { scope, node } = path; + const { callee } = node; + + node.optional = false; + + const ref = scope.generateUidIdentifierBasedOnNode(callee); + scope.push({ id: ref }); + node.callee = ref; + + if (t.isMemberExpression(callee) && !t.isNewExpression(node)) { + const context = scope.generateUidIdentifierBasedOnNode(callee.object); + scope.push({ id: context }); + callee.object = t.assignmentExpression("=", context, callee.object); + + node.arguments.unshift(context); + node.callee = t.memberExpression(node.callee, t.identifier("call")); + } + + path.replaceWith(t.conditionalExpression( + t.binaryExpression("==", t.assignmentExpression("=", ref, callee), t.nullLiteral()), + scope.buildUndefinedNode(), + node + )); + }, + }, + + }; + + return { + visitor, + + manipulateOptions(opts, parserOpts) { + parserOpts.plugins.push("optionalChaining"); + }, }; } diff --git a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/assignement/expected.js b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/assignement/expected.js index 28f9f5b1e2..c8ee711d7d 100644 --- a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/assignement/expected.js +++ b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/assignement/expected.js @@ -1,9 +1,5 @@ -var _temp; +var _a, _a$b$c, _a$b, _a2; -if (((_temp = a) != null ? _temp.b : void 0) != void 0) { - a.b = 42; -} +(_a = a) == null ? void 0 : _a.b = 42; -if (((_temp = a.b.c) != null ? _temp.d : void 0) != void 0) { - a.b.c.d = 42; -} +(((_a2 = a) == null ? void 0 : _a$b = _a2.b) == null ? void 0 : _a$b$c = _a$b.c) == null ? void 0 : _a$b$c.d = 42; \ No newline at end of file diff --git a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/delete/expected.js b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/delete/expected.js index a2fd158a7c..1e690c7d45 100644 --- a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/delete/expected.js +++ b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/delete/expected.js @@ -1,9 +1,5 @@ -var _temp; +var _a, _a$b$c, _a$b, _a2; -if (((_temp = a) != null ? _temp.b : void 0) != void 0) { - delete a.b; -} +(_a = a) == null ? void 0 : delete _a.b; -if (((_temp = ((_temp = a) != null ? _temp.b : void 0).c) != null ? _temp.d : void 0) != void 0) { - delete ((_temp = a.b) != null ? _temp.c : void 0).d; -} +(((_a2 = a) == null ? void 0 : _a$b = _a2.b) == null ? void 0 : _a$b$c = _a$b.c) == null ? void 0 : delete _a$b$c.d; \ No newline at end of file diff --git a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/function-call/actual.js b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/function-call/actual.js index 62587bbd25..9499c5f20e 100644 --- a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/function-call/actual.js +++ b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/function-call/actual.js @@ -1 +1,7 @@ +foo?.(foo); + foo?.bar() + +foo.bar?.(foo.bar, false) + +foo?.bar?.(foo.bar, true) diff --git a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/function-call/expected.js b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/function-call/expected.js index 141f7dd896..1df03edc8c 100644 --- a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/function-call/expected.js +++ b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/function-call/expected.js @@ -1,3 +1,9 @@ -var _temp; +var _foo, _foo2, _foo$bar, _foo3, _foo4, _foo4$bar, _foo5; -((_temp = foo) != null ? _temp.bar : Function())(); +(_foo = foo) == null ? void 0 : _foo(foo); + +(_foo2 = foo) == null ? void 0 : _foo2.bar(); + +(_foo$bar = (_foo3 = foo).bar) == null ? void 0 : _foo$bar.call(_foo3, foo.bar, false); + +(_foo4 = foo) == null ? void 0 : (_foo4$bar = (_foo5 = _foo4).bar) == null ? void 0 : _foo4$bar.call(_foo5, foo.bar, true); \ No newline at end of file diff --git a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/member-access/expected.js b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/member-access/expected.js index fe9a91bd04..1c779cf856 100644 --- a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/member-access/expected.js +++ b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/member-access/expected.js @@ -1,5 +1,5 @@ -var _temp; +var _foo, _a$b$c, _a; -(_temp = foo) != null ? _temp.bar : void 0; +(_foo = foo) == null ? void 0 : _foo.bar; -((_temp = ((_temp = a) != null ? _temp.b : void 0).c) != null ? _temp.d : void 0).e; +(_a$b$c = (_a = a) == null ? void 0 : _a.b.c) == null ? void 0 : _a$b$c.d.e; \ No newline at end of file diff --git a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/new/actual.js b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/new/actual.js new file mode 100644 index 0000000000..b937f96491 --- /dev/null +++ b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/new/actual.js @@ -0,0 +1,8 @@ +new a?.b +new a?.b?.c?.d + +new a?.b() +new a?.b?.c?.d() + +new b?.(b) +new a?.b?.(a.b, true) diff --git a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/new/expected.js b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/new/expected.js new file mode 100644 index 0000000000..80b39699a2 --- /dev/null +++ b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/new/expected.js @@ -0,0 +1,10 @@ +var _a, _a$b$c, _a$b, _a2, _a3, _a$b$c2, _a$b2, _a4, _b, _a5, _a5$b; + +(_a = a) == null ? void 0 : new _a.b(); +(((_a2 = a) == null ? void 0 : _a$b = _a2.b) == null ? void 0 : _a$b$c = _a$b.c) == null ? void 0 : new _a$b$c.d(); + +(_a3 = a) == null ? void 0 : new _a3.b(); +(((_a4 = a) == null ? void 0 : _a$b2 = _a4.b) == null ? void 0 : _a$b$c2 = _a$b2.c) == null ? void 0 : new _a$b$c2.d(); + +(_b = b) == null ? void 0 : new _b(b); +(_a5 = a) == null ? void 0 : (_a5$b = _a5.b) == null ? void 0 : new _a5$b(a.b, true); \ No newline at end of file