use :node; var Node = module.require('../Node').Node; fn NullPropagatingExpression(left, right) extends Node { this.type = 'NullPropagatingExpression'; this.computed = false; this.object = left; this.object.parent = this; this.property = right; this.property.parent = this; } NullPropagatingExpression.prototype.codegen = () -> { if !super.codegen() { return; } var context = this.getContext(); var childType = this.object.type; this.object = this.object.codegen(); this.property = this.property.codegen(false); // If the left expression is a function call (e.g: a()?.b) // then store its value in a separate variable to avoid // calling the function twice. if this.object.hasCallExpression?() { var id = { "type": "Identifier", "name": NullPropagatingExpression.getNextObjectName(), "__member_expression": { "type": "MemberExpression", "object": this.object, "property": this.property, "computed": false }, }; context.node.body.splice(context.position + (NullPropagatingExpression.nullPropagatingIndex - 1), 0, { "type": "VariableDeclaration", "declarations": [ { "type": "VariableDeclarator", "id": id, "init": this.object } ], "kind": "let", "codeGenerated": true }); this.object = id; } var condition; // If this node is the last NullPropagatingExpression in // the tree, then just create a simple object !== null condition if childType != 'NullPropagatingExpression' { condition = { "type": "BinaryExpression", "operator": "!==", "left": this.object, "right": { "type": "Literal", "value": null, "raw": "null" }, "__member_expression": { "type": "MemberExpression", "object": this.object, "property": this.property, "computed": false }, "__first_object": this.object }; } else { // Otherwise create an object !== null condition and add it // with logical AND to the previous condition condition = { "type": 'LogicalExpression', "operator": '&&', "left": this.object, "right": { "type": "BinaryExpression", "operator": "!==", "left": { "type": "MemberExpression", "object": this.object.__member_expression.object, "property": this.object.__member_expression.property, "computed": false }, "right": { "type": "Literal", "value": null, "raw": "null" }, }, "__member_expression": { "type": "MemberExpression", "object": this.object.__member_expression, "property": this.property, "computed": false }, "__first_object": this.object.__first_object }; } // Return the condition if this isn't the first NullPropagatingExpression if this.parent?.type == 'NullPropagatingExpression' { return condition; } // Add typeof object !== undefined check condition = { "type": 'LogicalExpression', "operator": '&&', "left": { "type": "BinaryExpression", "operator": "!==", "left": { "type": "UnaryExpression", "operator": "typeof", "argument": this.object.__first_object ?? this.object }, "right": { "type": "Literal", "value": "undefined", "raw": "\"undefined\"" }, }, "right": condition }; condition = { "type": "ConditionalExpression", "test": condition, "consequent": { "type": "MemberExpression", "object": this.object if this.object.type == 'Identifier' || !this.object.__member_expression else this.object.__member_expression, "property": this.property, "computed": false }, "alternate": { "type": "UnaryExpression", "operator": "void", "argument": { "type": "Literal", "value": 0, "raw": "0" }, "prefix": true }, "__null_propagating": true }; return condition; }; NullPropagatingExpression.prototype.hasCallExpression = () -> true; NullPropagatingExpression.getNextObjectName = () -> { if !this.nullPropagatingIndex? { this.nullPropagatingIndex = 0; this.definedObjectNames = []; } var name = "nullPropagating" + this.nullPropagatingIndex++; this.definedObjectNames.push(name); return name; }; NullPropagatingExpression.isObjectNameDefined = (name) -> { return this.definedObjectNames.indexOf(name) != -1; }; NullPropagatingExpression.resetVariableNames = () -> { this.nullPropagatingIndex = 0; this.definedObjectNames = []; }; exports.NullPropagatingExpression = NullPropagatingExpression;