diff --git a/packages/babel-helper-member-expression-to-functions/src/index.js b/packages/babel-helper-member-expression-to-functions/src/index.js index 0e5ef5ecb8e1..e14c0abe50ba 100644 --- a/packages/babel-helper-member-expression-to-functions/src/index.js +++ b/packages/babel-helper-member-expression-to-functions/src/index.js @@ -29,6 +29,38 @@ class AssignmentMemoiser { } } +/** + * Test if a NodePath will be cast to boolean when evaluated. + * + * @example + * // returns true + * const nodePathAQDotB = NodePath("if (a?.#b) {}").get("test"); // a?.#b + * willPathCastToBoolean(nodePathAQDotB) + * @example + * // returns false + * willPathCastToBoolean(NodePath("a?.#b")) + * @todo Respect transparent expression wrappers + * @see {@link packages/babel-plugin-proposal-optional-chaining/src/index.js} + * @param {NodePath} path + * @returns {boolean} + */ + +function willPathCastToBoolean(path: NodePath): boolean { + const maybeWrapped = path; + const { node, parentPath } = maybeWrapped; + if (parentPath.isLogicalExpression()) { + const { operator } = parentPath.node; + if (operator === "&&" || operator === "||") { + return willPathCastToBoolean(parentPath); + } + } + return ( + parentPath.isConditional({ test: node }) || + parentPath.isUnaryExpression({ operator: "!" }) || + parentPath.isLoop({ test: node }) + ); +} + function toNonOptional(path, base) { const { node } = path; if (path.isOptionalMemberExpression()) { @@ -129,6 +161,8 @@ const handle = { return; } + const willEndPathCastToBoolean = willPathCastToBoolean(endPath); + const rootParentPath = endPath.parentPath; if ( rootParentPath.isUpdateExpression({ argument: node }) || @@ -238,33 +272,60 @@ const handle = { regular = endParentPath.node; } - replacementPath.replaceWith( - t.conditionalExpression( - t.logicalExpression( - "||", - t.binaryExpression( - "===", - baseNeedsMemoised - ? t.assignmentExpression( - "=", - t.cloneNode(baseRef), - t.cloneNode(startingNode), - ) - : t.cloneNode(baseRef), - t.nullLiteral(), - ), - t.binaryExpression( - "===", - t.cloneNode(baseRef), - scope.buildUndefinedNode(), - ), + if (willEndPathCastToBoolean) { + const nonNullishCheck = t.logicalExpression( + "&&", + t.binaryExpression( + "!==", + baseNeedsMemoised + ? t.assignmentExpression( + "=", + t.cloneNode(baseRef), + t.cloneNode(startingNode), + ) + : t.cloneNode(baseRef), + t.nullLiteral(), ), - isDeleteOperation - ? t.booleanLiteral(true) - : scope.buildUndefinedNode(), - regular, - ), - ); + t.binaryExpression( + "!==", + t.cloneNode(baseRef), + scope.buildUndefinedNode(), + ), + ); + replacementPath.replaceWith( + t.logicalExpression("&&", nonNullishCheck, regular), + ); + } else { + // todo: respect assumptions.noDocumentAll when assumptions are implemented + const nullishCheck = t.logicalExpression( + "||", + t.binaryExpression( + "===", + baseNeedsMemoised + ? t.assignmentExpression( + "=", + t.cloneNode(baseRef), + t.cloneNode(startingNode), + ) + : t.cloneNode(baseRef), + t.nullLiteral(), + ), + t.binaryExpression( + "===", + t.cloneNode(baseRef), + scope.buildUndefinedNode(), + ), + ); + replacementPath.replaceWith( + t.conditionalExpression( + nullishCheck, + isDeleteOperation + ? t.booleanLiteral(true) + : scope.buildUndefinedNode(), + regular, + ), + ); + } // context and isDeleteOperation can not be both truthy if (context) { diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-cast-to-boolean/exec.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-cast-to-boolean/exec.js new file mode 100644 index 000000000000..1c53f4066b61 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-cast-to-boolean/exec.js @@ -0,0 +1,108 @@ +class C { + static #a = { + b: { + c: { + d: 2, + }, + }, + }; + static testIf(o) { + if (o?.#a.b.c.d) { + return true; + } + return false; + } + static testConditional(o) { + return o?.#a.b?.c.d ? true : false; + } + static testLoop(o) { + while (o?.#a.b.c.d) { + for (; o?.#a.b.c?.d; ) { + let i = 0; + do { + i++; + if (i === 2) { + return true; + } + } while (o?.#a.b?.c.d); + } + } + return false; + } + static testNegate(o) { + return !!o?.#a.b?.c.d; + } + static testIfDeep(o) { + if (o.obj?.#a.b?.c.d) { + return true; + } + return false; + } + static testConditionalDeep(o) { + return o.obj?.#a.b?.c.d ? true : false; + } + static testLoopDeep(o) { + while (o.obj?.#a.b.c.d) { + for (; o.obj?.#a.b.c?.d; ) { + let i = 0; + do { + i++; + if (i === 2) { + return true; + } + } while (o.obj?.#a.b?.c.d); + } + } + return false; + } + static testNegateDeep(o) { + return !!o.obj?.#a.b?.c.d; + } + + static testLogicalInIf(o) { + if (o?.#a.b?.c.d && o?.#a?.b.c.d) { + return true; + } + return false; + } + + static testLogicalInReturn(o) { + return o?.#a.b?.c.d && o?.#a?.b.c.d; + } + + static test() { + const c = C; + expect(C.testIf(c)).toBe(true); + expect(C.testConditional(c)).toBe(true); + expect(C.testLoop(c)).toBe(true); + expect(C.testNegate(c)).toBe(true); + + expect(C.testIfDeep({ obj: c })).toBe(true); + expect(C.testConditionalDeep({ obj: c })).toBe(true); + expect(C.testLoopDeep({ obj: c })).toBe(true); + expect(C.testNegateDeep({ obj: c })).toBe(true); + + expect(C.testLogicalInIf(c)).toBe(true); + expect(C.testLogicalInReturn(c)).toBe(2); + } + + static testNullish() { + for (const n of [null, undefined]) { + expect(C.testIf(n)).toBe(false); + expect(C.testConditional(n)).toBe(false); + expect(C.testLoop(n)).toBe(false); + expect(C.testNegate(n)).toBe(false); + + expect(C.testIfDeep({ obj: n })).toBe(false); + expect(C.testConditionalDeep({ obj: n })).toBe(false); + expect(C.testLoopDeep({ obj: n })).toBe(false); + expect(C.testNegateDeep({ obj: n })).toBe(false); + + expect(C.testLogicalInIf(n)).toBe(false); + expect(C.testLogicalInReturn(n)).toBe(undefined); + } + } +} + +C.test(); +C.testNullish(); diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-cast-to-boolean/input.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-cast-to-boolean/input.js new file mode 100644 index 000000000000..b58d13bee0ac --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-cast-to-boolean/input.js @@ -0,0 +1,75 @@ +class C { + static #a = { + b: { + c: { + d: 2, + }, + }, + }; + static testIf(o) { + if (o?.#a.b.c.d) { + return true; + } + return false; + } + static testConditional(o) { + return o?.#a.b?.c.d ? true : false; + } + static testLoop(o) { + while (o?.#a.b.c.d) { + for (; o?.#a.b.c?.d; ) { + let i = 0; + do { + i++; + if (i === 2) { + return true; + } + } while (o?.#a.b?.c.d); + } + } + return false; + } + static testNegate(o) { + return !!o?.#a.b?.c.d; + } + static testIfDeep(o) { + if (o.obj?.#a.b?.c.d) { + return true; + } + return false; + } + static testConditionalDeep(o) { + return o.obj?.#a.b?.c.d ? true : false; + } + static testLoopDeep(o) { + while (o.obj?.#a.b.c.d) { + for (; o.obj?.#a.b.c?.d; ) { + let i = 0; + do { + i++; + if (i === 2) { + return true; + } + } while (o.obj?.#a.b?.c.d); + } + } + return false; + } + static testNegateDeep(o) { + return !!o.obj?.#a.b?.c.d; + } + + static testLogicalInIf(o) { + if (o?.#a.b?.c.d && o?.#a?.b.c.d) { + return true; + } + return false; + } + + static testLogicalInReturn(o) { + return o?.#a.b?.c.d && o?.#a?.b.c.d; + } +} + +C.test(); +C.testNullish(); diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-cast-to-boolean/output.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-cast-to-boolean/output.js new file mode 100644 index 000000000000..95ef49e9d891 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-cast-to-boolean/output.js @@ -0,0 +1,127 @@ +var _a = babelHelpers.classPrivateFieldLooseKey("a"); + +var C = /*#__PURE__*/function () { + "use strict"; + + function C() { + babelHelpers.classCallCheck(this, C); + } + + babelHelpers.createClass(C, null, [{ + key: "testIf", + value: function testIf(o) { + if (o !== null && o !== void 0 && babelHelpers.classPrivateFieldLooseBase(o, _a)[_a].b.c.d) { + return true; + } + + return false; + } + }, { + key: "testConditional", + value: function testConditional(o) { + return (o === null || o === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(o, _a)[_a].b)?.c.d ? true : false; + } + }, { + key: "testLoop", + value: function testLoop(o) { + while (o !== null && o !== void 0 && babelHelpers.classPrivateFieldLooseBase(o, _a)[_a].b.c.d) { + for (; (o === null || o === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(o, _a)[_a].b.c)?.d;) { + var i = 0; + + do { + i++; + + if (i === 2) { + return true; + } + } while ((o === null || o === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(o, _a)[_a].b)?.c.d); + } + } + + return false; + } + }, { + key: "testNegate", + value: function testNegate(o) { + return !!(o === null || o === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(o, _a)[_a].b)?.c.d; + } + }, { + key: "testIfDeep", + value: function testIfDeep(o) { + var _o$obj; + + if (((_o$obj = o.obj) === null || _o$obj === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(_o$obj, _a)[_a].b)?.c.d) { + return true; + } + + return false; + } + }, { + key: "testConditionalDeep", + value: function testConditionalDeep(o) { + var _o$obj2; + + return ((_o$obj2 = o.obj) === null || _o$obj2 === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(_o$obj2, _a)[_a].b)?.c.d ? true : false; + } + }, { + key: "testLoopDeep", + value: function testLoopDeep(o) { + while ((_o$obj3 = o.obj) !== null && _o$obj3 !== void 0 && babelHelpers.classPrivateFieldLooseBase(_o$obj3, _a)[_a].b.c.d) { + var _o$obj3; + + for (; ((_o$obj4 = o.obj) === null || _o$obj4 === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(_o$obj4, _a)[_a].b.c)?.d;) { + var _o$obj4; + + var i = 0; + + do { + var _o$obj5; + + i++; + + if (i === 2) { + return true; + } + } while (((_o$obj5 = o.obj) === null || _o$obj5 === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(_o$obj5, _a)[_a].b)?.c.d); + } + } + + return false; + } + }, { + key: "testNegateDeep", + value: function testNegateDeep(o) { + var _o$obj6; + + return !!((_o$obj6 = o.obj) === null || _o$obj6 === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(_o$obj6, _a)[_a].b)?.c.d; + } + }, { + key: "testLogicalInIf", + value: function testLogicalInIf(o) { + if ((o === null || o === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(o, _a)[_a].b)?.c.d && (o === null || o === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(o, _a)[_a])?.b.c.d) { + return true; + } + + return false; + } + }, { + key: "testLogicalInReturn", + value: function testLogicalInReturn(o) { + return (o === null || o === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(o, _a)[_a].b)?.c.d && (o === null || o === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(o, _a)[_a])?.b.c.d; + } + }]); + return C; +}(); + +Object.defineProperty(C, _a, { + writable: true, + value: { + b: { + c: { + d: 2 + } + } + } +}); +C.test(); +C.testNullish(); diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-cast-to-boolean/exec.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-cast-to-boolean/exec.js new file mode 100644 index 000000000000..1c53f4066b61 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-cast-to-boolean/exec.js @@ -0,0 +1,108 @@ +class C { + static #a = { + b: { + c: { + d: 2, + }, + }, + }; + static testIf(o) { + if (o?.#a.b.c.d) { + return true; + } + return false; + } + static testConditional(o) { + return o?.#a.b?.c.d ? true : false; + } + static testLoop(o) { + while (o?.#a.b.c.d) { + for (; o?.#a.b.c?.d; ) { + let i = 0; + do { + i++; + if (i === 2) { + return true; + } + } while (o?.#a.b?.c.d); + } + } + return false; + } + static testNegate(o) { + return !!o?.#a.b?.c.d; + } + static testIfDeep(o) { + if (o.obj?.#a.b?.c.d) { + return true; + } + return false; + } + static testConditionalDeep(o) { + return o.obj?.#a.b?.c.d ? true : false; + } + static testLoopDeep(o) { + while (o.obj?.#a.b.c.d) { + for (; o.obj?.#a.b.c?.d; ) { + let i = 0; + do { + i++; + if (i === 2) { + return true; + } + } while (o.obj?.#a.b?.c.d); + } + } + return false; + } + static testNegateDeep(o) { + return !!o.obj?.#a.b?.c.d; + } + + static testLogicalInIf(o) { + if (o?.#a.b?.c.d && o?.#a?.b.c.d) { + return true; + } + return false; + } + + static testLogicalInReturn(o) { + return o?.#a.b?.c.d && o?.#a?.b.c.d; + } + + static test() { + const c = C; + expect(C.testIf(c)).toBe(true); + expect(C.testConditional(c)).toBe(true); + expect(C.testLoop(c)).toBe(true); + expect(C.testNegate(c)).toBe(true); + + expect(C.testIfDeep({ obj: c })).toBe(true); + expect(C.testConditionalDeep({ obj: c })).toBe(true); + expect(C.testLoopDeep({ obj: c })).toBe(true); + expect(C.testNegateDeep({ obj: c })).toBe(true); + + expect(C.testLogicalInIf(c)).toBe(true); + expect(C.testLogicalInReturn(c)).toBe(2); + } + + static testNullish() { + for (const n of [null, undefined]) { + expect(C.testIf(n)).toBe(false); + expect(C.testConditional(n)).toBe(false); + expect(C.testLoop(n)).toBe(false); + expect(C.testNegate(n)).toBe(false); + + expect(C.testIfDeep({ obj: n })).toBe(false); + expect(C.testConditionalDeep({ obj: n })).toBe(false); + expect(C.testLoopDeep({ obj: n })).toBe(false); + expect(C.testNegateDeep({ obj: n })).toBe(false); + + expect(C.testLogicalInIf(n)).toBe(false); + expect(C.testLogicalInReturn(n)).toBe(undefined); + } + } +} + +C.test(); +C.testNullish(); diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-cast-to-boolean/input.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-cast-to-boolean/input.js new file mode 100644 index 000000000000..b58d13bee0ac --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-cast-to-boolean/input.js @@ -0,0 +1,75 @@ +class C { + static #a = { + b: { + c: { + d: 2, + }, + }, + }; + static testIf(o) { + if (o?.#a.b.c.d) { + return true; + } + return false; + } + static testConditional(o) { + return o?.#a.b?.c.d ? true : false; + } + static testLoop(o) { + while (o?.#a.b.c.d) { + for (; o?.#a.b.c?.d; ) { + let i = 0; + do { + i++; + if (i === 2) { + return true; + } + } while (o?.#a.b?.c.d); + } + } + return false; + } + static testNegate(o) { + return !!o?.#a.b?.c.d; + } + static testIfDeep(o) { + if (o.obj?.#a.b?.c.d) { + return true; + } + return false; + } + static testConditionalDeep(o) { + return o.obj?.#a.b?.c.d ? true : false; + } + static testLoopDeep(o) { + while (o.obj?.#a.b.c.d) { + for (; o.obj?.#a.b.c?.d; ) { + let i = 0; + do { + i++; + if (i === 2) { + return true; + } + } while (o.obj?.#a.b?.c.d); + } + } + return false; + } + static testNegateDeep(o) { + return !!o.obj?.#a.b?.c.d; + } + + static testLogicalInIf(o) { + if (o?.#a.b?.c.d && o?.#a?.b.c.d) { + return true; + } + return false; + } + + static testLogicalInReturn(o) { + return o?.#a.b?.c.d && o?.#a?.b.c.d; + } +} + +C.test(); +C.testNullish(); diff --git a/packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-cast-to-boolean/output.js b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-cast-to-boolean/output.js new file mode 100644 index 000000000000..c2204496280b --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-cast-to-boolean/output.js @@ -0,0 +1,125 @@ +var C = /*#__PURE__*/function () { + "use strict"; + + function C() { + babelHelpers.classCallCheck(this, C); + } + + babelHelpers.createClass(C, null, [{ + key: "testIf", + value: function testIf(o) { + if (o !== null && o !== void 0 && babelHelpers.classStaticPrivateFieldSpecGet(o, C, _a).b.c.d) { + return true; + } + + return false; + } + }, { + key: "testConditional", + value: function testConditional(o) { + return (o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(o, C, _a).b)?.c.d ? true : false; + } + }, { + key: "testLoop", + value: function testLoop(o) { + while (o !== null && o !== void 0 && babelHelpers.classStaticPrivateFieldSpecGet(o, C, _a).b.c.d) { + for (; (o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(o, C, _a).b.c)?.d;) { + var i = 0; + + do { + i++; + + if (i === 2) { + return true; + } + } while ((o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(o, C, _a).b)?.c.d); + } + } + + return false; + } + }, { + key: "testNegate", + value: function testNegate(o) { + return !!(o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(o, C, _a).b)?.c.d; + } + }, { + key: "testIfDeep", + value: function testIfDeep(o) { + var _o$obj; + + if (((_o$obj = o.obj) === null || _o$obj === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$obj, C, _a).b)?.c.d) { + return true; + } + + return false; + } + }, { + key: "testConditionalDeep", + value: function testConditionalDeep(o) { + var _o$obj2; + + return ((_o$obj2 = o.obj) === null || _o$obj2 === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$obj2, C, _a).b)?.c.d ? true : false; + } + }, { + key: "testLoopDeep", + value: function testLoopDeep(o) { + while ((_o$obj3 = o.obj) !== null && _o$obj3 !== void 0 && babelHelpers.classStaticPrivateFieldSpecGet(_o$obj3, C, _a).b.c.d) { + var _o$obj3; + + for (; ((_o$obj4 = o.obj) === null || _o$obj4 === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$obj4, C, _a).b.c)?.d;) { + var _o$obj4; + + var i = 0; + + do { + var _o$obj5; + + i++; + + if (i === 2) { + return true; + } + } while (((_o$obj5 = o.obj) === null || _o$obj5 === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$obj5, C, _a).b)?.c.d); + } + } + + return false; + } + }, { + key: "testNegateDeep", + value: function testNegateDeep(o) { + var _o$obj6; + + return !!((_o$obj6 = o.obj) === null || _o$obj6 === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(_o$obj6, C, _a).b)?.c.d; + } + }, { + key: "testLogicalInIf", + value: function testLogicalInIf(o) { + if ((o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(o, C, _a).b)?.c.d && (o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(o, C, _a))?.b.c.d) { + return true; + } + + return false; + } + }, { + key: "testLogicalInReturn", + value: function testLogicalInReturn(o) { + return (o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(o, C, _a).b)?.c.d && (o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(o, C, _a))?.b.c.d; + } + }]); + return C; +}(); + +var _a = { + writable: true, + value: { + b: { + c: { + d: 2 + } + } + } +}; +C.test(); +C.testNullish(); diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/cast-to-boolean/exec.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/cast-to-boolean/exec.js new file mode 100644 index 000000000000..c7e855e79489 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/cast-to-boolean/exec.js @@ -0,0 +1,109 @@ +class C { + static testIf(o) { + if (o?.a.b.c.d) { + return true; + } + return false; + } + static testConditional(o) { + return o?.a.b?.c.d ? true : false; + } + static testLoop(o) { + while (o?.a.b.c.d) { + for (; o?.a.b.c?.d; ) { + let i = 0; + do { + i++; + if (i === 2) { + return true; + } + } while (o?.a.b?.c.d); + } + } + return false; + } + static testNegate(o) { + return !!o?.a.b?.c.d; + } + static testIfDeep(o) { + if (o.obj?.a.b?.c.d) { + return true; + } + return false; + } + static testConditionalDeep(o) { + return o.obj?.a.b?.c.d ? true : false; + } + static testLoopDeep(o) { + while (o.obj?.a.b.c.d) { + for (; o.obj?.a.b.c?.d; ) { + let i = 0; + do { + i++; + if (i === 2) { + return true; + } + } while (o.obj?.a.b?.c.d); + } + } + return false; + } + static testNegateDeep(o) { + return !!o.obj?.a.b?.c.d; + } + + static testLogicalInIf(o) { + if (o?.a.b?.c.d && o?.a?.b.c.d) { + return true; + } + return false; + } + + static testLogicalInReturn(o) { + return o?.a.b?.c.d && o?.a?.b.c.d; + } + + static test() { + const c = { + a: { + b: { + c: { + d: 2, + }, + }, + }, + }; + expect(C.testIf(c)).toBe(true); + expect(C.testConditional(c)).toBe(true); + expect(C.testLoop(c)).toBe(true); + expect(C.testNegate(c)).toBe(true); + + expect(C.testIfDeep({ obj: c })).toBe(true); + expect(C.testConditionalDeep({ obj: c })).toBe(true); + expect(C.testLoopDeep({ obj: c })).toBe(true); + expect(C.testNegateDeep({ obj: c })).toBe(true); + + expect(C.testLogicalInIf(c)).toBe(true); + expect(C.testLogicalInReturn(c)).toBe(2); + } + + static testNullish() { + for (const n of [null, undefined]) { + expect(C.testIf(n)).toBe(false); + expect(C.testConditional(n)).toBe(false); + expect(C.testLoop(n)).toBe(false); + expect(C.testNegate(n)).toBe(false); + + expect(C.testIfDeep({ obj: n })).toBe(false); + expect(C.testConditionalDeep({ obj: n })).toBe(false); + expect(C.testLoopDeep({ obj: n })).toBe(false); + expect(C.testNegateDeep({ obj: n })).toBe(false); + + expect(C.testLogicalInIf(n)).toBe(false); + expect(C.testLogicalInReturn(n)).toBe(undefined); + } + } +} + +C.test(); +C.testNullish(); diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/cast-to-boolean/input.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/cast-to-boolean/input.js new file mode 100644 index 000000000000..aa3b561b3c55 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/cast-to-boolean/input.js @@ -0,0 +1,68 @@ +class C { + static testIf(o) { + if (o?.a.b.c.d) { + return true; + } + return false; + } + static testConditional(o) { + return o?.a.b?.c.d ? true : false; + } + static testLoop(o) { + while (o?.a.b.c.d) { + for (; o?.a.b.c?.d; ) { + let i = 0; + do { + i++; + if (i === 2) { + return true; + } + } while (o?.a.b?.c.d); + } + } + return false; + } + static testNegate(o) { + return !!o?.a.b?.c.d; + } + static testIfDeep(o) { + if (o.obj?.a.b?.c.d) { + return true; + } + return false; + } + static testConditionalDeep(o) { + return o.obj?.a.b?.c.d ? true : false; + } + static testLoopDeep(o) { + while (o.obj?.a.b.c.d) { + for (; o.obj?.a.b.c?.d; ) { + let i = 0; + do { + i++; + if (i === 2) { + return true; + } + } while (o.obj?.a.b?.c.d); + } + } + return false; + } + static testNegateDeep(o) { + return !!o.obj?.a.b?.c.d; + } + + static testLogicalInIf(o) { + if (o?.a.b?.c.d && o?.a?.b.c.d) { + return true; + } + return false; + } + + static testLogicalInReturn(o) { + return o?.a.b?.c.d && o?.a?.b.c.d; + } +} + +C.test(); +C.testNullish(); diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/cast-to-boolean/output.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/cast-to-boolean/output.js new file mode 100644 index 000000000000..75c7c73fd950 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/cast-to-boolean/output.js @@ -0,0 +1,109 @@ +class C { + static testIf(o) { + if (o !== null && o !== void 0 && o.a.b.c.d) { + return true; + } + + return false; + } + + static testConditional(o) { + var _o$a$b; + + return o !== null && o !== void 0 && (_o$a$b = o.a.b) !== null && _o$a$b !== void 0 && _o$a$b.c.d ? true : false; + } + + static testLoop(o) { + while (o !== null && o !== void 0 && o.a.b.c.d) { + for (; o !== null && o !== void 0 && (_o$a$b$c = o.a.b.c) !== null && _o$a$b$c !== void 0 && _o$a$b$c.d;) { + var _o$a$b$c; + + let i = 0; + + do { + var _o$a$b2; + + i++; + + if (i === 2) { + return true; + } + } while (o !== null && o !== void 0 && (_o$a$b2 = o.a.b) !== null && _o$a$b2 !== void 0 && _o$a$b2.c.d); + } + } + + return false; + } + + static testNegate(o) { + var _o$a$b3; + + return !!(o !== null && o !== void 0 && (_o$a$b3 = o.a.b) !== null && _o$a$b3 !== void 0 && _o$a$b3.c.d); + } + + static testIfDeep(o) { + var _o$obj, _o$obj$a$b; + + if ((_o$obj = o.obj) !== null && _o$obj !== void 0 && (_o$obj$a$b = _o$obj.a.b) !== null && _o$obj$a$b !== void 0 && _o$obj$a$b.c.d) { + return true; + } + + return false; + } + + static testConditionalDeep(o) { + var _o$obj2, _o$obj2$a$b; + + return (_o$obj2 = o.obj) !== null && _o$obj2 !== void 0 && (_o$obj2$a$b = _o$obj2.a.b) !== null && _o$obj2$a$b !== void 0 && _o$obj2$a$b.c.d ? true : false; + } + + static testLoopDeep(o) { + while ((_o$obj3 = o.obj) !== null && _o$obj3 !== void 0 && _o$obj3.a.b.c.d) { + var _o$obj3; + + for (; (_o$obj4 = o.obj) !== null && _o$obj4 !== void 0 && (_o$obj4$a$b$c = _o$obj4.a.b.c) !== null && _o$obj4$a$b$c !== void 0 && _o$obj4$a$b$c.d;) { + var _o$obj4, _o$obj4$a$b$c; + + let i = 0; + + do { + var _o$obj5, _o$obj5$a$b; + + i++; + + if (i === 2) { + return true; + } + } while ((_o$obj5 = o.obj) !== null && _o$obj5 !== void 0 && (_o$obj5$a$b = _o$obj5.a.b) !== null && _o$obj5$a$b !== void 0 && _o$obj5$a$b.c.d); + } + } + + return false; + } + + static testNegateDeep(o) { + var _o$obj6, _o$obj6$a$b; + + return !!((_o$obj6 = o.obj) !== null && _o$obj6 !== void 0 && (_o$obj6$a$b = _o$obj6.a.b) !== null && _o$obj6$a$b !== void 0 && _o$obj6$a$b.c.d); + } + + static testLogicalInIf(o) { + var _o$a$b4, _o$a; + + if (o !== null && o !== void 0 && (_o$a$b4 = o.a.b) !== null && _o$a$b4 !== void 0 && _o$a$b4.c.d && o !== null && o !== void 0 && (_o$a = o.a) !== null && _o$a !== void 0 && _o$a.b.c.d) { + return true; + } + + return false; + } + + static testLogicalInReturn(o) { + var _o$a$b5, _o$a2; + + return (o === null || o === void 0 ? void 0 : (_o$a$b5 = o.a.b) === null || _o$a$b5 === void 0 ? void 0 : _o$a$b5.c.d) && (o === null || o === void 0 ? void 0 : (_o$a2 = o.a) === null || _o$a2 === void 0 ? void 0 : _o$a2.b.c.d); + } + +} + +C.test(); +C.testNullish();