use :node;
var Node = module.require('../Node').Node;
fn NullCoalescingExpression(left, right)
extends Node { this.type = 'NullCoalescingExpression'; this.left = left; this.left.parent = this; this.right = right; this.right.parent = this;
}
NullCoalescingExpression.prototype.codegen = () -> {
if !super.codegen() { return; } var leftType = this.left.type; this.left = this.left.codegen(); this.right = this.right.codegen(); var context = this.getContext(); var addUndefinedCheck = true; // If the left expression is a function call (e.g: f() ?? 5) // then store its value in a separate variable to avoid // calling the function twice. if leftType == 'NullPropagatingExpression' || leftType == 'NullCoalescingExpression' || this.left.hasCallExpression?() { var id = { "type": "Identifier", "name": NullCoalescingExpression.getNextLeftName() }; context.node.body.splice(context.position, 0, { "type": "VariableDeclaration", "declarations": [ { "type": "VariableDeclarator", "id": id, "init": this.left } ], "kind": "let", "codeGenerated": true }); this.left = id; addUndefinedCheck = false; } var test = { "type": "BinaryExpression", "operator": "==", "left": this.left, "right": { "type": "Literal", "value": null, "raw": "null" } }; if this.left.type != 'Identifier' { addUndefinedCheck = false; } if addUndefinedCheck { test = { "type": "LogicalExpression", "operator": "||", "left": { "type": "BinaryExpression", "operator": "===", "left": { "type": "UnaryExpression", "operator": "typeof", "argument": this.left, "prefix": true }, "right": { "type": "Literal", "value": "undefined", "raw": "'undefined'" } }, "right": test }; } // If the null coalescing operator is an expression // statement child, the generated JS should be an if statement. if this.parent?.type == 'ExpressionStatement' { if !this.right.hasCallExpression() { this.parent.type = 'EmptyStatement'; return this; } this.parent.type = 'IfStatement'; this.parent.test = test; this.parent.consequent = { "type": "BlockStatement", "body": [ { "type": "ExpressionStatement", "expression": this.right } ] }; } else { // Otherwise - if the null coalescing operator is // inside an expression, the generated JS should // look like: // // typeof left === 'undefined' || left === null ? right : left return { "type": "ConditionalExpression", "test": test, "consequent": this.right, "alternate": this.left }; }
};
NullCoalescingExpression.prototype.hasCallExpression = () -> {
return this.left?.hasCallExpression() || this.right?.hasCallExpression();
};
NullCoalescingExpression.getNextLeftName = () -> {
if (!this.nullCoalescingIndex?) { this.nullCoalescingIndex = 0; } return "nullCoalescing" + this.nullCoalescingIndex++;
};
NullCoalescingExpression.resetVariableNames = () -> {
this.nullCoalescingIndex = 0;
};
exports.NullCoalescingExpression = NullCoalescingExpression;