From e7758dd3256f337eddb40fc672ef3a634e8ba276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 30 Oct 2020 18:12:01 -0400 Subject: [PATCH 1/8] optimize optional chain when expression will be cast to bool --- .../src/index.js | 79 ++++++++----- .../fixtures/loose/cast-to-boolean/exec.js | 109 ++++++++++++++++++ .../fixtures/loose/cast-to-boolean/input.js | 68 +++++++++++ .../fixtures/loose/cast-to-boolean/output.js | 109 ++++++++++++++++++ .../test/fixtures/loose/options.json | 3 + 5 files changed, 341 insertions(+), 27 deletions(-) create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/exec.js create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/input.js create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/output.js create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/options.json diff --git a/packages/babel-plugin-proposal-optional-chaining/src/index.js b/packages/babel-plugin-proposal-optional-chaining/src/index.js index f9f8f99ab84d..67de19513855 100644 --- a/packages/babel-plugin-proposal-optional-chaining/src/index.js +++ b/packages/babel-plugin-proposal-optional-chaining/src/index.js @@ -6,6 +6,26 @@ import { import syntaxOptionalChaining from "@babel/plugin-syntax-optional-chaining"; import { types as t, template } from "@babel/core"; +const { ast } = template.expression; + +function willPathCastToBoolean(path: NodePath) { + const { node, parentPath } = path; + if (node === undefined) { + return false; + } + if (parentPath.isLogicalExpression()) { + const { operator } = parentPath.node; + if (operator === "&&" || operator === "||") { + return willPathCastToBoolean(skipTransparentExprWrappers(parentPath)); + } + } + return ( + parentPath.isConditional({ test: node }) || + parentPath.isUnaryExpression({ operator: "!" }) || + parentPath.isLoop({ test: node }) + ); +} + export default declare((api, options) => { api.assertVersion(7); @@ -61,6 +81,9 @@ export default declare((api, options) => { if (!isTransparentExprWrapper(p)) return true; maybeWrapped = p; }); + const willReplacementCastToBoolean = willPathCastToBoolean( + maybeWrapped, + ); let isDeleteOperation = false; const parentIsCall = parentPath.isCallExpression({ callee: maybeWrapped.node }) && @@ -202,33 +225,35 @@ export default declare((api, options) => { [t.cloneNode(baseRef ?? object)], ); } - 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(), - ), - ), - isDeleteOperation - ? t.booleanLiteral(true) - : scope.buildUndefinedNode(), - replacement, - ), - ); - - replacementPath = skipTransparentExprWrappers( - replacementPath.get("alternate"), - ); + + if (willReplacementCastToBoolean) { + // `if (a?.b) {}` transformed to `if (a != null && a.b) {}` + // we don't need to return `void 0` because the returned value will + // eveutally cast to boolean. + const nonNullishCheck = loose + ? ast`${t.cloneNode(check)} != null` + : ast` + ${t.cloneNode(check)} !== null && ${t.cloneNode(ref)} !== void 0`; + replacementPath.replaceWith( + t.logicalExpression("&&", nonNullishCheck, replacement), + ); + replacementPath = skipTransparentExprWrappers( + replacementPath.get("right"), + ); + } else { + const nullishCheck = loose + ? ast`${t.cloneNode(check)} == null` + : ast` + ${t.cloneNode(check)} === null || ${t.cloneNode(ref)} === void 0`; + + const returnValue = isDeleteOperation ? ast`true` : ast`void 0`; + replacementPath.replaceWith( + t.conditionalExpression(nullishCheck, returnValue, replacement), + ); + replacementPath = skipTransparentExprWrappers( + replacementPath.get("alternate"), + ); + } } }, }, diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/exec.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/exec.js new file mode 100644 index 000000000000..c7e855e79489 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/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/loose/cast-to-boolean/input.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/input.js new file mode 100644 index 000000000000..aa3b561b3c55 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/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/loose/cast-to-boolean/output.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/output.js new file mode 100644 index 000000000000..29b086c1368a --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/output.js @@ -0,0 +1,109 @@ +class C { + static testIf(o) { + if (o != null && o.a.b.c.d) { + return true; + } + + return false; + } + + static testConditional(o) { + var _o$a$b; + + return o != null && (_o$a$b = o.a.b) != null && _o$a$b.c.d ? true : false; + } + + static testLoop(o) { + while (o != null && o.a.b.c.d) { + for (; o != null && (_o$a$b$c = o.a.b.c) != null && _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$a$b2 = o.a.b) != null && _o$a$b2.c.d); + } + } + + return false; + } + + static testNegate(o) { + var _o$a$b3; + + return !!(o != null && (_o$a$b3 = o.a.b) != null && _o$a$b3.c.d); + } + + static testIfDeep(o) { + var _o$obj, _o$obj$a$b; + + if ((_o$obj = o.obj) != null && (_o$obj$a$b = _o$obj.a.b) != null && _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$a$b = _o$obj2.a.b) != null && _o$obj2$a$b.c.d ? true : false; + } + + static testLoopDeep(o) { + while ((_o$obj3 = o.obj) != null && _o$obj3.a.b.c.d) { + var _o$obj3; + + for (; (_o$obj4 = o.obj) != null && (_o$obj4$a$b$c = _o$obj4.a.b.c) != null && _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$a$b = _o$obj5.a.b) != null && _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$a$b = _o$obj6.a.b) != null && _o$obj6$a$b.c.d); + } + + static testLogicalInIf(o) { + var _o$a$b4, _o$a; + + if (o != null && (_o$a$b4 = o.a.b) != null && _o$a$b4.c.d && o != null && (_o$a = o.a) != null && _o$a.b.c.d) { + return true; + } + + return false; + } + + static testLogicalInReturn(o) { + var _o$a$b5, _o$a2; + + return (o == null ? void 0 : (_o$a$b5 = o.a.b) == null ? void 0 : _o$a$b5.c.d) && (o == null ? void 0 : (_o$a2 = o.a) == null ? void 0 : _o$a2.b.c.d); + } + +} + +C.test(); +C.testNullish(); diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/options.json b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/options.json new file mode 100644 index 000000000000..39ea3f99c7ff --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/options.json @@ -0,0 +1,3 @@ +{ + "plugins": [["proposal-optional-chaining", { "loose": true }]] +} From 345f6fbb0d75b4418a3a61aa5f6cf8a2fc73e922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Mon, 2 Nov 2020 11:47:35 -0500 Subject: [PATCH 2/8] fix: willPathCastToBoolean should respect transparent expression wrappers --- .../src/index.js | 51 +++++++++++++++---- .../transparent-expr-wrappers/options.json | 2 +- .../ts-as-call-context-in-if/input.ts | 3 ++ .../ts-as-call-context-in-if/output.js | 5 ++ .../ts-as-call-context/options.json | 10 ---- .../ts-as-in-conditional/input.ts | 3 ++ .../ts-as-in-conditional/output.js | 5 ++ .../ts-as-member-expression/options.json | 10 ---- .../options.json | 10 ---- 9 files changed, 57 insertions(+), 42 deletions(-) create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-call-context-in-if/input.ts create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-call-context-in-if/output.js delete mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-call-context/options.json create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-in-conditional/input.ts create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-in-conditional/output.js delete mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-member-expression/options.json delete mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-parenthesized-expression-member-call/options.json diff --git a/packages/babel-plugin-proposal-optional-chaining/src/index.js b/packages/babel-plugin-proposal-optional-chaining/src/index.js index 67de19513855..91b0a5147fd1 100644 --- a/packages/babel-plugin-proposal-optional-chaining/src/index.js +++ b/packages/babel-plugin-proposal-optional-chaining/src/index.js @@ -8,15 +8,28 @@ import { types as t, template } from "@babel/core"; const { ast } = template.expression; -function willPathCastToBoolean(path: NodePath) { - const { node, parentPath } = path; - if (node === undefined) { - return false; - } +/** + * Test if a NodePath will be cast to boolean when evaluated. + * It respects transparent expression wrappers defined in + * "@babel/helper-skip-transparent-expression-wrappers" + * + * @example + * // returns true + * const nodePathADotB = NodePath("if (a.b) {}").get("test"); // a.b + * willPathCastToBoolean(nodePathADotB) + * @example + * // returns false + * willPathCastToBoolean(NodePath("a.b")) + * @param {NodePath} path + * @returns {boolean} + */ +function willPathCastToBoolean(path: NodePath): boolean { + const maybeWrapped = findOutermostTransparentParent(path); + const { node, parentPath } = maybeWrapped; if (parentPath.isLogicalExpression()) { const { operator } = parentPath.node; if (operator === "&&" || operator === "||") { - return willPathCastToBoolean(skipTransparentExprWrappers(parentPath)); + return willPathCastToBoolean(parentPath); } } return ( @@ -26,6 +39,25 @@ function willPathCastToBoolean(path: NodePath) { ); } +/** + * Return the outermost transparent expression wrapper of a given path, + * otherwise returns path itself. + * @example + * const nodePathADotB = NodePath("(a.b as any)").get("expression"); // a.b + * // returns NodePath("(a.b as any)") + * findOutermostTransparentParent(nodePathADotB); + * @param {NodePath} path + * @returns {NodePath} + */ +function findOutermostTransparentParent(path: NodePath): NodePath { + let maybeWrapped = path; + path.findParent(p => { + if (!isTransparentExprWrapper(p)) return true; + maybeWrapped = p; + }); + return maybeWrapped; +} + export default declare((api, options) => { api.assertVersion(7); @@ -76,11 +108,8 @@ export default declare((api, options) => { const { scope } = path; // maybeWrapped points to the outermost transparent expression wrapper // or the path itself - let maybeWrapped = path; - const parentPath = path.findParent(p => { - if (!isTransparentExprWrapper(p)) return true; - maybeWrapped = p; - }); + const maybeWrapped = findOutermostTransparentParent(path); + const { parentPath } = maybeWrapped; const willReplacementCastToBoolean = willPathCastToBoolean( maybeWrapped, ); diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/options.json b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/options.json index 43d8f4d114cc..3316439b95ea 100644 --- a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/options.json +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/options.json @@ -1,3 +1,3 @@ { - "plugins": ["proposal-optional-chaining"] + "plugins": ["proposal-optional-chaining", "syntax-typescript"] } diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-call-context-in-if/input.ts b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-call-context-in-if/input.ts new file mode 100644 index 000000000000..9605582089e3 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-call-context-in-if/input.ts @@ -0,0 +1,3 @@ +(a) => { + if ((a.b as any)?.()) {} +} diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-call-context-in-if/output.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-call-context-in-if/output.js new file mode 100644 index 000000000000..a40c6ab306cb --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-call-context-in-if/output.js @@ -0,0 +1,5 @@ +a => { + var _a$b; + + if ((_a$b = (a.b as any)) !== null && _a$b !== void 0 && _a$b.call(a)) {} +}; diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-call-context/options.json b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-call-context/options.json deleted file mode 100644 index 3d509031920b..000000000000 --- a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-call-context/options.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "plugins": [ - [ - "syntax-typescript" - ], - [ - "proposal-optional-chaining" - ] - ] -} diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-in-conditional/input.ts b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-in-conditional/input.ts new file mode 100644 index 000000000000..e84ec4e9d9a6 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-in-conditional/input.ts @@ -0,0 +1,3 @@ +(a) => { + (((a?.b as any) && a.c?.d) as any) ? 0 : 1 +} diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-in-conditional/output.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-in-conditional/output.js new file mode 100644 index 000000000000..5dc63a91c68d --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-in-conditional/output.js @@ -0,0 +1,5 @@ +a => { + var _a$c; + + ((a !== null && a !== void 0 && a.b as any) && (_a$c = a.c) !== null && _a$c !== void 0 && _a$c.d as any) ? 0 : 1; +}; diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-member-expression/options.json b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-member-expression/options.json deleted file mode 100644 index 3d509031920b..000000000000 --- a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-member-expression/options.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "plugins": [ - [ - "syntax-typescript" - ], - [ - "proposal-optional-chaining" - ] - ] -} diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-parenthesized-expression-member-call/options.json b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-parenthesized-expression-member-call/options.json deleted file mode 100644 index 00511863bbd0..000000000000 --- a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-parenthesized-expression-member-call/options.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "plugins": [ - [ - "syntax-typescript" - ], - [ - "proposal-optional-chaining" - ] - ] -} From 28a3b43791219a76554ffd3348fad4d11f660e49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Mon, 2 Nov 2020 12:08:56 -0500 Subject: [PATCH 3/8] feat: optimize for private member get --- .../src/index.js | 113 ++++++++++++++---- .../optional-chain-cast-to-boolean/exec.js | 108 +++++++++++++++++ .../optional-chain-cast-to-boolean/input.js | 75 ++++++++++++ .../options.json | 4 + .../optional-chain-cast-to-boolean/output.js | 109 +++++++++++++++++ .../optional-chain-cast-to-boolean/exec.js | 108 +++++++++++++++++ .../optional-chain-cast-to-boolean/input.js | 75 ++++++++++++ .../options.json | 4 + .../optional-chain-cast-to-boolean/output.js | 107 +++++++++++++++++ .../fixtures/general/cast-to-boolean/exec.js | 109 +++++++++++++++++ .../fixtures/general/cast-to-boolean/input.js | 68 +++++++++++ .../general/cast-to-boolean/output.js | 109 +++++++++++++++++ 12 files changed, 963 insertions(+), 26 deletions(-) create mode 100644 packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-cast-to-boolean/exec.js create mode 100644 packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-cast-to-boolean/input.js create mode 100644 packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-cast-to-boolean/options.json create mode 100644 packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-cast-to-boolean/output.js create mode 100644 packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-cast-to-boolean/exec.js create mode 100644 packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-cast-to-boolean/input.js create mode 100644 packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-cast-to-boolean/options.json create mode 100644 packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-cast-to-boolean/output.js create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/cast-to-boolean/exec.js create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/cast-to-boolean/input.js create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/cast-to-boolean/output.js 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/options.json b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-cast-to-boolean/options.json new file mode 100644 index 000000000000..ee8a5dac8ccf --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-cast-to-boolean/options.json @@ -0,0 +1,4 @@ +{ + "plugins": [["external-helpers", { "helperVersion": "7.100.0" }], ["proposal-class-properties", {"loose": true }]], + "minNodeVersion": "14.0.0" +} 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..eddfa4c275ec --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private-loose/optional-chain-cast-to-boolean/output.js @@ -0,0 +1,109 @@ +var _a = babelHelpers.classPrivateFieldLooseKey("a"); + +class C { + static testIf(o) { + if (o !== null && o !== void 0 && babelHelpers.classPrivateFieldLooseBase(o, _a)[_a].b.c.d) { + return true; + } + + return false; + } + + static testConditional(o) { + return (o === null || o === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(o, _a)[_a].b)?.c.d ? true : false; + } + + static 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;) { + let 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; + } + + static testNegate(o) { + return !!(o === null || o === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(o, _a)[_a].b)?.c.d; + } + + static 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; + } + + static 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; + } + + static 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; + + let 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; + } + + static 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; + } + + static 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; + } + + static 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; + } + +} + +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/options.json b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-cast-to-boolean/options.json new file mode 100644 index 000000000000..e7e7eecfe816 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-cast-to-boolean/options.json @@ -0,0 +1,4 @@ +{ + "plugins": [["external-helpers", { "helperVersion": "7.100.0" }], "proposal-class-properties"], + "minNodeVersion": "14.0.0" +} 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..2d5921667e34 --- /dev/null +++ b/packages/babel-plugin-proposal-class-properties/test/fixtures/private/optional-chain-cast-to-boolean/output.js @@ -0,0 +1,107 @@ +class C { + static testIf(o) { + if (o !== null && o !== void 0 && babelHelpers.classStaticPrivateFieldSpecGet(o, C, _a).b.c.d) { + return true; + } + + return false; + } + + static testConditional(o) { + return (o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(o, C, _a).b)?.c.d ? true : false; + } + + static 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;) { + let 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; + } + + static testNegate(o) { + return !!(o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(o, C, _a).b)?.c.d; + } + + static 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; + } + + static 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; + } + + static 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; + + let 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; + } + + static 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; + } + + static 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; + } + + static 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; + } + +} + +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(); From a01800dd425be7be7fcbd126ee88e38f2110ceb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Thu, 12 Nov 2020 10:45:36 -0500 Subject: [PATCH 4/8] add tests on nullish coalescing --- .../optional-chain-cast-to-boolean/exec.js | 11 +++++++++++ .../optional-chain-cast-to-boolean/input.js | 7 +++++++ .../optional-chain-cast-to-boolean/output.js | 8 ++++++++ .../private/optional-chain-cast-to-boolean/exec.js | 11 +++++++++++ .../private/optional-chain-cast-to-boolean/input.js | 7 +++++++ .../private/optional-chain-cast-to-boolean/output.js | 8 ++++++++ .../test/fixtures/general/cast-to-boolean/exec.js | 11 +++++++++++ .../test/fixtures/general/cast-to-boolean/input.js | 7 +++++++ .../test/fixtures/general/cast-to-boolean/output.js | 12 ++++++++++++ .../test/fixtures/loose/cast-to-boolean/exec.js | 11 +++++++++++ .../test/fixtures/loose/cast-to-boolean/input.js | 7 +++++++ .../test/fixtures/loose/cast-to-boolean/output.js | 12 ++++++++++++ 12 files changed, 112 insertions(+) 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 index 1c53f4066b61..c035e0eb1ddd 100644 --- 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 @@ -70,6 +70,13 @@ class C { return o?.#a.b?.c.d && o?.#a?.b.c.d; } + static testNullishCoalescing(o) { + if (o?.#a.b?.c.non_existent ?? o?.#a.b?.c.d) { + return o?.#a.b?.c.non_existent ?? o?.#a.b?.c.d; + } + return o?.#a.b?.c.non_existent ?? o; + } + static test() { const c = C; expect(C.testIf(c)).toBe(true); @@ -84,6 +91,8 @@ class C { expect(C.testLogicalInIf(c)).toBe(true); expect(C.testLogicalInReturn(c)).toBe(2); + + expect(C.testNullishCoalescing(c)).toBe(2); } static testNullish() { @@ -100,6 +109,8 @@ class C { expect(C.testLogicalInIf(n)).toBe(false); expect(C.testLogicalInReturn(n)).toBe(undefined); + + expect(C.testNullishCoalescing(n)).toBe(n); } } } 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 index b58d13bee0ac..f80f89dd7a89 100644 --- 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 @@ -69,6 +69,13 @@ class C { static testLogicalInReturn(o) { return o?.#a.b?.c.d && o?.#a?.b.c.d; } + + static testNullishCoalescing(o) { + if (o?.#a.b?.c.non_existent ?? o?.#a.b?.c.d) { + return o?.#a.b?.c.non_existent ?? o?.#a.b?.c.d; + } + return o?.#a.b?.c.non_existent ?? o; + } } C.test(); 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 index eddfa4c275ec..f5aaf8eabf66 100644 --- 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 @@ -93,6 +93,14 @@ class C { 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; } + static testNullishCoalescing(o) { + if ((o === null || o === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(o, _a)[_a].b)?.c.non_existent ?? (o === null || o === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(o, _a)[_a].b)?.c.d) { + return (o === null || o === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(o, _a)[_a].b)?.c.non_existent ?? (o === null || o === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(o, _a)[_a].b)?.c.d; + } + + return (o === null || o === void 0 ? void 0 : babelHelpers.classPrivateFieldLooseBase(o, _a)[_a].b)?.c.non_existent ?? o; + } + } Object.defineProperty(C, _a, { 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 index 1c53f4066b61..c035e0eb1ddd 100644 --- 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 @@ -70,6 +70,13 @@ class C { return o?.#a.b?.c.d && o?.#a?.b.c.d; } + static testNullishCoalescing(o) { + if (o?.#a.b?.c.non_existent ?? o?.#a.b?.c.d) { + return o?.#a.b?.c.non_existent ?? o?.#a.b?.c.d; + } + return o?.#a.b?.c.non_existent ?? o; + } + static test() { const c = C; expect(C.testIf(c)).toBe(true); @@ -84,6 +91,8 @@ class C { expect(C.testLogicalInIf(c)).toBe(true); expect(C.testLogicalInReturn(c)).toBe(2); + + expect(C.testNullishCoalescing(c)).toBe(2); } static testNullish() { @@ -100,6 +109,8 @@ class C { expect(C.testLogicalInIf(n)).toBe(false); expect(C.testLogicalInReturn(n)).toBe(undefined); + + expect(C.testNullishCoalescing(n)).toBe(n); } } } 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 index b58d13bee0ac..f80f89dd7a89 100644 --- 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 @@ -69,6 +69,13 @@ class C { static testLogicalInReturn(o) { return o?.#a.b?.c.d && o?.#a?.b.c.d; } + + static testNullishCoalescing(o) { + if (o?.#a.b?.c.non_existent ?? o?.#a.b?.c.d) { + return o?.#a.b?.c.non_existent ?? o?.#a.b?.c.d; + } + return o?.#a.b?.c.non_existent ?? o; + } } C.test(); 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 index 2d5921667e34..891e6e20bc16 100644 --- 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 @@ -91,6 +91,14 @@ class C { 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; } + static testNullishCoalescing(o) { + if ((o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(o, C, _a).b)?.c.non_existent ?? (o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(o, C, _a).b)?.c.d) { + return (o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(o, C, _a).b)?.c.non_existent ?? (o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(o, C, _a).b)?.c.d; + } + + return (o === null || o === void 0 ? void 0 : babelHelpers.classStaticPrivateFieldSpecGet(o, C, _a).b)?.c.non_existent ?? o; + } + } var _a = { 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 index c7e855e79489..77ae4c0a0706 100644 --- 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 @@ -63,6 +63,13 @@ class C { return o?.a.b?.c.d && o?.a?.b.c.d; } + static testNullishCoalescing(o) { + if (o?.a.b?.c.non_existent ?? o?.a.b?.c.d) { + return o?.a.b?.c.non_existent ?? o?.a.b?.c.d; + } + return o?.a.b?.c.non_existent ?? o; + } + static test() { const c = { a: { @@ -85,6 +92,8 @@ class C { expect(C.testLogicalInIf(c)).toBe(true); expect(C.testLogicalInReturn(c)).toBe(2); + + expect(C.testNullishCoalescing(c)).toBe(2); } static testNullish() { @@ -101,6 +110,8 @@ class C { expect(C.testLogicalInIf(n)).toBe(false); expect(C.testLogicalInReturn(n)).toBe(undefined); + + expect(C.testNullishCoalescing(n)).toBe(n); } } } 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 index aa3b561b3c55..a9aab8bee395 100644 --- 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 @@ -62,6 +62,13 @@ class C { static testLogicalInReturn(o) { return o?.a.b?.c.d && o?.a?.b.c.d; } + + static testNullishCoalescing(o) { + if (o?.a.b?.c.non_existent ?? o?.a.b?.c.d) { + return o?.a.b?.c.non_existent ?? o?.a.b?.c.d; + } + return o?.a.b?.c.non_existent ?? o; + } } C.test(); 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 index 75c7c73fd950..63cd8798aeaf 100644 --- 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 @@ -103,6 +103,18 @@ class C { 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); } + static testNullishCoalescing(o) { + var _o$a$b6, _o$a$b7, _o$a$b10; + + if ((o === null || o === void 0 ? void 0 : (_o$a$b6 = o.a.b) === null || _o$a$b6 === void 0 ? void 0 : _o$a$b6.c.non_existent) ?? (o === null || o === void 0 ? void 0 : (_o$a$b7 = o.a.b) === null || _o$a$b7 === void 0 ? void 0 : _o$a$b7.c.d)) { + var _o$a$b8, _o$a$b9; + + return (o === null || o === void 0 ? void 0 : (_o$a$b8 = o.a.b) === null || _o$a$b8 === void 0 ? void 0 : _o$a$b8.c.non_existent) ?? (o === null || o === void 0 ? void 0 : (_o$a$b9 = o.a.b) === null || _o$a$b9 === void 0 ? void 0 : _o$a$b9.c.d); + } + + return (o === null || o === void 0 ? void 0 : (_o$a$b10 = o.a.b) === null || _o$a$b10 === void 0 ? void 0 : _o$a$b10.c.non_existent) ?? o; + } + } C.test(); diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/exec.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/exec.js index c7e855e79489..77ae4c0a0706 100644 --- a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/exec.js +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/exec.js @@ -63,6 +63,13 @@ class C { return o?.a.b?.c.d && o?.a?.b.c.d; } + static testNullishCoalescing(o) { + if (o?.a.b?.c.non_existent ?? o?.a.b?.c.d) { + return o?.a.b?.c.non_existent ?? o?.a.b?.c.d; + } + return o?.a.b?.c.non_existent ?? o; + } + static test() { const c = { a: { @@ -85,6 +92,8 @@ class C { expect(C.testLogicalInIf(c)).toBe(true); expect(C.testLogicalInReturn(c)).toBe(2); + + expect(C.testNullishCoalescing(c)).toBe(2); } static testNullish() { @@ -101,6 +110,8 @@ class C { expect(C.testLogicalInIf(n)).toBe(false); expect(C.testLogicalInReturn(n)).toBe(undefined); + + expect(C.testNullishCoalescing(n)).toBe(n); } } } diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/input.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/input.js index aa3b561b3c55..a9aab8bee395 100644 --- a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/input.js +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/input.js @@ -62,6 +62,13 @@ class C { static testLogicalInReturn(o) { return o?.a.b?.c.d && o?.a?.b.c.d; } + + static testNullishCoalescing(o) { + if (o?.a.b?.c.non_existent ?? o?.a.b?.c.d) { + return o?.a.b?.c.non_existent ?? o?.a.b?.c.d; + } + return o?.a.b?.c.non_existent ?? o; + } } C.test(); diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/output.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/output.js index 29b086c1368a..d8f4d3189423 100644 --- a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/output.js +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/output.js @@ -103,6 +103,18 @@ class C { return (o == null ? void 0 : (_o$a$b5 = o.a.b) == null ? void 0 : _o$a$b5.c.d) && (o == null ? void 0 : (_o$a2 = o.a) == null ? void 0 : _o$a2.b.c.d); } + static testNullishCoalescing(o) { + var _o$a$b6, _o$a$b7, _o$a$b10; + + if ((o == null ? void 0 : (_o$a$b6 = o.a.b) == null ? void 0 : _o$a$b6.c.non_existent) ?? (o == null ? void 0 : (_o$a$b7 = o.a.b) == null ? void 0 : _o$a$b7.c.d)) { + var _o$a$b8, _o$a$b9; + + return (o == null ? void 0 : (_o$a$b8 = o.a.b) == null ? void 0 : _o$a$b8.c.non_existent) ?? (o == null ? void 0 : (_o$a$b9 = o.a.b) == null ? void 0 : _o$a$b9.c.d); + } + + return (o == null ? void 0 : (_o$a$b10 = o.a.b) == null ? void 0 : _o$a$b10.c.non_existent) ?? o; + } + } C.test(); From 9b1d2c97fa0822bc113c5940919725bdfe1ae450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Thu, 12 Nov 2020 11:50:54 -0500 Subject: [PATCH 5/8] feat: support sequence expression --- .../src/index.js | 54 +----------- .../src/util.js | 65 ++++++++++++++ .../test/util.test.js | 85 +++++++++++++++++++ 3 files changed, 154 insertions(+), 50 deletions(-) create mode 100644 packages/babel-plugin-proposal-optional-chaining/src/util.js create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/util.test.js diff --git a/packages/babel-plugin-proposal-optional-chaining/src/index.js b/packages/babel-plugin-proposal-optional-chaining/src/index.js index 91b0a5147fd1..c25d95d7f996 100644 --- a/packages/babel-plugin-proposal-optional-chaining/src/index.js +++ b/packages/babel-plugin-proposal-optional-chaining/src/index.js @@ -5,59 +5,13 @@ import { } from "@babel/helper-skip-transparent-expression-wrappers"; import syntaxOptionalChaining from "@babel/plugin-syntax-optional-chaining"; import { types as t, template } from "@babel/core"; +import { + willPathCastToBoolean, + findOutermostTransparentParent, +} from "./util.js"; const { ast } = template.expression; -/** - * Test if a NodePath will be cast to boolean when evaluated. - * It respects transparent expression wrappers defined in - * "@babel/helper-skip-transparent-expression-wrappers" - * - * @example - * // returns true - * const nodePathADotB = NodePath("if (a.b) {}").get("test"); // a.b - * willPathCastToBoolean(nodePathADotB) - * @example - * // returns false - * willPathCastToBoolean(NodePath("a.b")) - * @param {NodePath} path - * @returns {boolean} - */ -function willPathCastToBoolean(path: NodePath): boolean { - const maybeWrapped = findOutermostTransparentParent(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 }) - ); -} - -/** - * Return the outermost transparent expression wrapper of a given path, - * otherwise returns path itself. - * @example - * const nodePathADotB = NodePath("(a.b as any)").get("expression"); // a.b - * // returns NodePath("(a.b as any)") - * findOutermostTransparentParent(nodePathADotB); - * @param {NodePath} path - * @returns {NodePath} - */ -function findOutermostTransparentParent(path: NodePath): NodePath { - let maybeWrapped = path; - path.findParent(p => { - if (!isTransparentExprWrapper(p)) return true; - maybeWrapped = p; - }); - return maybeWrapped; -} - export default declare((api, options) => { api.assertVersion(7); diff --git a/packages/babel-plugin-proposal-optional-chaining/src/util.js b/packages/babel-plugin-proposal-optional-chaining/src/util.js new file mode 100644 index 000000000000..7f98ccb2fcd6 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/src/util.js @@ -0,0 +1,65 @@ +import { isTransparentExprWrapper } from "@babel/helper-skip-transparent-expression-wrappers"; +/** + * Test if a NodePath will be cast to boolean when evaluated. + * It respects transparent expression wrappers defined in + * "@babel/helper-skip-transparent-expression-wrappers" + * + * @example + * // returns true + * const nodePathADotB = NodePath("if (a.b) {}").get("test"); // a.b + * willPathCastToBoolean(nodePathADotB) + * @example + * // returns false + * willPathCastToBoolean(NodePath("a.b")) + * @param {NodePath} path + * @returns {boolean} + */ +export function willPathCastToBoolean(path: NodePath): boolean { + const maybeWrapped = findOutermostTransparentParent(path); + const { node, parentPath } = maybeWrapped; + if (parentPath.isLogicalExpression()) { + const { operator, right } = parentPath.node; + if ( + operator === "&&" || + operator === "||" || + (operator === "??" && node === right) + ) { + return willPathCastToBoolean(parentPath); + } + } + if (parentPath.isSequenceExpression()) { + const { expressions } = parentPath.node; + if (expressions[expressions.length - 1] === node) { + return willPathCastToBoolean(parentPath); + } else { + // if it is in the middle of a sequence expression, we don't + // care the return value so just cast to boolean for smaller + // output + return true; + } + } + return ( + parentPath.isConditional({ test: node }) || + parentPath.isUnaryExpression({ operator: "!" }) || + parentPath.isLoop({ test: node }) + ); +} + +/** + * Return the outermost transparent expression wrapper of a given path, + * otherwise returns path itself. + * @example + * const nodePathADotB = NodePath("(a.b as any)").get("expression"); // a.b + * // returns NodePath("(a.b as any)") + * findOutermostTransparentParent(nodePathADotB); + * @param {NodePath} path + * @returns {NodePath} + */ +export function findOutermostTransparentParent(path: NodePath): NodePath { + let maybeWrapped = path; + path.findParent(p => { + if (!isTransparentExprWrapper(p)) return true; + maybeWrapped = p; + }); + return maybeWrapped; +} diff --git a/packages/babel-plugin-proposal-optional-chaining/test/util.test.js b/packages/babel-plugin-proposal-optional-chaining/test/util.test.js new file mode 100644 index 000000000000..43c4555c2fb3 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/util.test.js @@ -0,0 +1,85 @@ +import { willPathCastToBoolean } from "../src/util"; +import { parseSync, traverse } from "@babel/core"; + +function getPath(input, parserOpts) { + let targetPath; + traverse(parseSync(input, { parserOpts, filename: "example.js" }), { + OptionalMemberExpression(path) { + targetPath = path; + path.stop(); + }, + noScope: true, + }); + return targetPath; +} + +describe("willPathCastToBoolean", () => { + const positiveCases = [ + "if(a?.b) {}", + "while(a?.b) {}", + "a?.b ? 0 : 1", + "for(;a?.b;);", + "while(a?.b);", + "do {} while (a?.b)", + "!a?.b", + "if(a && a?.b) {}", + "if(a?.b && a) {}", + "while(a || a?.b) {}", + "while(a?.b || a) {}", + "for(; a ?? a?.b ;);", + "do {} while (0, a?.b)", + "(a?.b, 0)", + "!(a && ( b || ( c ?? ( a?.b && d ) ) ) )", + + // parenthesized + "!((a?.b), 0)", + "((a?.b)) ? 0 : 1", + "while( (a || ((a?.b) && c) ) );", + ]; + + const negativeCases = [ + "() => a?.b", + "for(; a?.b ?? a;);", + "a?.b && a", + "a && a?.b", + "() => a?.b || a", + "() => a || a?.b", + "a?.b()", + "a = a?.b && null", + + // parenthesized + "(a?.b)", + ]; + + describe("default parser options", () => { + test.each(positiveCases.map(x => [x]))( + "willPathCastToBoolean(a?.b in %p) should return true", + input => { + expect(willPathCastToBoolean(getPath(input))).toBe(true); + }, + ); + test.each(negativeCases.map(x => [x]))( + "willPathCastToBoolean(a?.b in %p) should return false", + input => { + expect(willPathCastToBoolean(getPath(input))).toBe(false); + }, + ); + }); + + describe("createParenthesizedExpressions", () => { + test.each(positiveCases.map(x => [x]))( + "willPathCastToBoolean(a?.b in %p with { createParenthesizedExpressions: true }) should return true", + input => { + const parserOpts = { createParenthesizedExpressions: true }; + expect(willPathCastToBoolean(getPath(input, parserOpts))).toBe(true); + }, + ); + test.each(negativeCases.map(x => [x]))( + "willPathCastToBoolean(a?.b in %p with { createParenthesizedExpressions: true }) should return false", + input => { + const parserOpts = { createParenthesizedExpressions: true }; + expect(willPathCastToBoolean(getPath(input, parserOpts))).toBe(false); + }, + ); + }); +}); From 2423f21bfd9245a17b5fc18c8090d3d8e8121b79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Thu, 12 Nov 2020 12:45:03 -0500 Subject: [PATCH 6/8] chore: update test fixtures for Node.js 6 --- .../test/fixtures/general/cast-to-boolean/options.json | 3 +++ .../test/fixtures/general/cast-to-boolean/output.js | 10 +++++----- .../test/fixtures/general/containers/output.js | 2 +- .../parenthesized-expression-containers/output.js | 2 +- .../test/fixtures/loose/cast-to-boolean/options.json | 3 +++ .../test/fixtures/loose/cast-to-boolean/output.js | 10 +++++----- 6 files changed, 18 insertions(+), 12 deletions(-) create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/cast-to-boolean/options.json create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/options.json diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/cast-to-boolean/options.json b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/cast-to-boolean/options.json new file mode 100644 index 000000000000..84af5f34637a --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/cast-to-boolean/options.json @@ -0,0 +1,3 @@ +{ + "plugins": [["proposal-optional-chaining"], ["proposal-nullish-coalescing-operator"]] +} 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 index 63cd8798aeaf..b02747090059 100644 --- 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 @@ -104,15 +104,15 @@ class C { } static testNullishCoalescing(o) { - var _o$a$b6, _o$a$b7, _o$a$b10; + var _o$a$b$c$non_existent, _o$a$b6, _o$a$b7, _o$a$b$c$non_existent3, _o$a$b10; - if ((o === null || o === void 0 ? void 0 : (_o$a$b6 = o.a.b) === null || _o$a$b6 === void 0 ? void 0 : _o$a$b6.c.non_existent) ?? (o === null || o === void 0 ? void 0 : (_o$a$b7 = o.a.b) === null || _o$a$b7 === void 0 ? void 0 : _o$a$b7.c.d)) { - var _o$a$b8, _o$a$b9; + if ((_o$a$b$c$non_existent = o === null || o === void 0 ? void 0 : (_o$a$b6 = o.a.b) === null || _o$a$b6 === void 0 ? void 0 : _o$a$b6.c.non_existent) !== null && _o$a$b$c$non_existent !== void 0 ? _o$a$b$c$non_existent : o === null || o === void 0 ? void 0 : (_o$a$b7 = o.a.b) === null || _o$a$b7 === void 0 ? void 0 : _o$a$b7.c.d) { + var _o$a$b$c$non_existent2, _o$a$b8, _o$a$b9; - return (o === null || o === void 0 ? void 0 : (_o$a$b8 = o.a.b) === null || _o$a$b8 === void 0 ? void 0 : _o$a$b8.c.non_existent) ?? (o === null || o === void 0 ? void 0 : (_o$a$b9 = o.a.b) === null || _o$a$b9 === void 0 ? void 0 : _o$a$b9.c.d); + return (_o$a$b$c$non_existent2 = o === null || o === void 0 ? void 0 : (_o$a$b8 = o.a.b) === null || _o$a$b8 === void 0 ? void 0 : _o$a$b8.c.non_existent) !== null && _o$a$b$c$non_existent2 !== void 0 ? _o$a$b$c$non_existent2 : o === null || o === void 0 ? void 0 : (_o$a$b9 = o.a.b) === null || _o$a$b9 === void 0 ? void 0 : _o$a$b9.c.d; } - return (o === null || o === void 0 ? void 0 : (_o$a$b10 = o.a.b) === null || _o$a$b10 === void 0 ? void 0 : _o$a$b10.c.non_existent) ?? o; + return (_o$a$b$c$non_existent3 = o === null || o === void 0 ? void 0 : (_o$a$b10 = o.a.b) === null || _o$a$b10 === void 0 ? void 0 : _o$a$b10.c.non_existent) !== null && _o$a$b$c$non_existent3 !== void 0 ? _o$a$b$c$non_existent3 : o; } } diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/containers/output.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/containers/output.js index 7d4419088c62..14773cb2ef94 100644 --- a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/containers/output.js +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/containers/output.js @@ -4,4 +4,4 @@ var street = (_user$address = user.address) === null || _user$address === void 0 street = (_user$address2 = user.address) === null || _user$address2 === void 0 ? void 0 : _user$address2.street; test((_a = a) === null || _a === void 0 ? void 0 : _a.b, 1); test((_a2 = a) === null || _a2 === void 0 ? void 0 : _a2.b, 1); -1, (_a3 = a) === null || _a3 === void 0 ? void 0 : _a3.b, 2; +1, (_a3 = a) !== null && _a3 !== void 0 && _a3.b, 2; diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-expression-containers/output.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-expression-containers/output.js index 72b42c86c35f..c2df86860289 100644 --- a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-expression-containers/output.js +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-expression-containers/output.js @@ -4,4 +4,4 @@ var street = (_user$address = user.address) === null || _user$address === void 0 street = (_user$address2 = user.address) === null || _user$address2 === void 0 ? void 0 : _user$address2.street; test((_a = a) === null || _a === void 0 ? void 0 : _a.b, 1); test(((_a2 = a) === null || _a2 === void 0 ? void 0 : _a2.b), 1); -((1, (_a3 = a) === null || _a3 === void 0 ? void 0 : _a3.b, 2)); +((1, (_a3 = a) !== null && _a3 !== void 0 && _a3.b, 2)); diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/options.json b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/options.json new file mode 100644 index 000000000000..bc300a8b64cf --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/options.json @@ -0,0 +1,3 @@ +{ + "plugins": [["proposal-optional-chaining", { "loose": true }], ["proposal-nullish-coalescing-operator", { "loose": true }]] +} diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/output.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/output.js index d8f4d3189423..4b41cfb287c7 100644 --- a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/output.js +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/loose/cast-to-boolean/output.js @@ -104,15 +104,15 @@ class C { } static testNullishCoalescing(o) { - var _o$a$b6, _o$a$b7, _o$a$b10; + var _o$a$b$c$non_existent, _o$a$b6, _o$a$b7, _o$a$b$c$non_existent3, _o$a$b10; - if ((o == null ? void 0 : (_o$a$b6 = o.a.b) == null ? void 0 : _o$a$b6.c.non_existent) ?? (o == null ? void 0 : (_o$a$b7 = o.a.b) == null ? void 0 : _o$a$b7.c.d)) { - var _o$a$b8, _o$a$b9; + if ((_o$a$b$c$non_existent = o == null ? void 0 : (_o$a$b6 = o.a.b) == null ? void 0 : _o$a$b6.c.non_existent) != null ? _o$a$b$c$non_existent : o == null ? void 0 : (_o$a$b7 = o.a.b) == null ? void 0 : _o$a$b7.c.d) { + var _o$a$b$c$non_existent2, _o$a$b8, _o$a$b9; - return (o == null ? void 0 : (_o$a$b8 = o.a.b) == null ? void 0 : _o$a$b8.c.non_existent) ?? (o == null ? void 0 : (_o$a$b9 = o.a.b) == null ? void 0 : _o$a$b9.c.d); + return (_o$a$b$c$non_existent2 = o == null ? void 0 : (_o$a$b8 = o.a.b) == null ? void 0 : _o$a$b8.c.non_existent) != null ? _o$a$b$c$non_existent2 : o == null ? void 0 : (_o$a$b9 = o.a.b) == null ? void 0 : _o$a$b9.c.d; } - return (o == null ? void 0 : (_o$a$b10 = o.a.b) == null ? void 0 : _o$a$b10.c.non_existent) ?? o; + return (_o$a$b$c$non_existent3 = o == null ? void 0 : (_o$a$b10 = o.a.b) == null ? void 0 : _o$a$b10.c.non_existent) != null ? _o$a$b$c$non_existent3 : o; } } From ae89c04ac1fb9c7bd3bbf70e899c26f7e00e91f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Thu, 12 Nov 2020 15:50:59 -0500 Subject: [PATCH 7/8] chore: update willPathCastToBoolean in helper-member-expression --- .../src/index.js | 33 +------------- .../src/util.js | 45 +++++++++++++++++++ .../test/util.test.js | 8 ++-- 3 files changed, 50 insertions(+), 36 deletions(-) create mode 100644 packages/babel-helper-member-expression-to-functions/src/util.js 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 e14c0abe50ba..71c81bb02410 100644 --- a/packages/babel-helper-member-expression-to-functions/src/index.js +++ b/packages/babel-helper-member-expression-to-functions/src/index.js @@ -1,4 +1,5 @@ import * as t from "@babel/types"; +import { willPathCastToBoolean } from "./util.js"; class AssignmentMemoiser { constructor() { @@ -29,38 +30,6 @@ 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()) { diff --git a/packages/babel-helper-member-expression-to-functions/src/util.js b/packages/babel-helper-member-expression-to-functions/src/util.js new file mode 100644 index 000000000000..107ff410eb25 --- /dev/null +++ b/packages/babel-helper-member-expression-to-functions/src/util.js @@ -0,0 +1,45 @@ +/** + * 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/util.js} + * @param {NodePath} path + * @returns {boolean} + */ +export function willPathCastToBoolean(path: NodePath): boolean { + const maybeWrapped = path; + const { node, parentPath } = maybeWrapped; + if (parentPath.isLogicalExpression()) { + const { operator, right } = parentPath.node; + if ( + operator === "&&" || + operator === "||" || + (operator === "??" && node === right) + ) { + return willPathCastToBoolean(parentPath); + } + } + if (parentPath.isSequenceExpression()) { + const { expressions } = parentPath.node; + if (expressions[expressions.length - 1] === node) { + return willPathCastToBoolean(parentPath); + } else { + // if it is in the middle of a sequence expression, we don't + // care the return value so just cast to boolean for smaller + // output + return true; + } + } + return ( + parentPath.isConditional({ test: node }) || + parentPath.isUnaryExpression({ operator: "!" }) || + parentPath.isLoop({ test: node }) + ); +} diff --git a/packages/babel-plugin-proposal-optional-chaining/test/util.test.js b/packages/babel-plugin-proposal-optional-chaining/test/util.test.js index 43c4555c2fb3..fbf99d64e52e 100644 --- a/packages/babel-plugin-proposal-optional-chaining/test/util.test.js +++ b/packages/babel-plugin-proposal-optional-chaining/test/util.test.js @@ -52,13 +52,13 @@ describe("willPathCastToBoolean", () => { ]; describe("default parser options", () => { - test.each(positiveCases.map(x => [x]))( + test.each(positiveCases)( "willPathCastToBoolean(a?.b in %p) should return true", input => { expect(willPathCastToBoolean(getPath(input))).toBe(true); }, ); - test.each(negativeCases.map(x => [x]))( + test.each(negativeCases)( "willPathCastToBoolean(a?.b in %p) should return false", input => { expect(willPathCastToBoolean(getPath(input))).toBe(false); @@ -67,14 +67,14 @@ describe("willPathCastToBoolean", () => { }); describe("createParenthesizedExpressions", () => { - test.each(positiveCases.map(x => [x]))( + test.each(positiveCases)( "willPathCastToBoolean(a?.b in %p with { createParenthesizedExpressions: true }) should return true", input => { const parserOpts = { createParenthesizedExpressions: true }; expect(willPathCastToBoolean(getPath(input, parserOpts))).toBe(true); }, ); - test.each(negativeCases.map(x => [x]))( + test.each(negativeCases)( "willPathCastToBoolean(a?.b in %p with { createParenthesizedExpressions: true }) should return false", input => { const parserOpts = { createParenthesizedExpressions: true }; From 9b38e9cd8dc7a6771e6772815949595bab172328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 13 Nov 2020 15:51:58 -0500 Subject: [PATCH 8/8] chore: build @babel/plugin-proposal-optional-chaining and helper-member-expression-to-functions --- Gulpfile.js | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/Gulpfile.js b/Gulpfile.js index be30c73e94dd..dfa1d5b871da 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -102,7 +102,13 @@ const babelVersion = function buildRollup(packages) { const sourcemap = process.env.NODE_ENV === "production"; return Promise.all( - packages.map(async ({ src, format, dest, name, filename, version }) => { + packages.map(async ({ src, format, dest, name, filename }) => { + const pkgJSON = require("./" + src + "/package.json"); + const version = pkgJSON.version + versionSuffix; + const { dependencies = {}, peerDependencies = {} } = pkgJSON; + const external = Object.keys(dependencies).concat( + Object.keys(peerDependencies) + ); let nodeResolveBrowser = false, babelEnvName = "rollup"; switch (src) { @@ -115,6 +121,7 @@ function buildRollup(packages) { fancyLog(`Compiling '${chalk.cyan(input)}' with rollup ...`); const bundle = await rollup.rollup({ input, + external, plugins: [ rollupBabelSource(), rollupReplace({ @@ -161,6 +168,7 @@ function buildRollup(packages) { format, name, sourcemap: sourcemap, + exports: "named", }); if (!process.env.IS_PUBLISH) { @@ -180,6 +188,7 @@ function buildRollup(packages) { format, name, sourcemap: sourcemap, + exports: "named", plugins: [ rollupTerser({ // workaround https://bugs.webkit.org/show_bug.cgi?id=212725 @@ -194,13 +203,14 @@ function buildRollup(packages) { } const libBundles = [ - { - src: "packages/babel-parser", - format: "cjs", - dest: "lib", - version: require("./packages/babel-parser/package").version + versionSuffix, - }, -]; + "packages/babel-parser", + "packages/babel-plugin-proposal-optional-chaining", + "packages/babel-helper-member-expression-to-functions", +].map(src => ({ + src, + format: "cjs", + dest: "lib", +})); const standaloneBundle = [ {