Projekt

Obecné

Profil

Stáhnout (11 KB) Statistiky
| Větev: | Revize:
1
'use strict';
2

    
3
var isObject = require('isobject');
4
var define = require('define-property');
5
var utils = require('snapdragon-util');
6
var ownNames;
7

    
8
/**
9
 * Create a new AST `Node` with the given `val` and `type`.
10
 *
11
 * ```js
12
 * var node = new Node('*', 'Star');
13
 * var node = new Node({type: 'star', val: '*'});
14
 * ```
15
 * @name Node
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
19
 * @api public
20
 */
21

    
22
function Node(val, type, parent) {
23
  if (typeof type !== 'string') {
24
    parent = type;
25
    type = null;
26
  }
27

    
28
  define(this, 'parent', parent);
29
  define(this, 'isNode', true);
30
  define(this, 'expect', null);
31

    
32
  if (typeof type !== 'string' && isObject(val)) {
33
    lazyKeys();
34
    var keys = Object.keys(val);
35
    for (var i = 0; i < keys.length; i++) {
36
      var key = keys[i];
37
      if (ownNames.indexOf(key) === -1) {
38
        this[key] = val[key];
39
      }
40
    }
41
  } else {
42
    this.type = type;
43
    this.val = val;
44
  }
45
}
46

    
47
/**
48
 * Returns true if the given value is a node.
49
 *
50
 * ```js
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
55
 * ```
56
 * @param {Object} `node`
57
 * @returns {Boolean}
58
 * @api public
59
 */
60

    
61
Node.isNode = function(node) {
62
  return utils.isNode(node);
63
};
64

    
65
/**
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.
69
 *
70
 * ```js
71
 * var node = new Node();
72
 * node.define('foo', 'something non-enumerable');
73
 * ```
74
 * @param {String} `name`
75
 * @param {any} `val`
76
 * @return {Object} returns the node instance
77
 * @api public
78
 */
79

    
80
Node.prototype.define = function(name, val) {
81
  define(this, name, val);
82
  return this;
83
};
84

    
85
/**
86
 * Returns true if `node.val` is an empty string, or `node.nodes` does
87
 * not contain any non-empty text nodes.
88
 *
89
 * ```js
90
 * var node = new Node({type: 'text'});
91
 * node.isEmpty(); //=> true
92
 * node.val = 'foo';
93
 * node.isEmpty(); //=> false
94
 * ```
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.
96
 * @return {Boolean}
97
 * @api public
98
 */
99

    
100
Node.prototype.isEmpty = function(fn) {
101
  return utils.isEmpty(this, fn);
102
};
103

    
104
/**
105
 * Given node `foo` and node `bar`, push node `bar` onto `foo.nodes`, and
106
 * set `foo` as `bar.parent`.
107
 *
108
 * ```js
109
 * var foo = new Node({type: 'foo'});
110
 * var bar = new Node({type: 'bar'});
111
 * foo.push(bar);
112
 * ```
113
 * @param {Object} `node`
114
 * @return {Number} Returns the length of `node.nodes`
115
 * @api public
116
 */
117

    
118
Node.prototype.push = function(node) {
119
  assert(Node.isNode(node), 'expected node to be an instance of Node');
120
  define(node, 'parent', this);
121

    
122
  this.nodes = this.nodes || [];
123
  return this.nodes.push(node);
124
};
125

    
126
/**
127
 * Given node `foo` and node `bar`, unshift node `bar` onto `foo.nodes`, and
128
 * set `foo` as `bar.parent`.
129
 *
130
 * ```js
131
 * var foo = new Node({type: 'foo'});
132
 * var bar = new Node({type: 'bar'});
133
 * foo.unshift(bar);
134
 * ```
135
 * @param {Object} `node`
136
 * @return {Number} Returns the length of `node.nodes`
137
 * @api public
138
 */
139

    
140
Node.prototype.unshift = function(node) {
141
  assert(Node.isNode(node), 'expected node to be an instance of Node');
142
  define(node, 'parent', this);
143

    
144
  this.nodes = this.nodes || [];
145
  return this.nodes.unshift(node);
146
};
147

    
148
/**
149
 * Pop a node from `node.nodes`.
150
 *
151
 * ```js
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);
158
 * //=> 4
159
 * node.pop();
160
 * console.log(node.nodes.length);
161
 * //=> 3
162
 * ```
163
 * @return {Number} Returns the popped `node`
164
 * @api public
165
 */
