optimize optional chain when expression will be cast to boolean (#12291)

This commit is contained in:
Huáng Jùnliàng 2020-11-20 14:55:33 -05:00 committed by GitHub
parent e7e0c25d6a
commit a44151acef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1610 additions and 98 deletions

View File

@ -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 = [
{

View File

@ -1,4 +1,5 @@
import * as t from "@babel/types";
import { willPathCastToBoolean } from "./util.js";
class AssignmentMemoiser {
constructor() {
@ -129,6 +130,8 @@ const handle = {
return;
}
const willEndPathCastToBoolean = willPathCastToBoolean(endPath);
const rootParentPath = endPath.parentPath;
if (
rootParentPath.isUpdateExpression({ argument: node }) ||
@ -238,33 +241,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) {

View File

@ -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 })
);
}

View File

@ -0,0 +1,119 @@
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 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);
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);
expect(C.testNullishCoalescing(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);
expect(C.testNullishCoalescing(n)).toBe(n);
}
}
}
C.test();
C.testNullish();

View File

@ -0,0 +1,82 @@
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 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();
C.testNullish();

View File

@ -0,0 +1,4 @@
{
"plugins": [["external-helpers", { "helperVersion": "7.100.0" }], ["proposal-class-properties", {"loose": true }]],
"minNodeVersion": "14.0.0"
}

View File

@ -0,0 +1,117 @@
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;
}
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, {
writable: true,
value: {
b: {
c: {
d: 2
}
}
}
});
C.test();
C.testNullish();

View File

@ -0,0 +1,119 @@
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 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);
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);
expect(C.testNullishCoalescing(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);
expect(C.testNullishCoalescing(n)).toBe(n);
}
}
}
C.test();
C.testNullish();

View File

@ -0,0 +1,82 @@
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 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();
C.testNullish();

View File

@ -0,0 +1,4 @@
{
"plugins": [["external-helpers", { "helperVersion": "7.100.0" }], "proposal-class-properties"],
"minNodeVersion": "14.0.0"
}

View File

@ -0,0 +1,115 @@
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;
}
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 = {
writable: true,
value: {
b: {
c: {
d: 2
}
}
}
};
C.test();
C.testNullish();

View File

@ -5,6 +5,12 @@ 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;
export default declare((api, options) => {
api.assertVersion(7);
@ -56,11 +62,11 @@ 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,
);
let isDeleteOperation = false;
const parentIsCall =
parentPath.isCallExpression({ callee: maybeWrapped.node }) &&
@ -202,33 +208,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"),
);
}
}
},
},

View File

@ -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;
}

View File

@ -0,0 +1,120 @@
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 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: {
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);
expect(C.testNullishCoalescing(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);
expect(C.testNullishCoalescing(n)).toBe(n);
}
}
}
C.test();
C.testNullish();

View File

@ -0,0 +1,75 @@
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 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();
C.testNullish();

View File

@ -0,0 +1,3 @@
{
"plugins": [["proposal-optional-chaining"], ["proposal-nullish-coalescing-operator"]]
}

View File

@ -0,0 +1,121 @@
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);
}
static testNullishCoalescing(o) {
var _o$a$b$c$non_existent, _o$a$b6, _o$a$b7, _o$a$b$c$non_existent3, _o$a$b10;
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$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$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;
}
}
C.test();
C.testNullish();

View File

@ -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;

View File

@ -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));

View File

@ -0,0 +1,120 @@
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 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: {
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);
expect(C.testNullishCoalescing(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);
expect(C.testNullishCoalescing(n)).toBe(n);
}
}
}
C.test();
C.testNullish();

View File

@ -0,0 +1,75 @@
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 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();
C.testNullish();

View File

@ -0,0 +1,3 @@
{
"plugins": [["proposal-optional-chaining", { "loose": true }], ["proposal-nullish-coalescing-operator", { "loose": true }]]
}

View File

@ -0,0 +1,121 @@
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);
}
static testNullishCoalescing(o) {
var _o$a$b$c$non_existent, _o$a$b6, _o$a$b7, _o$a$b$c$non_existent3, _o$a$b10;
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$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$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;
}
}
C.test();
C.testNullish();

View File

@ -0,0 +1,3 @@
{
"plugins": [["proposal-optional-chaining", { "loose": true }]]
}

View File

@ -1,3 +1,3 @@
{
"plugins": ["proposal-optional-chaining"]
"plugins": ["proposal-optional-chaining", "syntax-typescript"]
}

View File

@ -0,0 +1,3 @@
(a) => {
if ((a.b as any)?.()) {}
}

View File

@ -0,0 +1,5 @@
a => {
var _a$b;
if ((_a$b = (a.b as any)) !== null && _a$b !== void 0 && _a$b.call(a)) {}
};

View File

@ -1,10 +0,0 @@
{
"plugins": [
[
"syntax-typescript"
],
[
"proposal-optional-chaining"
]
]
}

View File

@ -0,0 +1,3 @@
(a) => {
(((a?.b as any) && a.c?.d) as any) ? 0 : 1
}

View File

@ -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;
};

View File

@ -1,10 +0,0 @@
{
"plugins": [
[
"syntax-typescript"
],
[
"proposal-optional-chaining"
]
]
}

View File

@ -1,10 +0,0 @@
{
"plugins": [
[
"syntax-typescript"
],
[
"proposal-optional-chaining"
]
]
}

View File

@ -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)(
"willPathCastToBoolean(a?.b in %p) should return true",
input => {
expect(willPathCastToBoolean(getPath(input))).toBe(true);
},
);
test.each(negativeCases)(
"willPathCastToBoolean(a?.b in %p) should return false",
input => {
expect(willPathCastToBoolean(getPath(input))).toBe(false);
},
);
});
describe("createParenthesizedExpressions", () => {
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)(
"willPathCastToBoolean(a?.b in %p with { createParenthesizedExpressions: true }) should return false",
input => {
const parserOpts = { createParenthesizedExpressions: true };
expect(willPathCastToBoolean(getPath(input, parserOpts))).toBe(false);
},
);
});
});