diff --git a/packages/babel-plugin-transform-optional-chaining/src/index.js b/packages/babel-plugin-transform-optional-chaining/src/index.js index b856e0d7a7..c2ddf4965b 100644 --- a/packages/babel-plugin-transform-optional-chaining/src/index.js +++ b/packages/babel-plugin-transform-optional-chaining/src/index.js @@ -1,4 +1,15 @@ export default function ({ types: t }) { + const nilIdentifier = t.identifier("undefined"); + + function setOptionalTransformed(node) { + t.assertMemberExpression(node); // Dev + node._optionalTransformed = true; + } + + function isOptionalTransformed(node) { + t.assertMemberExpression(node); // Dev + return node._optionalTransformed === true; + } function createCondition(ref, access, nextProperty, bailout) { @@ -23,8 +34,87 @@ export default function ({ types: t }) { 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 remplacement = t.ifStatement(isChainNil, t.blockStatement([t.expressionStatement(path.node)])); + + setOptionalTransformed(left); + + path.traverse({ + MemberExpression({ node }) { + setOptionalTransformed(node); + }, + }); + + path.parentPath.replaceWith(remplacement); + }, + + 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 remplacement = t.ifStatement(isChainNil, t.blockStatement([t.expressionStatement(path.node)])); + + setOptionalTransformed(argument); + + path.parentPath.replaceWith(remplacement); + }, + MemberExpression(path, state) { - if (!isNodeOptional(path.node)) { + if (!isNodeOptional(path.node) || isOptionalTransformed(path.node)) { return; } @@ -38,15 +128,9 @@ export default function ({ types: t }) { } if (t.isAssignmentExpression(path.parent)) { - - const remplacement = createCondition( - t.identifier("temp_here_please"), - object, - property, - t.identifier("undefined"), - ); - - path.parentPath.replaceWith(remplacement); + return; + } else if (t.isUnaryExpression(path.parent)) { + return; } else if (t.isCallExpression(path.parent)) { const remplacement = createCondition( @@ -56,6 +140,7 @@ export default function ({ types: t }) { t.callExpression(t.identifier("Function"), []), ); + setOptionalTransformed(path.node); path.replaceWith(remplacement); } else { @@ -63,9 +148,10 @@ export default function ({ types: t }) { state.optionalTemp, object, property, - t.identifier("undefined"), + nilIdentifier, ); + setOptionalTransformed(path.node); path.replaceWith(remplacement); } }, diff --git a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/assignement/actual.js b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/assignement/actual.js index bf7a81758e..b9405035c9 100644 --- a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/assignement/actual.js +++ b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/assignement/actual.js @@ -1 +1,3 @@ a?.b = 42 + +a?.b?.c?.d = 42 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 new file mode 100644 index 0000000000..a4c87fdf0c --- /dev/null +++ b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/assignement/expected.js @@ -0,0 +1,9 @@ +var _temp; + +if (((_temp = a) != null ? _temp.b : undefined) != undefined) { + a.b = 42; +} + +if (((_temp = a.b.c) != null ? _temp.d : undefined) != undefined) { + a.b.c.d = 42; +} \ No newline at end of file diff --git a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/delete/actual.js b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/delete/actual.js new file mode 100644 index 0000000000..e75af35f7f --- /dev/null +++ b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/delete/actual.js @@ -0,0 +1,3 @@ +delete a?.b + +delete a?.b?.c?.d 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 new file mode 100644 index 0000000000..ec0bfb91c3 --- /dev/null +++ b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/delete/expected.js @@ -0,0 +1,9 @@ +var _temp; + +if (((_temp = a) != null ? _temp.b : undefined) != undefined) { + delete a.b; +} + +if (((_temp = ((_temp = a) != null ? _temp.b : undefined).c) != null ? _temp.d : undefined) != undefined) { + delete ((_temp = a.b) != null ? _temp.c : undefined).d; +} \ No newline at end of file diff --git a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/member-access/actual.js b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/member-access/actual.js index 6ed5c83b93..83447e76d5 100644 --- a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/member-access/actual.js +++ b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/member-access/actual.js @@ -1 +1,3 @@ foo?.bar + +a?.b.c?.d.e 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 c8e1bdce5e..013c8d6f86 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,3 +1,5 @@ var _temp; -(_temp = foo) != null ? _temp.bar : undefined; \ No newline at end of file +(_temp = foo) != null ? _temp.bar : undefined; + +((_temp = ((_temp = a) != null ? _temp.b : undefined).c) != null ? _temp.d : undefined).e; \ No newline at end of file diff --git a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/nested-member-access/actual.js b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/nested-member-access/actual.js deleted file mode 100644 index f9e4957390..0000000000 --- a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/nested-member-access/actual.js +++ /dev/null @@ -1 +0,0 @@ -a?.b.c?.d.e diff --git a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/nested-member-access/expected.js b/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/nested-member-access/expected.js deleted file mode 100644 index 3e328660a1..0000000000 --- a/packages/babel-plugin-transform-optional-chaining/test/fixtures/general/nested-member-access/expected.js +++ /dev/null @@ -1,3 +0,0 @@ -var _temp; - -((_temp = ((_temp = a) != null ? _temp.b : undefined).c) != null ? _temp.d : undefined).e; \ No newline at end of file