166

    
167
Node.prototype.pop = function() {
168
  return this.nodes && this.nodes.pop();
169
};
170

    
171
/**
172
 * Shift a node from `node.nodes`.
173
 *
174
 * ```js
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);
181
 * //=> 4
182
 * node.shift();
183
 * console.log(node.nodes.length);
184
 * //=> 3
185
 * ```
186
 * @return {Object} Returns the shifted `node`
187
 * @api public
188
 */
189

    
190
Node.prototype.shift = function() {
191
  return this.nodes && this.nodes.shift();
192
};
193

    
194
/**
195
 * Remove `node` from `node.nodes`.
196
 *
197
 * ```js
198
 * node.remove(childNode);
199
 * ```
200
 * @param {Object} `node`
201
 * @return {Object} Returns the removed node.
202
 * @api public
203
 */
204

    
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;
209
  if (idx !== -1) {
210
    node.index = -1;
211
    return this.nodes.splice(idx, 1);
212
  }
213
  return null;
214
};
215

    
216
/**
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.
219
 *
220
 * ```js
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)
225
 * ```
226
 * @param {String} `type`
227
 * @return {Object} Returns a child node or undefined.
228
 * @api public
229
 */
230

    
231
Node.prototype.find = function(type) {
232
  return utils.findNode(this.nodes, type);
233
};
234

    
235
/**
236
 * Return true if the node is the given `type`.
237
 *
238
 * ```js
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
243
 * ```
244
 * @param {String} `type`
245
 * @return {Boolean}
246
 * @api public
247
 */
248

    
249
Node.prototype.isType = function(type) {
250
  return utils.isType(this, type);
251
};
252

    
253
/**
254
 * Return true if the `node.nodes` has the given `type`.
255
 *
256
 * ```js
257
 * var foo = new Node({type: 'foo'});
258
 * var bar = new Node({type: 'bar'});
259
 * foo.push(bar);
260
 *
261
 * cosole.log(foo.hasType('qux'));          // false
262
 * cosole.log(foo.hasType(/^(qux|bar)$/));  // true
263
 * cosole.log(foo.hasType(['qux', 'bar'])); // true
264
 * ```
265
 * @param {String} `type`
266
 * @return {Boolean}
267
 * @api public
268
 */
269

    
270
Node.prototype.hasType = function(type) {
271
  return utils.hasType(this, type);
272
};
273

    
274
/**
275
 * Get the siblings array, or `null` if it doesn't exist.
276
 *
277
 * ```js
278
 * var foo = new Node({type: 'foo'});
279
 * var bar = new Node({type: 'bar'});
280
 * var baz = new Node({type: 'baz'});
281
 * foo.push(bar);
282
 * foo.push(baz);
283
 *
284
 * console.log(bar.siblings.length) // 2
285
 * console.log(baz.siblings.length) // 2
286
 * ```
287
 * @return {Array}
288
 * @api public
289
 */
290

    
291
Object.defineProperty(Node.prototype, 'siblings', {
292
  set: function() {
293
    throw new Error('node.siblings is a getter and cannot be defined');
294
  },
295
  get: function() {
296
    return this.parent ? this.parent.nodes : null;
297
  }
298
});
299

    
300
/**
301
 * Get the node's current index from `node.parent.nodes`.
302
 * This should always be correct, even when the parent adds nodes.
303
 *
304
 * ```js
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'});
309
 * foo.push(bar);
310
 * foo.push(baz);
311
 * foo.unshift(qux);
312
 *
313
 * console.log(bar.index) // 1
314
 * console.log(baz.index) // 2
315
 * console.log(qux.index) // 0
316
 * ```
317
 * @return {Number}
318
 * @api public
319
 */
