3 var isObject = require('isobject');
4 var define = require('define-property');
5 var utils = require('snapdragon-util');
9 * Create a new AST `Node` with the given `val` and `type`.
12 * var node = new Node('*', 'Star');
13 * var node = new Node({type: 'star', val: '*'});
16 * @param {String|Object} `val` Pass a matched substring, or an object to merge onto the node.
17 * @param {String} `type` The node type to use when `val` is a string.
18 * @return {Object} node instance
22 function Node(val, type, parent) {
23 if (typeof type !== 'string') {
28 define(this, 'parent', parent);
29 define(this, 'isNode', true);
30 define(this, 'expect', null);
32 if (typeof type !== 'string' && isObject(val)) {
34 var keys = Object.keys(val);
35 for (var i = 0; i < keys.length; i++) {
37 if (ownNames.indexOf(key) === -1) {
48 * Returns true if the given value is a node.
51 * var Node = require('snapdragon-node');
52 * var node = new Node({type: 'foo'});
53 * console.log(Node.isNode(node)); //=> true
54 * console.log(Node.isNode({})); //=> false
56 * @param {Object} `node`
61 Node.isNode = function(node) {
62 return utils.isNode(node);
66 * Define a non-enumberable property on the node instance.
67 * Useful for adding properties that shouldn't be extended
68 * or visible during debugging.
71 * var node = new Node();
72 * node.define('foo', 'something non-enumerable');
74 * @param {String} `name`
76 * @return {Object} returns the node instance
80 Node.prototype.define = function(name, val) {
81 define(this, name, val);
86 * Returns true if `node.val` is an empty string, or `node.nodes` does
87 * not contain any non-empty text nodes.
90 * var node = new Node({type: 'text'});
91 * node.isEmpty(); //=> true
93 * node.isEmpty(); //=> false
95 * @param {Function} `fn` (optional) Filter function that is called on `node` and/or child nodes. `isEmpty` will return false immediately when the filter function returns false on any nodes.
100 Node.prototype.isEmpty = function(fn) {
101 return utils.isEmpty(this, fn);
105 * Given node `foo` and node `bar`, push node `bar` onto `foo.nodes`, and
106 * set `foo` as `bar.parent`.
109 * var foo = new Node({type: 'foo'});
110 * var bar = new Node({type: 'bar'});
113 * @param {Object} `node`
114 * @return {Number} Returns the length of `node.nodes`
118 Node.prototype.push = function(node) {
119 assert(Node.isNode(node), 'expected node to be an instance of Node');
120 define(node, 'parent', this);
122 this.nodes = this.nodes || [];
123 return this.nodes.push(node);
127 * Given node `foo` and node `bar`, unshift node `bar` onto `foo.nodes`, and
128 * set `foo` as `bar.parent`.
131 * var foo = new Node({type: 'foo'});
132 * var bar = new Node({type: 'bar'});
135 * @param {Object} `node`
136 * @return {Number} Returns the length of `node.nodes`
140 Node.prototype.unshift = function(node) {
141 assert(Node.isNode(node), 'expected node to be an instance of Node');
142 define(node, 'parent', this);
144 this.nodes = this.nodes || [];
145 return this.nodes.unshift(node);
149 * Pop a node from `node.nodes`.
152 * var node = new Node({type: 'foo'});
153 * node.push(new Node({type: 'a'}));
154 * node.push(new Node({type: 'b'}));
155 * node.push(new Node({type: 'c'}));
156 * node.push(new Node({type: 'd'}));
157 * console.log(node.nodes.length);
160 * console.log(node.nodes.length);
163 * @return {Number} Returns the popped `node`
167 Node.prototype.pop = function() {
168 return this.nodes && this.nodes.pop();
172 * Shift a node from `node.nodes`.
175 * var node = new Node({type: 'foo'});
176 * node.push(new Node({type: 'a'}));
177 * node.push(new Node({type: 'b'}));
178 * node.push(new Node({type: 'c'}));
179 * node.push(new Node({type: 'd'}));
180 * console.log(node.nodes.length);
183 * console.log(node.nodes.length);
186 * @return {Object} Returns the shifted `node`
190 Node.prototype.shift = function() {
191 return this.nodes && this.nodes.shift();
195 * Remove `node` from `node.nodes`.
198 * node.remove(childNode);
200 * @param {Object} `node`
201 * @return {Object} Returns the removed node.
205 Node.prototype.remove = function(node) {
206 assert(Node.isNode(node), 'expected node to be an instance of Node');
207 this.nodes = this.nodes || [];
208 var idx = node.index;
211 return this.nodes.splice(idx, 1);
217 * Get the first child node from `node.nodes` that matches the given `type`.
218 * If `type` is a number, the child node at that index is returned.
221 * var child = node.find(1); //<= index of the node to get
222 * var child = node.find('foo'); //<= node.type of a child node
223 * var child = node.find(/^(foo|bar)$/); //<= regex to match node.type
224 * var child = node.find(['foo', 'bar']); //<= array of node.type(s)
226 * @param {String} `type`
227 * @return {Object} Returns a child node or undefined.
231 Node.prototype.find = function(type) {
232 return utils.findNode(this.nodes, type);
236 * Return true if the node is the given `type`.
239 * var node = new Node({type: 'bar'});
240 * cosole.log(node.isType('foo')); // false
241 * cosole.log(node.isType(/^(foo|bar)$/)); // true
242 * cosole.log(node.isType(['foo', 'bar'])); // true
244 * @param {String} `type`
249 Node.prototype.isType = function(type) {
250 return utils.isType(this, type);
254 * Return true if the `node.nodes` has the given `type`.
257 * var foo = new Node({type: 'foo'});
258 * var bar = new Node({type: 'bar'});
261 * cosole.log(foo.hasType('qux')); // false
262 * cosole.log(foo.hasType(/^(qux|bar)$/)); // true
263 * cosole.log(foo.hasType(['qux', 'bar'])); // true
265 * @param {String} `type`
270 Node.prototype.hasType = function(type) {
271 return utils.hasType(this, type);
275 * Get the siblings array, or `null` if it doesn't exist.
278 * var foo = new Node({type: 'foo'});
279 * var bar = new Node({type: 'bar'});
280 * var baz = new Node({type: 'baz'});
284 * console.log(bar.siblings.length) // 2
285 * console.log(baz.siblings.length) // 2
291 Object.defineProperty(Node.prototype, 'siblings', {
293 throw new Error('node.siblings is a getter and cannot be defined');
296 return this.parent ? this.parent.nodes : null;
301 * Get the node's current index from `node.parent.nodes`.
302 * This should always be correct, even when the parent adds nodes.
305 * var foo = new Node({type: 'foo'});
306 * var bar = new Node({type: 'bar'});
307 * var baz = new Node({type: 'baz'});
308 * var qux = new Node({type: 'qux'});
313 * console.log(bar.index) // 1
314 * console.log(baz.index) // 2
315 * console.log(qux.index) // 0
321 Object.defineProperty(Node.prototype, 'index', {
322 set: function(index) {
323 define(this, 'idx', index);
326 if (!Array.isArray(this.siblings)) {
329 var tok = this.idx !== -1 ? this.siblings[this.idx] : null;
331 this.idx = this.siblings.indexOf(this);
338 * Get the previous node from the siblings array or `null`.
341 * var foo = new Node({type: 'foo'});
342 * var bar = new Node({type: 'bar'});
343 * var baz = new Node({type: 'baz'});
347 * console.log(baz.prev.type) // 'bar'
353 Object.defineProperty(Node.prototype, 'prev', {
355 throw new Error('node.prev is a getter and cannot be defined');
358 if (Array.isArray(this.siblings)) {
359 return this.siblings[this.index - 1] || this.parent.prev;
366 * Get the siblings array, or `null` if it doesn't exist.
369 * var foo = new Node({type: 'foo'});
370 * var bar = new Node({type: 'bar'});
371 * var baz = new Node({type: 'baz'});
375 * console.log(bar.siblings.length) // 2
376 * console.log(baz.siblings.length) // 2
382 Object.defineProperty(Node.prototype, 'next', {
384 throw new Error('node.next is a getter and cannot be defined');
387 if (Array.isArray(this.siblings)) {
388 return this.siblings[this.index + 1] || this.parent.next;
395 * Get the first node from `node.nodes`.
398 * var foo = new Node({type: 'foo'});
399 * var bar = new Node({type: 'bar'});
400 * var baz = new Node({type: 'baz'});
401 * var qux = new Node({type: 'qux'});
406 * console.log(foo.first.type) // 'bar'
408 * @return {Object} The first node, or undefiend
412 Object.defineProperty(Node.prototype, 'first', {
414 return this.nodes ? this.nodes[0] : null;
419 * Get the last node from `node.nodes`.
422 * var foo = new Node({type: 'foo'});
423 * var bar = new Node({type: 'bar'});
424 * var baz = new Node({type: 'baz'});
425 * var qux = new Node({type: 'qux'});
430 * console.log(foo.last.type) // 'qux'
432 * @return {Object} The last node, or undefiend
436 Object.defineProperty(Node.prototype, 'last', {
438 return this.nodes ? utils.last(this.nodes) : null;
443 * Get the last node from `node.nodes`.
446 * var foo = new Node({type: 'foo'});
447 * var bar = new Node({type: 'bar'});
448 * var baz = new Node({type: 'baz'});
449 * var qux = new Node({type: 'qux'});
454 * console.log(foo.last.type) // 'qux'
456 * @return {Object} The last node, or undefiend
460 Object.defineProperty(Node.prototype, 'scope', {
462 if (this.isScope !== true) {
463 return this.parent ? this.parent.scope : this;
470 * Get own property names from Node prototype, but only the
471 * first time `Node` is instantiated
474 function lazyKeys() {
476 ownNames = Object.getOwnPropertyNames(Node.prototype);
481 * Simplified assertion. Throws an error is `val` is falsey.
484 function assert(val, message) {
485 if (!val) throw new Error(message);
492 exports = module.exports = Node;