'use strict'; var Container = require('./Container'); var ArrayContainer = require('./ArrayContainer'); var ok = require('assert').ok; var extend = require('raptor-util/extend'); var inspect = require('util').inspect; var EventEmitter = require('events').EventEmitter; class Node { constructor(type) { this.type = type; this.statement = false; this.container = null; this.pos = null; // The character index of the node in the original source file this.tagDef = null; // The tag definition associated with this Node this._codeGeneratorFuncs = null; this._flags = {}; this._transformersApplied = {}; this._preserveWhitespace = null; this._events = null; this.data = {}; } on(event, listener) { if (!this._events) { this._events = new EventEmitter(); } this._events.on(event, listener); } emit(event, args) { if (this._events) { this._events.emit.apply(this._events, arguments); } } listenerCount(event) { if (this._events) { return this._events.listenerCount(event); } else { return 0; } } onBeforeGenerateCode(listener) { this.on('beforeGenerateCode', listener); } onAfterGenerateCode(listener) { this.on('afterGenerateCode', listener); } wrap(wrapperNode) { ok(this.container, 'Node does not belong to a container: ' + this); var replaced = this.container.replaceChild(wrapperNode, this); ok(replaced, 'Invalid state. Child does not belong to the container'); wrapperNode.appendChild(this); } replaceWith(newNode) { ok(this.container, 'Node does not belong to a container: ' + this); var replaced = this.container.replaceChild(newNode, this); ok(replaced, 'Invalid state. Child does not belong to the container'); } insertSiblingBefore(newNode) { ok(this.container, 'Node does not belong to a container: ' + this); this.container.insertChildAfter(newNode, this); } insertSiblingAfter(newNode) { ok(this.container, 'Node does not belong to a container: ' + this); this.container.insertChildAfter(newNode, this); } /** * Converts the provided `array` into a `ArrayContainer`. If the provided `array` is already an instance of a `Container` then it is simply returned. * @param {[type]} array [description] * @return {[type]} [description] */ makeContainer(array) { if (array instanceof Container) { return array; } return new ArrayContainer(this, array); } prependChild(node) { ok(this.body, 'Node does not support child nodes: ' + this); this.body.prependChild(node); } appendChild(node) { ok(this.body, 'Node does not support child nodes: ' + this); this.body.appendChild(node); } insertBefore(newNode, referenceNode) { ok(this.body, 'Node does not support child nodes: ' + this); this.body.insertBefore(newNode, referenceNode); } forEachChild(callback, thisObj) { if (this.body) { this.body.forEach(callback, thisObj); } } moveChildrenTo(targetNode) { ok(this.body, 'Node does not support child nodes: ' + this); ok(this !== targetNode, 'Target node cannot be the same as the source node'); this.body.moveChildrenTo(targetNode); } forEachNextSibling(callback, thisObj) { var container = this.container; if (container) { container.forEachNextSibling(this, callback, thisObj); } } isTransformerApplied(transformer) { return this._transformersApplied[transformer.id] === true; } setTransformerApplied(transformer) { this._transformersApplied[transformer.id] = true; } toString() { return inspect(this); } toJSON() { let result = extend({}, this); delete result.container; delete result.statement; delete result.pos; delete result._transformersApplied; delete result._codeGeneratorFuncs; delete result._flags; delete result.data; delete result.tagDef; delete result._preserveWhitespace; delete result._events; return result; } detach() { if (this.container) { this.container.removeChild(this); this.container = null; } } /** * Returns true if the current node represents a compound expression (e.g. ) * @return {Boolean} [description] */ isCompoundExpression() { return false; } isDetached() { return this.container == null; } /** * Used by the Node.js require('util').inspect function. * We default to inspecting on the simplified version * of this node that is the same version we use when * serializing to JSON. */ inspect(depth, opts) { // We inspect in the simplified version of this object t return this.toJSON(); } setType(newType) { this.type = newType; } setCodeGenerator(mode, codeGeneratorFunc) { if (arguments.length === 1) { codeGeneratorFunc = arguments[0]; mode = null; } if (!this._codeGeneratorFuncs) { this._codeGeneratorFuncs = {}; } this._codeGeneratorFuncs[mode || 'DEFAULT'] = codeGeneratorFunc; } getCodeGenerator(mode) { if (this._codeGeneratorFuncs) { return this._codeGeneratorFuncs[mode] || this._codeGeneratorFuncs.DEFAULT; } else { return undefined; } } setFlag(name) { this._flags[name] = true; } clearFlag(name) { delete this._flags[name]; } isFlagSet(name) { return this._flags.hasOwnProperty(name); } get bodyText() { var bodyText = ''; this.forEachChild((child) => { if (child.type === 'Text') { var childText = child.argument; if (childText && childText.type === 'Literal') { bodyText += childText.value; } } }); return bodyText; } get parentNode() { return this.container && this.container.node; } setPreserveWhitespace(isPreserved) { this._preserveWhitespace = isPreserved; } isPreserveWhitespace() { var preserveWhitespace = this._preserveWhitespace; if (preserveWhitespace == null) { preserveWhitespace = this.tagDef && this.tagDef.preserveWhitespace; } return preserveWhitespace === true; } } module.exports = Node;