320

    
321
Object.defineProperty(Node.prototype, 'index', {
322
  set: function(index) {
323
    define(this, 'idx', index);
324
  },
325
  get: function() {
326
    if (!Array.isArray(this.siblings)) {
327
      return -1;
328
    }
329
    var tok = this.idx !== -1 ? this.siblings[this.idx] : null;
330
    if (tok !== this) {
331
      this.idx = this.siblings.indexOf(this);
332
    }
333
    return this.idx;
334
  }
335
});
336

    
337
/**
338
 * Get the previous node from the siblings array or `null`.
339
 *
340
 * ```js
341
 * var foo = new Node({type: 'foo'});
342
 * var bar = new Node({type: 'bar'});
343
 * var baz = new Node({type: 'baz'});
344
 * foo.push(bar);
345
 * foo.push(baz);
346
 *
347
 * console.log(baz.prev.type) // 'bar'
348
 * ```
349
 * @return {Object}
350
 * @api public
351
 */
352

    
353
Object.defineProperty(Node.prototype, 'prev', {
354
  set: function() {
355
    throw new Error('node.prev is a getter and cannot be defined');
356
  },
357
  get: function() {
358
    if (Array.isArray(this.siblings)) {
359
      return this.siblings[this.index - 1] || this.parent.prev;
360
    }
361
    return null;
362
  }
363
});
364

    
365
/**
366
 * Get the siblings array, or `null` if it doesn't exist.
367
 *
368
 * ```js
369
 * var foo = new Node({type: 'foo'});
370
 * var bar = new Node({type: 'bar'});
371
 * var baz = new Node({type: 'baz'});
372
 * foo.push(bar);
373
 * foo.push(baz);
374
 *
375
 * console.log(bar.siblings.length) // 2
376
 * console.log(baz.siblings.length) // 2
377
 * ```
378
 * @return {Object}
379
 * @api public
380
 */
381

    
382
Object.defineProperty(Node.prototype, 'next', {
383
  set: function() {
384
    throw new Error('node.next is a getter and cannot be defined');
385
  },
386
  get: function() {
387
    if (Array.isArray(this.siblings)) {
388
      return this.siblings[this.index + 1] || this.parent.next;
389
    }
390
    return null;
391
  }
392
});
393

    
394
/**
395
 * Get the first node from `node.nodes`.
396
 *
397
 * ```js
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'});
402
 * foo.push(bar);
403
 * foo.push(baz);
404
 * foo.push(qux);
405
 *
406
 * console.log(foo.first.type) // 'bar'
407
 * ```
408
 * @return {Object} The first node, or undefiend
409
 * @api public
410
 */
411

    
412
Object.defineProperty(Node.prototype, 'first', {
413
  get: function() {
414
    return this.nodes ? this.nodes[0] : null;
415
  }
416
});
417

    
418
/**
419
 * Get the last node from `node.nodes`.
420
 *
421
 * ```js
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'});
426
 * foo.push(bar);
427
 * foo.push(baz);
428
 * foo.push(qux);
429
 *
430
 * console.log(foo.last.type) // 'qux'
431
 * ```
432
 * @return {Object} The last node, or undefiend
433
 * @api public
434
 */
435

    
436
Object.defineProperty(Node.prototype, 'last', {
437
  get: function() {
438
    return this.nodes ? utils.last(this.nodes) : null;
439
  }
440
});
441

    
442
/**
443
 * Get the last node from `node.nodes`.
444
 *
445
 * ```js
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'});
450
 * foo.push(bar);
451
 * foo.push(baz);
452
 * foo.push(qux);
453
 *
454
 * console.log(foo.last.type) // 'qux'
455
 * ```
456
 * @return {Object} The last node, or undefiend
457
 * @api public
458
 */
459

    
460
Object.defineProperty(Node.prototype, 'scope', {
461
  get: function() {
462
    if (this.isScope !== true) {
463
      return this.parent ? this.parent.scope : this;
464
    }
465
    return this;
466
  }
467
});
468

    
469
/**
470
 * Get own property names from Node prototype, but only the
471
 * first time `Node` is instantiated
472
 */
473

    
474
function lazyKeys() {
475
  if (!ownNames) {
476
    ownNames = Object.getOwnPropertyNames(Node.prototype);
477
  }
478
}
479

    
480
/**
481
 * Simplified assertion. Throws an error is `val` is falsey.
482
 */
483

    
484
function assert(val, message) {
485
  if (!val) throw new Error(message);
486
}
487

    
488
/**
489
 * Expose `Node`
490
 */
491

    
492
exports = module.exports = Node;
(3-3/4)