estraverse.js
  1  /*
  2    Copyright (C) 2012-2013 Yusuke Suzuki <utatane.tea@gmail.com>
  3    Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com>
  4  
  5    Redistribution and use in source and binary forms, with or without
  6    modification, are permitted provided that the following conditions are met:
  7  
  8      * Redistributions of source code must retain the above copyright
  9        notice, this list of conditions and the following disclaimer.
 10      * Redistributions in binary form must reproduce the above copyright
 11        notice, this list of conditions and the following disclaimer in the
 12        documentation and/or other materials provided with the distribution.
 13  
 14    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 15    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 16    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 17    ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 18    DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 19    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 20    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 21    ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 22    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 23    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 24  */
 25  /*jslint vars:false, bitwise:true*/
 26  /*jshint indent:4*/
 27  /*global exports:true*/
 28  (function clone(exports) {
 29      'use strict';
 30  
 31      var Syntax,
 32          VisitorOption,
 33          VisitorKeys,
 34          BREAK,
 35          SKIP,
 36          REMOVE;
 37  
 38      function deepCopy(obj) {
 39          var ret = {}, key, val;
 40          for (key in obj) {
 41              if (obj.hasOwnProperty(key)) {
 42                  val = obj[key];
 43                  if (typeof val === 'object' && val !== null) {
 44                      ret[key] = deepCopy(val);
 45                  } else {
 46                      ret[key] = val;
 47                  }
 48              }
 49          }
 50          return ret;
 51      }
 52  
 53      // based on LLVM libc++ upper_bound / lower_bound
 54      // MIT License
 55  
 56      function upperBound(array, func) {
 57          var diff, len, i, current;
 58  
 59          len = array.length;
 60          i = 0;
 61  
 62          while (len) {
 63              diff = len >>> 1;
 64              current = i + diff;
 65              if (func(array[current])) {
 66                  len = diff;
 67              } else {
 68                  i = current + 1;
 69                  len -= diff + 1;
 70              }
 71          }
 72          return i;
 73      }
 74  
 75      Syntax = {
 76          AssignmentExpression: 'AssignmentExpression',
 77          AssignmentPattern: 'AssignmentPattern',
 78          ArrayExpression: 'ArrayExpression',
 79          ArrayPattern: 'ArrayPattern',
 80          ArrowFunctionExpression: 'ArrowFunctionExpression',
 81          AwaitExpression: 'AwaitExpression', // CAUTION: It's deferred to ES7.
 82          BlockStatement: 'BlockStatement',
 83          BinaryExpression: 'BinaryExpression',
 84          BreakStatement: 'BreakStatement',
 85          CallExpression: 'CallExpression',
 86          CatchClause: 'CatchClause',
 87          ChainExpression: 'ChainExpression',
 88          ClassBody: 'ClassBody',
 89          ClassDeclaration: 'ClassDeclaration',
 90          ClassExpression: 'ClassExpression',
 91          ComprehensionBlock: 'ComprehensionBlock',  // CAUTION: It's deferred to ES7.
 92          ComprehensionExpression: 'ComprehensionExpression',  // CAUTION: It's deferred to ES7.
 93          ConditionalExpression: 'ConditionalExpression',
 94          ContinueStatement: 'ContinueStatement',
 95          DebuggerStatement: 'DebuggerStatement',
 96          DirectiveStatement: 'DirectiveStatement',
 97          DoWhileStatement: 'DoWhileStatement',
 98          EmptyStatement: 'EmptyStatement',
 99          ExportAllDeclaration: 'ExportAllDeclaration',
100          ExportDefaultDeclaration: 'ExportDefaultDeclaration',
101          ExportNamedDeclaration: 'ExportNamedDeclaration',
102          ExportSpecifier: 'ExportSpecifier',
103          ExpressionStatement: 'ExpressionStatement',
104          ForStatement: 'ForStatement',
105          ForInStatement: 'ForInStatement',
106          ForOfStatement: 'ForOfStatement',
107          FunctionDeclaration: 'FunctionDeclaration',
108          FunctionExpression: 'FunctionExpression',
109          GeneratorExpression: 'GeneratorExpression',  // CAUTION: It's deferred to ES7.
110          Identifier: 'Identifier',
111          IfStatement: 'IfStatement',
112          ImportExpression: 'ImportExpression',
113          ImportDeclaration: 'ImportDeclaration',
114          ImportDefaultSpecifier: 'ImportDefaultSpecifier',
115          ImportNamespaceSpecifier: 'ImportNamespaceSpecifier',
116          ImportSpecifier: 'ImportSpecifier',
117          Literal: 'Literal',
118          LabeledStatement: 'LabeledStatement',
119          LogicalExpression: 'LogicalExpression',
120          MemberExpression: 'MemberExpression',
121          MetaProperty: 'MetaProperty',
122          MethodDefinition: 'MethodDefinition',
123          ModuleSpecifier: 'ModuleSpecifier',
124          NewExpression: 'NewExpression',
125          ObjectExpression: 'ObjectExpression',
126          ObjectPattern: 'ObjectPattern',
127          PrivateIdentifier: 'PrivateIdentifier',
128          Program: 'Program',
129          Property: 'Property',
130          PropertyDefinition: 'PropertyDefinition',
131          RestElement: 'RestElement',
132          ReturnStatement: 'ReturnStatement',
133          SequenceExpression: 'SequenceExpression',
134          SpreadElement: 'SpreadElement',
135          Super: 'Super',
136          SwitchStatement: 'SwitchStatement',
137          SwitchCase: 'SwitchCase',
138          TaggedTemplateExpression: 'TaggedTemplateExpression',
139          TemplateElement: 'TemplateElement',
140          TemplateLiteral: 'TemplateLiteral',
141          ThisExpression: 'ThisExpression',
142          ThrowStatement: 'ThrowStatement',
143          TryStatement: 'TryStatement',
144          UnaryExpression: 'UnaryExpression',
145          UpdateExpression: 'UpdateExpression',
146          VariableDeclaration: 'VariableDeclaration',
147          VariableDeclarator: 'VariableDeclarator',
148          WhileStatement: 'WhileStatement',
149          WithStatement: 'WithStatement',
150          YieldExpression: 'YieldExpression'
151      };
152  
153      VisitorKeys = {
154          AssignmentExpression: ['left', 'right'],
155          AssignmentPattern: ['left', 'right'],
156          ArrayExpression: ['elements'],
157          ArrayPattern: ['elements'],
158          ArrowFunctionExpression: ['params', 'body'],
159          AwaitExpression: ['argument'], // CAUTION: It's deferred to ES7.
160          BlockStatement: ['body'],
161          BinaryExpression: ['left', 'right'],
162          BreakStatement: ['label'],
163          CallExpression: ['callee', 'arguments'],
164          CatchClause: ['param', 'body'],
165          ChainExpression: ['expression'],
166          ClassBody: ['body'],
167          ClassDeclaration: ['id', 'superClass', 'body'],
168          ClassExpression: ['id', 'superClass', 'body'],
169          ComprehensionBlock: ['left', 'right'],  // CAUTION: It's deferred to ES7.
170          ComprehensionExpression: ['blocks', 'filter', 'body'],  // CAUTION: It's deferred to ES7.
171          ConditionalExpression: ['test', 'consequent', 'alternate'],
172          ContinueStatement: ['label'],
173          DebuggerStatement: [],
174          DirectiveStatement: [],
175          DoWhileStatement: ['body', 'test'],
176          EmptyStatement: [],
177          ExportAllDeclaration: ['source'],
178          ExportDefaultDeclaration: ['declaration'],
179          ExportNamedDeclaration: ['declaration', 'specifiers', 'source'],
180          ExportSpecifier: ['exported', 'local'],
181          ExpressionStatement: ['expression'],
182          ForStatement: ['init', 'test', 'update', 'body'],
183          ForInStatement: ['left', 'right', 'body'],
184          ForOfStatement: ['left', 'right', 'body'],
185          FunctionDeclaration: ['id', 'params', 'body'],
186          FunctionExpression: ['id', 'params', 'body'],
187          GeneratorExpression: ['blocks', 'filter', 'body'],  // CAUTION: It's deferred to ES7.
188          Identifier: [],
189          IfStatement: ['test', 'consequent', 'alternate'],
190          ImportExpression: ['source'],
191          ImportDeclaration: ['specifiers', 'source'],
192          ImportDefaultSpecifier: ['local'],
193          ImportNamespaceSpecifier: ['local'],
194          ImportSpecifier: ['imported', 'local'],
195          Literal: [],
196          LabeledStatement: ['label', 'body'],
197          LogicalExpression: ['left', 'right'],
198          MemberExpression: ['object', 'property'],
199          MetaProperty: ['meta', 'property'],
200          MethodDefinition: ['key', 'value'],
201          ModuleSpecifier: [],
202          NewExpression: ['callee', 'arguments'],
203          ObjectExpression: ['properties'],
204          ObjectPattern: ['properties'],
205          PrivateIdentifier: [],
206          Program: ['body'],
207          Property: ['key', 'value'],
208          PropertyDefinition: ['key', 'value'],
209          RestElement: [ 'argument' ],
210          ReturnStatement: ['argument'],
211          SequenceExpression: ['expressions'],
212          SpreadElement: ['argument'],
213          Super: [],
214          SwitchStatement: ['discriminant', 'cases'],
215          SwitchCase: ['test', 'consequent'],
216          TaggedTemplateExpression: ['tag', 'quasi'],
217          TemplateElement: [],
218          TemplateLiteral: ['quasis', 'expressions'],
219          ThisExpression: [],
220          ThrowStatement: ['argument'],
221          TryStatement: ['block', 'handler', 'finalizer'],
222          UnaryExpression: ['argument'],
223          UpdateExpression: ['argument'],
224          VariableDeclaration: ['declarations'],
225          VariableDeclarator: ['id', 'init'],
226          WhileStatement: ['test', 'body'],
227          WithStatement: ['object', 'body'],
228          YieldExpression: ['argument']
229      };
230  
231      // unique id
232      BREAK = {};
233      SKIP = {};
234      REMOVE = {};
235  
236      VisitorOption = {
237          Break: BREAK,
238          Skip: SKIP,
239          Remove: REMOVE
240      };
241  
242      function Reference(parent, key) {
243          this.parent = parent;
244          this.key = key;
245      }
246  
247      Reference.prototype.replace = function replace(node) {
248          this.parent[this.key] = node;
249      };
250  
251      Reference.prototype.remove = function remove() {
252          if (Array.isArray(this.parent)) {
253              this.parent.splice(this.key, 1);
254              return true;
255          } else {
256              this.replace(null);
257              return false;
258          }
259      };
260  
261      function Element(node, path, wrap, ref) {
262          this.node = node;
263          this.path = path;
264          this.wrap = wrap;
265          this.ref = ref;
266      }
267  
268      function Controller() { }
269  
270      // API:
271      // return property path array from root to current node
272      Controller.prototype.path = function path() {
273          var i, iz, j, jz, result, element;
274  
275          function addToPath(result, path) {
276              if (Array.isArray(path)) {
277                  for (j = 0, jz = path.length; j < jz; ++j) {
278                      result.push(path[j]);
279                  }
280              } else {
281                  result.push(path);
282              }
283          }
284  
285          // root node
286          if (!this.__current.path) {
287              return null;
288          }
289  
290          // first node is sentinel, second node is root element
291          result = [];
292          for (i = 2, iz = this.__leavelist.length; i < iz; ++i) {
293              element = this.__leavelist[i];
294              addToPath(result, element.path);
295          }
296          addToPath(result, this.__current.path);
297          return result;
298      };
299  
300      // API:
301      // return type of current node
302      Controller.prototype.type = function () {
303          var node = this.current();
304          return node.type || this.__current.wrap;
305      };
306  
307      // API:
308      // return array of parent elements
309      Controller.prototype.parents = function parents() {
310          var i, iz, result;
311  
312          // first node is sentinel
313          result = [];
314          for (i = 1, iz = this.__leavelist.length; i < iz; ++i) {
315              result.push(this.__leavelist[i].node);
316          }
317  
318          return result;
319      };
320  
321      // API:
322      // return current node
323      Controller.prototype.current = function current() {
324          return this.__current.node;
325      };
326  
327      Controller.prototype.__execute = function __execute(callback, element) {
328          var previous, result;
329  
330          result = undefined;
331  
332          previous  = this.__current;
333          this.__current = element;
334          this.__state = null;
335          if (callback) {
336              result = callback.call(this, element.node, this.__leavelist[this.__leavelist.length - 1].node);
337          }
338          this.__current = previous;
339  
340          return result;
341      };
342  
343      // API:
344      // notify control skip / break
345      Controller.prototype.notify = function notify(flag) {
346          this.__state = flag;
347      };
348  
349      // API:
350      // skip child nodes of current node
351      Controller.prototype.skip = function () {
352          this.notify(SKIP);
353      };
354  
355      // API:
356      // break traversals
357      Controller.prototype['break'] = function () {
358          this.notify(BREAK);
359      };
360  
361      // API:
362      // remove node
363      Controller.prototype.remove = function () {
364          this.notify(REMOVE);
365      };
366  
367      Controller.prototype.__initialize = function(root, visitor) {
368          this.visitor = visitor;
369          this.root = root;
370          this.__worklist = [];
371          this.__leavelist = [];
372          this.__current = null;
373          this.__state = null;
374          this.__fallback = null;
375          if (visitor.fallback === 'iteration') {
376              this.__fallback = Object.keys;
377          } else if (typeof visitor.fallback === 'function') {
378              this.__fallback = visitor.fallback;
379          }
380  
381          this.__keys = VisitorKeys;
382          if (visitor.keys) {
383              this.__keys = Object.assign(Object.create(this.__keys), visitor.keys);
384          }
385      };
386  
387      function isNode(node) {
388          if (node == null) {
389              return false;
390          }
391          return typeof node === 'object' && typeof node.type === 'string';
392      }
393  
394      function isProperty(nodeType, key) {
395          return (nodeType === Syntax.ObjectExpression || nodeType === Syntax.ObjectPattern) && 'properties' === key;
396      }
397    
398      function candidateExistsInLeaveList(leavelist, candidate) {
399          for (var i = leavelist.length - 1; i >= 0; --i) {
400              if (leavelist[i].node === candidate) {
401                  return true;
402              }
403          }
404          return false;
405      }
406  
407      Controller.prototype.traverse = function traverse(root, visitor) {
408          var worklist,
409              leavelist,
410              element,
411              node,
412              nodeType,
413              ret,
414              key,
415              current,
416              current2,
417              candidates,
418              candidate,
419              sentinel;
420  
421          this.__initialize(root, visitor);
422  
423          sentinel = {};
424  
425          // reference
426          worklist = this.__worklist;
427          leavelist = this.__leavelist;
428  
429          // initialize
430          worklist.push(new Element(root, null, null, null));
431          leavelist.push(new Element(null, null, null, null));
432  
433          while (worklist.length) {
434              element = worklist.pop();
435  
436              if (element === sentinel) {
437                  element = leavelist.pop();
438  
439                  ret = this.__execute(visitor.leave, element);
440  
441                  if (this.__state === BREAK || ret === BREAK) {
442                      return;
443                  }
444                  continue;
445              }
446  
447              if (element.node) {
448  
449                  ret = this.__execute(visitor.enter, element);
450  
451                  if (this.__state === BREAK || ret === BREAK) {
452                      return;
453                  }
454  
455                  worklist.push(sentinel);
456                  leavelist.push(element);
457  
458                  if (this.__state === SKIP || ret === SKIP) {
459                      continue;
460                  }
461  
462                  node = element.node;
463                  nodeType = node.type || element.wrap;
464                  candidates = this.__keys[nodeType];
465                  if (!candidates) {
466                      if (this.__fallback) {
467                          candidates = this.__fallback(node);
468                      } else {
469                          throw new Error('Unknown node type ' + nodeType + '.');
470                      }
471                  }
472  
473                  current = candidates.length;
474                  while ((current -= 1) >= 0) {
475                      key = candidates[current];
476                      candidate = node[key];
477                      if (!candidate) {
478                          continue;
479                      }
480  
481                      if (Array.isArray(candidate)) {
482                          current2 = candidate.length;
483                          while ((current2 -= 1) >= 0) {
484                              if (!candidate[current2]) {
485                                  continue;
486                              }
487  
488                              if (candidateExistsInLeaveList(leavelist, candidate[current2])) {
489                                continue;
490                              }
491  
492                              if (isProperty(nodeType, candidates[current])) {
493                                  element = new Element(candidate[current2], [key, current2], 'Property', null);
494                              } else if (isNode(candidate[current2])) {
495                                  element = new Element(candidate[current2], [key, current2], null, null);
496                              } else {
497                                  continue;
498                              }
499                              worklist.push(element);
500                          }
501                      } else if (isNode(candidate)) {
502                          if (candidateExistsInLeaveList(leavelist, candidate)) {
503                            continue;
504                          }
505  
506                          worklist.push(new Element(candidate, key, null, null));
507                      }
508                  }
509              }
510          }
511      };
512  
513      Controller.prototype.replace = function replace(root, visitor) {
514          var worklist,
515              leavelist,
516              node,
517              nodeType,
518              target,
519              element,
520              current,
521              current2,
522              candidates,
523              candidate,
524              sentinel,
525              outer,
526              key;
527  
528          function removeElem(element) {
529              var i,
530                  key,
531                  nextElem,
532                  parent;
533  
534              if (element.ref.remove()) {
535                  // When the reference is an element of an array.
536                  key = element.ref.key;
537                  parent = element.ref.parent;
538  
539                  // If removed from array, then decrease following items' keys.
540                  i = worklist.length;
541                  while (i--) {
542                      nextElem = worklist[i];
543                      if (nextElem.ref && nextElem.ref.parent === parent) {
544                          if  (nextElem.ref.key < key) {
545                              break;
546                          }
547                          --nextElem.ref.key;
548                      }
549                  }
550              }
551          }
552  
553          this.__initialize(root, visitor);
554  
555          sentinel = {};
556  
557          // reference
558          worklist = this.__worklist;
559          leavelist = this.__leavelist;
560  
561          // initialize
562          outer = {
563              root: root
564          };
565          element = new Element(root, null, null, new Reference(outer, 'root'));
566          worklist.push(element);
567          leavelist.push(element);
568  
569          while (worklist.length) {
570              element = worklist.pop();
571  
572              if (element === sentinel) {
573                  element = leavelist.pop();
574  
575                  target = this.__execute(visitor.leave, element);
576  
577                  // node may be replaced with null,
578                  // so distinguish between undefined and null in this place
579                  if (target !== undefined && target !== BREAK && target !== SKIP && target !== REMOVE) {
580                      // replace
581                      element.ref.replace(target);
582                  }
583  
584                  if (this.__state === REMOVE || target === REMOVE) {
585                      removeElem(element);
586                  }
587  
588                  if (this.__state === BREAK || target === BREAK) {
589                      return outer.root;
590                  }
591                  continue;
592              }
593  
594              target = this.__execute(visitor.enter, element);
595  
596              // node may be replaced with null,
597              // so distinguish between undefined and null in this place
598              if (target !== undefined && target !== BREAK && target !== SKIP && target !== REMOVE) {
599                  // replace
600                  element.ref.replace(target);
601                  element.node = target;
602              }
603  
604              if (this.__state === REMOVE || target === REMOVE) {
605                  removeElem(element);
606                  element.node = null;
607              }
608  
609              if (this.__state === BREAK || target === BREAK) {
610                  return outer.root;
611              }
612  
613              // node may be null
614              node = element.node;
615              if (!node) {
616                  continue;
617              }
618  
619              worklist.push(sentinel);
620              leavelist.push(element);
621  
622              if (this.__state === SKIP || target === SKIP) {
623                  continue;
624              }
625  
626              nodeType = node.type || element.wrap;
627              candidates = this.__keys[nodeType];
628              if (!candidates) {
629                  if (this.__fallback) {
630                      candidates = this.__fallback(node);
631                  } else {
632                      throw new Error('Unknown node type ' + nodeType + '.');
633                  }
634              }
635  
636              current = candidates.length;
637              while ((current -= 1) >= 0) {
638                  key = candidates[current];
639                  candidate = node[key];
640                  if (!candidate) {
641                      continue;
642                  }
643  
644                  if (Array.isArray(candidate)) {
645                      current2 = candidate.length;
646                      while ((current2 -= 1) >= 0) {
647                          if (!candidate[current2]) {
648                              continue;
649                          }
650                          if (isProperty(nodeType, candidates[current])) {
651                              element = new Element(candidate[current2], [key, current2], 'Property', new Reference(candidate, current2));
652                          } else if (isNode(candidate[current2])) {
653                              element = new Element(candidate[current2], [key, current2], null, new Reference(candidate, current2));
654                          } else {
655                              continue;
656                          }
657                          worklist.push(element);
658                      }
659                  } else if (isNode(candidate)) {
660                      worklist.push(new Element(candidate, key, null, new Reference(node, key)));
661                  }
662              }
663          }
664  
665          return outer.root;
666      };
667  
668      function traverse(root, visitor) {
669          var controller = new Controller();
670          return controller.traverse(root, visitor);
671      }
672  
673      function replace(root, visitor) {
674          var controller = new Controller();
675          return controller.replace(root, visitor);
676      }
677  
678      function extendCommentRange(comment, tokens) {
679          var target;
680  
681          target = upperBound(tokens, function search(token) {
682              return token.range[0] > comment.range[0];
683          });
684  
685          comment.extendedRange = [comment.range[0], comment.range[1]];
686  
687          if (target !== tokens.length) {
688              comment.extendedRange[1] = tokens[target].range[0];
689          }
690  
691          target -= 1;
692          if (target >= 0) {
693              comment.extendedRange[0] = tokens[target].range[1];
694          }
695  
696          return comment;
697      }
698  
699      function attachComments(tree, providedComments, tokens) {
700          // At first, we should calculate extended comment ranges.
701          var comments = [], comment, len, i, cursor;
702  
703          if (!tree.range) {
704              throw new Error('attachComments needs range information');
705          }
706  
707          // tokens array is empty, we attach comments to tree as 'leadingComments'
708          if (!tokens.length) {
709              if (providedComments.length) {
710                  for (i = 0, len = providedComments.length; i < len; i += 1) {
711                      comment = deepCopy(providedComments[i]);
712                      comment.extendedRange = [0, tree.range[0]];
713                      comments.push(comment);
714                  }
715                  tree.leadingComments = comments;
716              }
717              return tree;
718          }
719  
720          for (i = 0, len = providedComments.length; i < len; i += 1) {
721              comments.push(extendCommentRange(deepCopy(providedComments[i]), tokens));
722          }
723  
724          // This is based on John Freeman's implementation.
725          cursor = 0;
726          traverse(tree, {
727              enter: function (node) {
728                  var comment;
729  
730                  while (cursor < comments.length) {
731                      comment = comments[cursor];
732                      if (comment.extendedRange[1] > node.range[0]) {
733                          break;
734                      }
735  
736                      if (comment.extendedRange[1] === node.range[0]) {
737                          if (!node.leadingComments) {
738                              node.leadingComments = [];
739                          }
740                          node.leadingComments.push(comment);
741                          comments.splice(cursor, 1);
742                      } else {
743                          cursor += 1;
744                      }
745                  }
746  
747                  // already out of owned node
748                  if (cursor === comments.length) {
749                      return VisitorOption.Break;
750                  }
751  
752                  if (comments[cursor].extendedRange[0] > node.range[1]) {
753                      return VisitorOption.Skip;
754                  }
755              }
756          });
757  
758          cursor = 0;
759          traverse(tree, {
760              leave: function (node) {
761                  var comment;
762  
763                  while (cursor < comments.length) {
764                      comment = comments[cursor];
765                      if (node.range[1] < comment.extendedRange[0]) {
766                          break;
767                      }
768  
769                      if (node.range[1] === comment.extendedRange[0]) {
770                          if (!node.trailingComments) {
771                              node.trailingComments = [];
772                          }
773                          node.trailingComments.push(comment);
774                          comments.splice(cursor, 1);
775                      } else {
776                          cursor += 1;
777                      }
778                  }
779  
780                  // already out of owned node
781                  if (cursor === comments.length) {
782                      return VisitorOption.Break;
783                  }
784  
785                  if (comments[cursor].extendedRange[0] > node.range[1]) {
786                      return VisitorOption.Skip;
787                  }
788              }
789          });
790  
791          return tree;
792      }
793  
794      exports.Syntax = Syntax;
795      exports.traverse = traverse;
796      exports.replace = replace;
797      exports.attachComments = attachComments;
798      exports.VisitorKeys = VisitorKeys;
799      exports.VisitorOption = VisitorOption;
800      exports.Controller = Controller;
801      exports.cloneEnvironment = function () { return clone({}); };
802  
803      return exports;
804  }(exports));
805  /* vim: set sw=4 ts=4 et tw=80 : */