Skip to content

Commit

Permalink
add evaluation to optional chaining
Browse files Browse the repository at this point in the history
  • Loading branch information
vankop committed Jul 19, 2020
1 parent 52fa851 commit abd8905
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 4 deletions.
47 changes: 47 additions & 0 deletions lib/ConstPlugin.js
Expand Up @@ -10,6 +10,8 @@ const ConstDependency = require("./dependencies/ConstDependency");
const { evaluateToString } = require("./javascript/JavascriptParserHelpers");
const { parseResource } = require("./util/identifier");

/** @typedef {import("estree").Expression} ExpressionNode */
/** @typedef {import("estree").Super} SuperNode */
/** @typedef {import("./Compiler")} Compiler */

const collectDeclaration = (declarations, pattern) => {
Expand Down Expand Up @@ -358,6 +360,51 @@ class ConstPlugin {
}
}
);
parser.hooks.optionalChaining.tap("ConstPlugin", expr => {
const optionalExpressionsStack = [];
/** @type {ExpressionNode|SuperNode} */
let next;

if (expr.expression.type === "CallExpression") {
next = expr.expression.callee;
} else {
next = expr.expression;
}

while (next.type === "MemberExpression") {
if (next.optional) {
optionalExpressionsStack.push(next.object);
}
next = next.object;
}

while (optionalExpressionsStack.length) {
const expression = optionalExpressionsStack.pop();
const evaluated = parser.evaluateExpression(expression);

if (evaluated && evaluated.asNullish()) {
// ------------------------------------------
//
// Given the following code:
//
// nullishMemberChain?.a.b();
//
// the generated code is:
//
// null; // or undefined; if evaluated to undefined
//
// ------------------------------------------
//
const dep = new ConstDependency(
evaluated.isUndefined() ? "undefined" : "null",
expr.range
);
dep.loc = expr.loc;
parser.state.module.addPresentationalDependency(dep);
return true;
}
}
});
parser.hooks.evaluateIdentifier
.for("__resourceQuery")
.tap("ConstPlugin", expr => {
Expand Down
14 changes: 10 additions & 4 deletions lib/javascript/JavascriptParser.js
Expand Up @@ -260,6 +260,8 @@ class JavascriptParser extends Parser {
"members"
])
),
/** @type {SyncBailHook<[ChainExpressionNode], boolean | void>} */
optionalChaining: new SyncBailHook(["optionalChaining"]),
/** @type {HookMap<SyncBailHook<[ExpressionNode], boolean | void>>} */
new: new HookMap(() => new SyncBailHook(["expression"])),
/** @type {SyncBailHook<[MetaPropertyNode], boolean | void>} */
Expand Down Expand Up @@ -2352,10 +2354,14 @@ class JavascriptParser extends Parser {
* @param {ChainExpressionNode} expression expression
*/
walkChainExpression(expression) {
if (expression.expression.type === "CallExpression") {
this.walkCallExpression(expression.expression);
} else {
this.walkMemberExpression(expression.expression);
const result = this.hooks.optionalChaining.call(expression);

if (result === undefined) {
if (expression.expression.type === "CallExpression") {
this.walkCallExpression(expression.expression);
} else {
this.walkMemberExpression(expression.expression);
}
}
}

Expand Down
1 change: 1 addition & 0 deletions test/cases/parsing/optional-chaining/a.js
@@ -0,0 +1 @@
module.exports = 1;
5 changes: 5 additions & 0 deletions test/cases/parsing/optional-chaining/index.js
@@ -0,0 +1,5 @@
it("should handle optional members", () => {
expect(
module.hot?.accept((() => {throw new Error("fail")})())
).toBe(null);
});
1 change: 1 addition & 0 deletions types.d.ts
Expand Up @@ -3272,6 +3272,7 @@ declare abstract class JavascriptParser extends Parser {
boolean | void
>
>;
optionalChaining: SyncBailHook<[ChainExpression], boolean | void>;
new: HookMap<SyncBailHook<[Expression], boolean | void>>;
metaProperty: SyncBailHook<[MetaProperty], boolean | void>;
expression: HookMap<SyncBailHook<[Expression], boolean | void>>;
Expand Down

0 comments on commit abd8905

Please sign in to comment.