Implement Null Propagation Operator
This commit is contained in:
@@ -1,161 +1,105 @@
|
||||
export default function ({ types: t }) {
|
||||
const optionalNodesTransformed = new WeakSet();
|
||||
const nilIdentifier = t.unaryExpression("void", t.numericLiteral(0));
|
||||
|
||||
function setOptionalTransformed(node) {
|
||||
t.assertMemberExpression(node); // Dev
|
||||
optionalNodesTransformed.add(node);
|
||||
}
|
||||
|
||||
function isOptionalTransformed(node) {
|
||||
t.assertMemberExpression(node); // Dev
|
||||
return optionalNodesTransformed.has(node);
|
||||
}
|
||||
|
||||
function createCondition(ref, access, nextProperty, bailout) {
|
||||
|
||||
return t.conditionalExpression(
|
||||
createCheckAgainstNull(
|
||||
t.AssignmentExpression("=", ref, access)
|
||||
),
|
||||
|
||||
t.memberExpression(ref, nextProperty),
|
||||
bailout,
|
||||
);
|
||||
}
|
||||
|
||||
function createCheckAgainstNull(left) {
|
||||
return t.BinaryExpression("!=", left, t.NullLiteral());
|
||||
}
|
||||
|
||||
function isNodeOptional(node) {
|
||||
return node.optional === true;
|
||||
}
|
||||
|
||||
return {
|
||||
visitor: {
|
||||
|
||||
AssignmentExpression(path, state) {
|
||||
const { left } = path.node;
|
||||
|
||||
if (!isNodeOptional(left) || isOptionalTransformed(left)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!state.optionalTemp) {
|
||||
const id = path.scope.generateUidIdentifier();
|
||||
|
||||
state.optionalTemp = id;
|
||||
path.scope.push({ id });
|
||||
}
|
||||
|
||||
const { object, property } = left;
|
||||
|
||||
const isChainNil = t.BinaryExpression(
|
||||
"!=",
|
||||
createCondition(
|
||||
state.optionalTemp,
|
||||
object,
|
||||
property,
|
||||
nilIdentifier,
|
||||
),
|
||||
nilIdentifier,
|
||||
);
|
||||
|
||||
// FIXME(sven): if will be a ConditionalExpression for childs, only top level will be ifStatement
|
||||
const replacement = t.ifStatement(isChainNil, t.blockStatement([t.expressionStatement(path.node)]));
|
||||
|
||||
setOptionalTransformed(left);
|
||||
|
||||
path.traverse({
|
||||
MemberExpression({ node }) {
|
||||
setOptionalTransformed(node);
|
||||
},
|
||||
});
|
||||
|
||||
path.parentPath.replaceWith(replacement);
|
||||
},
|
||||
|
||||
UnaryExpression(path, state) {
|
||||
const { operator, argument } = path.node;
|
||||
|
||||
if (operator !== "delete") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isNodeOptional(argument) || isOptionalTransformed(argument)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!state.optionalTemp) {
|
||||
const id = path.scope.generateUidIdentifier();
|
||||
|
||||
state.optionalTemp = id;
|
||||
path.scope.push({ id });
|
||||
}
|
||||
|
||||
const { object, property } = argument;
|
||||
|
||||
const isChainNil = t.BinaryExpression(
|
||||
"!=",
|
||||
createCondition(
|
||||
state.optionalTemp,
|
||||
object,
|
||||
property,
|
||||
nilIdentifier,
|
||||
),
|
||||
nilIdentifier,
|
||||
);
|
||||
|
||||
const replacement = t.ifStatement(isChainNil, t.blockStatement([t.expressionStatement(path.node)]));
|
||||
|
||||
setOptionalTransformed(argument);
|
||||
|
||||
path.parentPath.replaceWith(replacement);
|
||||
},
|
||||
|
||||
MemberExpression(path, state) {
|
||||
if (!isNodeOptional(path.node) || isOptionalTransformed(path.node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { object, property } = path.node;
|
||||
|
||||
if (!state.optionalTemp) {
|
||||
const id = path.scope.generateUidIdentifier();
|
||||
|
||||
state.optionalTemp = id;
|
||||
path.scope.push({ id });
|
||||
}
|
||||
|
||||
if (t.isAssignmentExpression(path.parent)) {
|
||||
return;
|
||||
} else if (t.isUnaryExpression(path.parent)) {
|
||||
return;
|
||||
} else if (t.isCallExpression(path.parent)) {
|
||||
|
||||
const replacement = createCondition(
|
||||
state.optionalTemp,
|
||||
object,
|
||||
property,
|
||||
t.callExpression(t.identifier("Function"), []),
|
||||
);
|
||||
|
||||
setOptionalTransformed(path.node);
|
||||
path.replaceWith(replacement);
|
||||
} else {
|
||||
|
||||
const replacement = createCondition(
|
||||
state.optionalTemp,
|
||||
object,
|
||||
property,
|
||||
nilIdentifier,
|
||||
);
|
||||
|
||||
setOptionalTransformed(path.node);
|
||||
// DO NOT SUBMIT. This is until the parser is complete
|
||||
const fixer = {
|
||||
NewExpression: {
|
||||
exit(path) {
|
||||
const { callee } = path.node;
|
||||
if (t.isCallExpression(callee)) {
|
||||
const replacement = t.newExpression(callee.callee, callee.arguments);
|
||||
replacement.optional = true;
|
||||
path.replaceWith(replacement);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
CallExpression(path) {
|
||||
const { node } = path;
|
||||
if (!node.optional || node.callee) {
|
||||
return;
|
||||
}
|
||||
|
||||
const callee = t.clone(node.arguments[0]);
|
||||
if (t.isMemberExpression(callee)) {
|
||||
callee.optional = node.arguments[1].value;
|
||||
}
|
||||
node.callee = callee;
|
||||
},
|
||||
};
|
||||
// END DO NOT SUBMIT
|
||||
|
||||
const visitor = {
|
||||
Program(path) {
|
||||
path.traverse(fixer);
|
||||
},
|
||||
|
||||
MemberExpression(path) {
|
||||
if (!path.node.optional) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { scope, node } = path;
|
||||
const { object } = node;
|
||||
|
||||
node.optional = false;
|
||||
|
||||
const ref = scope.generateUidIdentifierBasedOnNode(object);
|
||||
scope.push({ id: ref });
|
||||
node.object = ref;
|
||||
|
||||
let parent = path;
|
||||
let expression;
|
||||
do {
|
||||
expression = parent;
|
||||
parent = parent.parentPath;
|
||||
} while (!parent.container);
|
||||
|
||||
const replace = parent.isExpression() ? parent : expression;
|
||||
replace.replaceWith(t.conditionalExpression(
|
||||
t.binaryExpression("==", t.assignmentExpression("=", ref, object), t.nullLiteral()),
|
||||
scope.buildUndefinedNode(),
|
||||
replace.node
|
||||
));
|
||||
},
|
||||
|
||||
"NewExpression|CallExpression": {
|
||||
exit(path) {
|
||||
if (!path.node.optional) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { scope, node } = path;
|
||||
const { callee } = node;
|
||||
|
||||
node.optional = false;
|
||||
|
||||
const ref = scope.generateUidIdentifierBasedOnNode(callee);
|
||||
scope.push({ id: ref });
|
||||
node.callee = ref;
|
||||
|
||||
if (t.isMemberExpression(callee) && !t.isNewExpression(node)) {
|
||||
const context = scope.generateUidIdentifierBasedOnNode(callee.object);
|
||||
scope.push({ id: context });
|
||||
callee.object = t.assignmentExpression("=", context, callee.object);
|
||||
|
||||
node.arguments.unshift(context);
|
||||
node.callee = t.memberExpression(node.callee, t.identifier("call"));
|
||||
}
|
||||
|
||||
path.replaceWith(t.conditionalExpression(
|
||||
t.binaryExpression("==", t.assignmentExpression("=", ref, callee), t.nullLiteral()),
|
||||
scope.buildUndefinedNode(),
|
||||
node
|
||||
));
|
||||
},
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
return {
|
||||
visitor,
|
||||
|
||||
manipulateOptions(opts, parserOpts) {
|
||||
parserOpts.plugins.push("optionalChaining");
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
var _temp;
|
||||
var _a, _a$b$c, _a$b, _a2;
|
||||
|
||||
if (((_temp = a) != null ? _temp.b : void 0) != void 0) {
|
||||
a.b = 42;
|
||||
}
|
||||
(_a = a) == null ? void 0 : _a.b = 42;
|
||||
|
||||
if (((_temp = a.b.c) != null ? _temp.d : void 0) != void 0) {
|
||||
a.b.c.d = 42;
|
||||
}
|
||||
(((_a2 = a) == null ? void 0 : _a$b = _a2.b) == null ? void 0 : _a$b$c = _a$b.c) == null ? void 0 : _a$b$c.d = 42;
|
||||
@@ -1,9 +1,5 @@
|
||||
var _temp;
|
||||
var _a, _a$b$c, _a$b, _a2;
|
||||
|
||||
if (((_temp = a) != null ? _temp.b : void 0) != void 0) {
|
||||
delete a.b;
|
||||
}
|
||||
(_a = a) == null ? void 0 : delete _a.b;
|
||||
|
||||
if (((_temp = ((_temp = a) != null ? _temp.b : void 0).c) != null ? _temp.d : void 0) != void 0) {
|
||||
delete ((_temp = a.b) != null ? _temp.c : void 0).d;
|
||||
}
|
||||
(((_a2 = a) == null ? void 0 : _a$b = _a2.b) == null ? void 0 : _a$b$c = _a$b.c) == null ? void 0 : delete _a$b$c.d;
|
||||
@@ -1 +1,7 @@
|
||||
foo?.(foo);
|
||||
|
||||
foo?.bar()
|
||||
|
||||
foo.bar?.(foo.bar, false)
|
||||
|
||||
foo?.bar?.(foo.bar, true)
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
var _temp;
|
||||
var _foo, _foo2, _foo$bar, _foo3, _foo4, _foo4$bar, _foo5;
|
||||
|
||||
((_temp = foo) != null ? _temp.bar : Function())();
|
||||
(_foo = foo) == null ? void 0 : _foo(foo);
|
||||
|
||||
(_foo2 = foo) == null ? void 0 : _foo2.bar();
|
||||
|
||||
(_foo$bar = (_foo3 = foo).bar) == null ? void 0 : _foo$bar.call(_foo3, foo.bar, false);
|
||||
|
||||
(_foo4 = foo) == null ? void 0 : (_foo4$bar = (_foo5 = _foo4).bar) == null ? void 0 : _foo4$bar.call(_foo5, foo.bar, true);
|
||||
@@ -1,5 +1,5 @@
|
||||
var _temp;
|
||||
var _foo, _a$b$c, _a;
|
||||
|
||||
(_temp = foo) != null ? _temp.bar : void 0;
|
||||
(_foo = foo) == null ? void 0 : _foo.bar;
|
||||
|
||||
((_temp = ((_temp = a) != null ? _temp.b : void 0).c) != null ? _temp.d : void 0).e;
|
||||
(_a$b$c = (_a = a) == null ? void 0 : _a.b.c) == null ? void 0 : _a$b$c.d.e;
|
||||
8
packages/babel-plugin-transform-optional-chaining/test/fixtures/general/new/actual.js
vendored
Normal file
8
packages/babel-plugin-transform-optional-chaining/test/fixtures/general/new/actual.js
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
new a?.b
|
||||
new a?.b?.c?.d
|
||||
|
||||
new a?.b()
|
||||
new a?.b?.c?.d()
|
||||
|
||||
new b?.(b)
|
||||
new a?.b?.(a.b, true)
|
||||
10
packages/babel-plugin-transform-optional-chaining/test/fixtures/general/new/expected.js
vendored
Normal file
10
packages/babel-plugin-transform-optional-chaining/test/fixtures/general/new/expected.js
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
var _a, _a$b$c, _a$b, _a2, _a3, _a$b$c2, _a$b2, _a4, _b, _a5, _a5$b;
|
||||
|
||||
(_a = a) == null ? void 0 : new _a.b();
|
||||
(((_a2 = a) == null ? void 0 : _a$b = _a2.b) == null ? void 0 : _a$b$c = _a$b.c) == null ? void 0 : new _a$b$c.d();
|
||||
|
||||
(_a3 = a) == null ? void 0 : new _a3.b();
|
||||
(((_a4 = a) == null ? void 0 : _a$b2 = _a4.b) == null ? void 0 : _a$b$c2 = _a$b2.c) == null ? void 0 : new _a$b$c2.d();
|
||||
|
||||
(_b = b) == null ? void 0 : new _b(b);
|
||||
(_a5 = a) == null ? void 0 : (_a5$b = _a5.b) == null ? void 0 : new _a5$b(a.b, true);
|
||||
Reference in New Issue
Block a user