| /* | 
|   Copyright (C) 2012-2013 Yusuke Suzuki <utatane.tea@gmail.com> | 
|   Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com> | 
|   | 
|   Redistribution and use in source and binary forms, with or without | 
|   modification, are permitted provided that the following conditions are met: | 
|   | 
|     * Redistributions of source code must retain the above copyright | 
|       notice, this list of conditions and the following disclaimer. | 
|     * Redistributions in binary form must reproduce the above copyright | 
|       notice, this list of conditions and the following disclaimer in the | 
|       documentation and/or other materials provided with the distribution. | 
|   | 
|   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | 
|   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
|   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | 
|   ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY | 
|   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | 
|   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | 
|   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | 
|   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
|   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | 
|   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
| */ | 
| /*jslint vars:false, bitwise:true*/ | 
| /*jshint indent:4*/ | 
| /*global exports:true*/ | 
| (function clone(exports) { | 
|     'use strict'; | 
|   | 
|     var Syntax, | 
|         VisitorOption, | 
|         VisitorKeys, | 
|         BREAK, | 
|         SKIP, | 
|         REMOVE; | 
|   | 
|     function deepCopy(obj) { | 
|         var ret = {}, key, val; | 
|         for (key in obj) { | 
|             if (obj.hasOwnProperty(key)) { | 
|                 val = obj[key]; | 
|                 if (typeof val === 'object' && val !== null) { | 
|                     ret[key] = deepCopy(val); | 
|                 } else { | 
|                     ret[key] = val; | 
|                 } | 
|             } | 
|         } | 
|         return ret; | 
|     } | 
|   | 
|     // based on LLVM libc++ upper_bound / lower_bound | 
|     // MIT License | 
|   | 
|     function upperBound(array, func) { | 
|         var diff, len, i, current; | 
|   | 
|         len = array.length; | 
|         i = 0; | 
|   | 
|         while (len) { | 
|             diff = len >>> 1; | 
|             current = i + diff; | 
|             if (func(array[current])) { | 
|                 len = diff; | 
|             } else { | 
|                 i = current + 1; | 
|                 len -= diff + 1; | 
|             } | 
|         } | 
|         return i; | 
|     } | 
|   | 
|     Syntax = { | 
|         AssignmentExpression: 'AssignmentExpression', | 
|         AssignmentPattern: 'AssignmentPattern', | 
|         ArrayExpression: 'ArrayExpression', | 
|         ArrayPattern: 'ArrayPattern', | 
|         ArrowFunctionExpression: 'ArrowFunctionExpression', | 
|         AwaitExpression: 'AwaitExpression', // CAUTION: It's deferred to ES7. | 
|         BlockStatement: 'BlockStatement', | 
|         BinaryExpression: 'BinaryExpression', | 
|         BreakStatement: 'BreakStatement', | 
|         CallExpression: 'CallExpression', | 
|         CatchClause: 'CatchClause', | 
|         ClassBody: 'ClassBody', | 
|         ClassDeclaration: 'ClassDeclaration', | 
|         ClassExpression: 'ClassExpression', | 
|         ComprehensionBlock: 'ComprehensionBlock',  // CAUTION: It's deferred to ES7. | 
|         ComprehensionExpression: 'ComprehensionExpression',  // CAUTION: It's deferred to ES7. | 
|         ConditionalExpression: 'ConditionalExpression', | 
|         ContinueStatement: 'ContinueStatement', | 
|         DebuggerStatement: 'DebuggerStatement', | 
|         DirectiveStatement: 'DirectiveStatement', | 
|         DoWhileStatement: 'DoWhileStatement', | 
|         EmptyStatement: 'EmptyStatement', | 
|         ExportAllDeclaration: 'ExportAllDeclaration', | 
|         ExportDefaultDeclaration: 'ExportDefaultDeclaration', | 
|         ExportNamedDeclaration: 'ExportNamedDeclaration', | 
|         ExportSpecifier: 'ExportSpecifier', | 
|         ExpressionStatement: 'ExpressionStatement', | 
|         ForStatement: 'ForStatement', | 
|         ForInStatement: 'ForInStatement', | 
|         ForOfStatement: 'ForOfStatement', | 
|         FunctionDeclaration: 'FunctionDeclaration', | 
|         FunctionExpression: 'FunctionExpression', | 
|         GeneratorExpression: 'GeneratorExpression',  // CAUTION: It's deferred to ES7. | 
|         Identifier: 'Identifier', | 
|         IfStatement: 'IfStatement', | 
|         ImportExpression: 'ImportExpression', | 
|         ImportDeclaration: 'ImportDeclaration', | 
|         ImportDefaultSpecifier: 'ImportDefaultSpecifier', | 
|         ImportNamespaceSpecifier: 'ImportNamespaceSpecifier', | 
|         ImportSpecifier: 'ImportSpecifier', | 
|         Literal: 'Literal', | 
|         LabeledStatement: 'LabeledStatement', | 
|         LogicalExpression: 'LogicalExpression', | 
|         MemberExpression: 'MemberExpression', | 
|         MetaProperty: 'MetaProperty', | 
|         MethodDefinition: 'MethodDefinition', | 
|         ModuleSpecifier: 'ModuleSpecifier', | 
|         NewExpression: 'NewExpression', | 
|         ObjectExpression: 'ObjectExpression', | 
|         ObjectPattern: 'ObjectPattern', | 
|         Program: 'Program', | 
|         Property: 'Property', | 
|         RestElement: 'RestElement', | 
|         ReturnStatement: 'ReturnStatement', | 
|         SequenceExpression: 'SequenceExpression', | 
|         SpreadElement: 'SpreadElement', | 
|         Super: 'Super', | 
|         SwitchStatement: 'SwitchStatement', | 
|         SwitchCase: 'SwitchCase', | 
|         TaggedTemplateExpression: 'TaggedTemplateExpression', | 
|         TemplateElement: 'TemplateElement', | 
|         TemplateLiteral: 'TemplateLiteral', | 
|         ThisExpression: 'ThisExpression', | 
|         ThrowStatement: 'ThrowStatement', | 
|         TryStatement: 'TryStatement', | 
|         UnaryExpression: 'UnaryExpression', | 
|         UpdateExpression: 'UpdateExpression', | 
|         VariableDeclaration: 'VariableDeclaration', | 
|         VariableDeclarator: 'VariableDeclarator', | 
|         WhileStatement: 'WhileStatement', | 
|         WithStatement: 'WithStatement', | 
|         YieldExpression: 'YieldExpression' | 
|     }; | 
|   | 
|     VisitorKeys = { | 
|         AssignmentExpression: ['left', 'right'], | 
|         AssignmentPattern: ['left', 'right'], | 
|         ArrayExpression: ['elements'], | 
|         ArrayPattern: ['elements'], | 
|         ArrowFunctionExpression: ['params', 'body'], | 
|         AwaitExpression: ['argument'], // CAUTION: It's deferred to ES7. | 
|         BlockStatement: ['body'], | 
|         BinaryExpression: ['left', 'right'], | 
|         BreakStatement: ['label'], | 
|         CallExpression: ['callee', 'arguments'], | 
|         CatchClause: ['param', 'body'], | 
|         ClassBody: ['body'], | 
|         ClassDeclaration: ['id', 'superClass', 'body'], | 
|         ClassExpression: ['id', 'superClass', 'body'], | 
|         ComprehensionBlock: ['left', 'right'],  // CAUTION: It's deferred to ES7. | 
|         ComprehensionExpression: ['blocks', 'filter', 'body'],  // CAUTION: It's deferred to ES7. | 
|         ConditionalExpression: ['test', 'consequent', 'alternate'], | 
|         ContinueStatement: ['label'], | 
|         DebuggerStatement: [], | 
|         DirectiveStatement: [], | 
|         DoWhileStatement: ['body', 'test'], | 
|         EmptyStatement: [], | 
|         ExportAllDeclaration: ['source'], | 
|         ExportDefaultDeclaration: ['declaration'], | 
|         ExportNamedDeclaration: ['declaration', 'specifiers', 'source'], | 
|         ExportSpecifier: ['exported', 'local'], | 
|         ExpressionStatement: ['expression'], | 
|         ForStatement: ['init', 'test', 'update', 'body'], | 
|         ForInStatement: ['left', 'right', 'body'], | 
|         ForOfStatement: ['left', 'right', 'body'], | 
|         FunctionDeclaration: ['id', 'params', 'body'], | 
|         FunctionExpression: ['id', 'params', 'body'], | 
|         GeneratorExpression: ['blocks', 'filter', 'body'],  // CAUTION: It's deferred to ES7. | 
|         Identifier: [], | 
|         IfStatement: ['test', 'consequent', 'alternate'], | 
|         ImportExpression: ['source'], | 
|         ImportDeclaration: ['specifiers', 'source'], | 
|         ImportDefaultSpecifier: ['local'], | 
|         ImportNamespaceSpecifier: ['local'], | 
|         ImportSpecifier: ['imported', 'local'], | 
|         Literal: [], | 
|         LabeledStatement: ['label', 'body'], | 
|         LogicalExpression: ['left', 'right'], | 
|         MemberExpression: ['object', 'property'], | 
|         MetaProperty: ['meta', 'property'], | 
|         MethodDefinition: ['key', 'value'], | 
|         ModuleSpecifier: [], | 
|         NewExpression: ['callee', 'arguments'], | 
|         ObjectExpression: ['properties'], | 
|         ObjectPattern: ['properties'], | 
|         Program: ['body'], | 
|         Property: ['key', 'value'], | 
|         RestElement: [ 'argument' ], | 
|         ReturnStatement: ['argument'], | 
|         SequenceExpression: ['expressions'], | 
|         SpreadElement: ['argument'], | 
|         Super: [], | 
|         SwitchStatement: ['discriminant', 'cases'], | 
|         SwitchCase: ['test', 'consequent'], | 
|         TaggedTemplateExpression: ['tag', 'quasi'], | 
|         TemplateElement: [], | 
|         TemplateLiteral: ['quasis', 'expressions'], | 
|         ThisExpression: [], | 
|         ThrowStatement: ['argument'], | 
|         TryStatement: ['block', 'handler', 'finalizer'], | 
|         UnaryExpression: ['argument'], | 
|         UpdateExpression: ['argument'], | 
|         VariableDeclaration: ['declarations'], | 
|         VariableDeclarator: ['id', 'init'], | 
|         WhileStatement: ['test', 'body'], | 
|         WithStatement: ['object', 'body'], | 
|         YieldExpression: ['argument'] | 
|     }; | 
|   | 
|     // unique id | 
|     BREAK = {}; | 
|     SKIP = {}; | 
|     REMOVE = {}; | 
|   | 
|     VisitorOption = { | 
|         Break: BREAK, | 
|         Skip: SKIP, | 
|         Remove: REMOVE | 
|     }; | 
|   | 
|     function Reference(parent, key) { | 
|         this.parent = parent; | 
|         this.key = key; | 
|     } | 
|   | 
|     Reference.prototype.replace = function replace(node) { | 
|         this.parent[this.key] = node; | 
|     }; | 
|   | 
|     Reference.prototype.remove = function remove() { | 
|         if (Array.isArray(this.parent)) { | 
|             this.parent.splice(this.key, 1); | 
|             return true; | 
|         } else { | 
|             this.replace(null); | 
|             return false; | 
|         } | 
|     }; | 
|   | 
|     function Element(node, path, wrap, ref) { | 
|         this.node = node; | 
|         this.path = path; | 
|         this.wrap = wrap; | 
|         this.ref = ref; | 
|     } | 
|   | 
|     function Controller() { } | 
|   | 
|     // API: | 
|     // return property path array from root to current node | 
|     Controller.prototype.path = function path() { | 
|         var i, iz, j, jz, result, element; | 
|   | 
|         function addToPath(result, path) { | 
|             if (Array.isArray(path)) { | 
|                 for (j = 0, jz = path.length; j < jz; ++j) { | 
|                     result.push(path[j]); | 
|                 } | 
|             } else { | 
|                 result.push(path); | 
|             } | 
|         } | 
|   | 
|         // root node | 
|         if (!this.__current.path) { | 
|             return null; | 
|         } | 
|   | 
|         // first node is sentinel, second node is root element | 
|         result = []; | 
|         for (i = 2, iz = this.__leavelist.length; i < iz; ++i) { | 
|             element = this.__leavelist[i]; | 
|             addToPath(result, element.path); | 
|         } | 
|         addToPath(result, this.__current.path); | 
|         return result; | 
|     }; | 
|   | 
|     // API: | 
|     // return type of current node | 
|     Controller.prototype.type = function () { | 
|         var node = this.current(); | 
|         return node.type || this.__current.wrap; | 
|     }; | 
|   | 
|     // API: | 
|     // return array of parent elements | 
|     Controller.prototype.parents = function parents() { | 
|         var i, iz, result; | 
|   | 
|         // first node is sentinel | 
|         result = []; | 
|         for (i = 1, iz = this.__leavelist.length; i < iz; ++i) { | 
|             result.push(this.__leavelist[i].node); | 
|         } | 
|   | 
|         return result; | 
|     }; | 
|   | 
|     // API: | 
|     // return current node | 
|     Controller.prototype.current = function current() { | 
|         return this.__current.node; | 
|     }; | 
|   | 
|     Controller.prototype.__execute = function __execute(callback, element) { | 
|         var previous, result; | 
|   | 
|         result = undefined; | 
|   | 
|         previous  = this.__current; | 
|         this.__current = element; | 
|         this.__state = null; | 
|         if (callback) { | 
|             result = callback.call(this, element.node, this.__leavelist[this.__leavelist.length - 1].node); | 
|         } | 
|         this.__current = previous; | 
|   | 
|         return result; | 
|     }; | 
|   | 
|     // API: | 
|     // notify control skip / break | 
|     Controller.prototype.notify = function notify(flag) { | 
|         this.__state = flag; | 
|     }; | 
|   | 
|     // API: | 
|     // skip child nodes of current node | 
|     Controller.prototype.skip = function () { | 
|         this.notify(SKIP); | 
|     }; | 
|   | 
|     // API: | 
|     // break traversals | 
|     Controller.prototype['break'] = function () { | 
|         this.notify(BREAK); | 
|     }; | 
|   | 
|     // API: | 
|     // remove node | 
|     Controller.prototype.remove = function () { | 
|         this.notify(REMOVE); | 
|     }; | 
|   | 
|     Controller.prototype.__initialize = function(root, visitor) { | 
|         this.visitor = visitor; | 
|         this.root = root; | 
|         this.__worklist = []; | 
|         this.__leavelist = []; | 
|         this.__current = null; | 
|         this.__state = null; | 
|         this.__fallback = null; | 
|         if (visitor.fallback === 'iteration') { | 
|             this.__fallback = Object.keys; | 
|         } else if (typeof visitor.fallback === 'function') { | 
|             this.__fallback = visitor.fallback; | 
|         } | 
|   | 
|         this.__keys = VisitorKeys; | 
|         if (visitor.keys) { | 
|             this.__keys = Object.assign(Object.create(this.__keys), visitor.keys); | 
|         } | 
|     }; | 
|   | 
|     function isNode(node) { | 
|         if (node == null) { | 
|             return false; | 
|         } | 
|         return typeof node === 'object' && typeof node.type === 'string'; | 
|     } | 
|   | 
|     function isProperty(nodeType, key) { | 
|         return (nodeType === Syntax.ObjectExpression || nodeType === Syntax.ObjectPattern) && 'properties' === key; | 
|     } | 
|   | 
|     Controller.prototype.traverse = function traverse(root, visitor) { | 
|         var worklist, | 
|             leavelist, | 
|             element, | 
|             node, | 
|             nodeType, | 
|             ret, | 
|             key, | 
|             current, | 
|             current2, | 
|             candidates, | 
|             candidate, | 
|             sentinel; | 
|   | 
|         this.__initialize(root, visitor); | 
|   | 
|         sentinel = {}; | 
|   | 
|         // reference | 
|         worklist = this.__worklist; | 
|         leavelist = this.__leavelist; | 
|   | 
|         // initialize | 
|         worklist.push(new Element(root, null, null, null)); | 
|         leavelist.push(new Element(null, null, null, null)); | 
|   | 
|         while (worklist.length) { | 
|             element = worklist.pop(); | 
|   | 
|             if (element === sentinel) { | 
|                 element = leavelist.pop(); | 
|   | 
|                 ret = this.__execute(visitor.leave, element); | 
|   | 
|                 if (this.__state === BREAK || ret === BREAK) { | 
|                     return; | 
|                 } | 
|                 continue; | 
|             } | 
|   | 
|             if (element.node) { | 
|   | 
|                 ret = this.__execute(visitor.enter, element); | 
|   | 
|                 if (this.__state === BREAK || ret === BREAK) { | 
|                     return; | 
|                 } | 
|   | 
|                 worklist.push(sentinel); | 
|                 leavelist.push(element); | 
|   | 
|                 if (this.__state === SKIP || ret === SKIP) { | 
|                     continue; | 
|                 } | 
|   | 
|                 node = element.node; | 
|                 nodeType = node.type || element.wrap; | 
|                 candidates = this.__keys[nodeType]; | 
|                 if (!candidates) { | 
|                     if (this.__fallback) { | 
|                         candidates = this.__fallback(node); | 
|                     } else { | 
|                         throw new Error('Unknown node type ' + nodeType + '.'); | 
|                     } | 
|                 } | 
|   | 
|                 current = candidates.length; | 
|                 while ((current -= 1) >= 0) { | 
|                     key = candidates[current]; | 
|                     candidate = node[key]; | 
|                     if (!candidate) { | 
|                         continue; | 
|                     } | 
|   | 
|                     if (Array.isArray(candidate)) { | 
|                         current2 = candidate.length; | 
|                         while ((current2 -= 1) >= 0) { | 
|                             if (!candidate[current2]) { | 
|                                 continue; | 
|                             } | 
|                             if (isProperty(nodeType, candidates[current])) { | 
|                                 element = new Element(candidate[current2], [key, current2], 'Property', null); | 
|                             } else if (isNode(candidate[current2])) { | 
|                                 element = new Element(candidate[current2], [key, current2], null, null); | 
|                             } else { | 
|                                 continue; | 
|                             } | 
|                             worklist.push(element); | 
|                         } | 
|                     } else if (isNode(candidate)) { | 
|                         worklist.push(new Element(candidate, key, null, null)); | 
|                     } | 
|                 } | 
|             } | 
|         } | 
|     }; | 
|   | 
|     Controller.prototype.replace = function replace(root, visitor) { | 
|         var worklist, | 
|             leavelist, | 
|             node, | 
|             nodeType, | 
|             target, | 
|             element, | 
|             current, | 
|             current2, | 
|             candidates, | 
|             candidate, | 
|             sentinel, | 
|             outer, | 
|             key; | 
|   | 
|         function removeElem(element) { | 
|             var i, | 
|                 key, | 
|                 nextElem, | 
|                 parent; | 
|   | 
|             if (element.ref.remove()) { | 
|                 // When the reference is an element of an array. | 
|                 key = element.ref.key; | 
|                 parent = element.ref.parent; | 
|   | 
|                 // If removed from array, then decrease following items' keys. | 
|                 i = worklist.length; | 
|                 while (i--) { | 
|                     nextElem = worklist[i]; | 
|                     if (nextElem.ref && nextElem.ref.parent === parent) { | 
|                         if  (nextElem.ref.key < key) { | 
|                             break; | 
|                         } | 
|                         --nextElem.ref.key; | 
|                     } | 
|                 } | 
|             } | 
|         } | 
|   | 
|         this.__initialize(root, visitor); | 
|   | 
|         sentinel = {}; | 
|   | 
|         // reference | 
|         worklist = this.__worklist; | 
|         leavelist = this.__leavelist; | 
|   | 
|         // initialize | 
|         outer = { | 
|             root: root | 
|         }; | 
|         element = new Element(root, null, null, new Reference(outer, 'root')); | 
|         worklist.push(element); | 
|         leavelist.push(element); | 
|   | 
|         while (worklist.length) { | 
|             element = worklist.pop(); | 
|   | 
|             if (element === sentinel) { | 
|                 element = leavelist.pop(); | 
|   | 
|                 target = this.__execute(visitor.leave, element); | 
|   | 
|                 // node may be replaced with null, | 
|                 // so distinguish between undefined and null in this place | 
|                 if (target !== undefined && target !== BREAK && target !== SKIP && target !== REMOVE) { | 
|                     // replace | 
|                     element.ref.replace(target); | 
|                 } | 
|   | 
|                 if (this.__state === REMOVE || target === REMOVE) { | 
|                     removeElem(element); | 
|                 } | 
|   | 
|                 if (this.__state === BREAK || target === BREAK) { | 
|                     return outer.root; | 
|                 } | 
|                 continue; | 
|             } | 
|   | 
|             target = this.__execute(visitor.enter, element); | 
|   | 
|             // node may be replaced with null, | 
|             // so distinguish between undefined and null in this place | 
|             if (target !== undefined && target !== BREAK && target !== SKIP && target !== REMOVE) { | 
|                 // replace | 
|                 element.ref.replace(target); | 
|                 element.node = target; | 
|             } | 
|   | 
|             if (this.__state === REMOVE || target === REMOVE) { | 
|                 removeElem(element); | 
|                 element.node = null; | 
|             } | 
|   | 
|             if (this.__state === BREAK || target === BREAK) { | 
|                 return outer.root; | 
|             } | 
|   | 
|             // node may be null | 
|             node = element.node; | 
|             if (!node) { | 
|                 continue; | 
|             } | 
|   | 
|             worklist.push(sentinel); | 
|             leavelist.push(element); | 
|   | 
|             if (this.__state === SKIP || target === SKIP) { | 
|                 continue; | 
|             } | 
|   | 
|             nodeType = node.type || element.wrap; | 
|             candidates = this.__keys[nodeType]; | 
|             if (!candidates) { | 
|                 if (this.__fallback) { | 
|                     candidates = this.__fallback(node); | 
|                 } else { | 
|                     throw new Error('Unknown node type ' + nodeType + '.'); | 
|                 } | 
|             } | 
|   | 
|             current = candidates.length; | 
|             while ((current -= 1) >= 0) { | 
|                 key = candidates[current]; | 
|                 candidate = node[key]; | 
|                 if (!candidate) { | 
|                     continue; | 
|                 } | 
|   | 
|                 if (Array.isArray(candidate)) { | 
|                     current2 = candidate.length; | 
|                     while ((current2 -= 1) >= 0) { | 
|                         if (!candidate[current2]) { | 
|                             continue; | 
|                         } | 
|                         if (isProperty(nodeType, candidates[current])) { | 
|                             element = new Element(candidate[current2], [key, current2], 'Property', new Reference(candidate, current2)); | 
|                         } else if (isNode(candidate[current2])) { | 
|                             element = new Element(candidate[current2], [key, current2], null, new Reference(candidate, current2)); | 
|                         } else { | 
|                             continue; | 
|                         } | 
|                         worklist.push(element); | 
|                     } | 
|                 } else if (isNode(candidate)) { | 
|                     worklist.push(new Element(candidate, key, null, new Reference(node, key))); | 
|                 } | 
|             } | 
|         } | 
|   | 
|         return outer.root; | 
|     }; | 
|   | 
|     function traverse(root, visitor) { | 
|         var controller = new Controller(); | 
|         return controller.traverse(root, visitor); | 
|     } | 
|   | 
|     function replace(root, visitor) { | 
|         var controller = new Controller(); | 
|         return controller.replace(root, visitor); | 
|     } | 
|   | 
|     function extendCommentRange(comment, tokens) { | 
|         var target; | 
|   | 
|         target = upperBound(tokens, function search(token) { | 
|             return token.range[0] > comment.range[0]; | 
|         }); | 
|   | 
|         comment.extendedRange = [comment.range[0], comment.range[1]]; | 
|   | 
|         if (target !== tokens.length) { | 
|             comment.extendedRange[1] = tokens[target].range[0]; | 
|         } | 
|   | 
|         target -= 1; | 
|         if (target >= 0) { | 
|             comment.extendedRange[0] = tokens[target].range[1]; | 
|         } | 
|   | 
|         return comment; | 
|     } | 
|   | 
|     function attachComments(tree, providedComments, tokens) { | 
|         // At first, we should calculate extended comment ranges. | 
|         var comments = [], comment, len, i, cursor; | 
|   | 
|         if (!tree.range) { | 
|             throw new Error('attachComments needs range information'); | 
|         } | 
|   | 
|         // tokens array is empty, we attach comments to tree as 'leadingComments' | 
|         if (!tokens.length) { | 
|             if (providedComments.length) { | 
|                 for (i = 0, len = providedComments.length; i < len; i += 1) { | 
|                     comment = deepCopy(providedComments[i]); | 
|                     comment.extendedRange = [0, tree.range[0]]; | 
|                     comments.push(comment); | 
|                 } | 
|                 tree.leadingComments = comments; | 
|             } | 
|             return tree; | 
|         } | 
|   | 
|         for (i = 0, len = providedComments.length; i < len; i += 1) { | 
|             comments.push(extendCommentRange(deepCopy(providedComments[i]), tokens)); | 
|         } | 
|   | 
|         // This is based on John Freeman's implementation. | 
|         cursor = 0; | 
|         traverse(tree, { | 
|             enter: function (node) { | 
|                 var comment; | 
|   | 
|                 while (cursor < comments.length) { | 
|                     comment = comments[cursor]; | 
|                     if (comment.extendedRange[1] > node.range[0]) { | 
|                         break; | 
|                     } | 
|   | 
|                     if (comment.extendedRange[1] === node.range[0]) { | 
|                         if (!node.leadingComments) { | 
|                             node.leadingComments = []; | 
|                         } | 
|                         node.leadingComments.push(comment); | 
|                         comments.splice(cursor, 1); | 
|                     } else { | 
|                         cursor += 1; | 
|                     } | 
|                 } | 
|   | 
|                 // already out of owned node | 
|                 if (cursor === comments.length) { | 
|                     return VisitorOption.Break; | 
|                 } | 
|   | 
|                 if (comments[cursor].extendedRange[0] > node.range[1]) { | 
|                     return VisitorOption.Skip; | 
|                 } | 
|             } | 
|         }); | 
|   | 
|         cursor = 0; | 
|         traverse(tree, { | 
|             leave: function (node) { | 
|                 var comment; | 
|   | 
|                 while (cursor < comments.length) { | 
|                     comment = comments[cursor]; | 
|                     if (node.range[1] < comment.extendedRange[0]) { | 
|                         break; | 
|                     } | 
|   | 
|                     if (node.range[1] === comment.extendedRange[0]) { | 
|                         if (!node.trailingComments) { | 
|                             node.trailingComments = []; | 
|                         } | 
|                         node.trailingComments.push(comment); | 
|                         comments.splice(cursor, 1); | 
|                     } else { | 
|                         cursor += 1; | 
|                     } | 
|                 } | 
|   | 
|                 // already out of owned node | 
|                 if (cursor === comments.length) { | 
|                     return VisitorOption.Break; | 
|                 } | 
|   | 
|                 if (comments[cursor].extendedRange[0] > node.range[1]) { | 
|                     return VisitorOption.Skip; | 
|                 } | 
|             } | 
|         }); | 
|   | 
|         return tree; | 
|     } | 
|   | 
|     exports.version = require('./package.json').version; | 
|     exports.Syntax = Syntax; | 
|     exports.traverse = traverse; | 
|     exports.replace = replace; | 
|     exports.attachComments = attachComments; | 
|     exports.VisitorKeys = VisitorKeys; | 
|     exports.VisitorOption = VisitorOption; | 
|     exports.Controller = Controller; | 
|     exports.cloneEnvironment = function () { return clone({}); }; | 
|   | 
|     return exports; | 
| }(exports)); | 
| /* vim: set sw=4 ts=4 et tw=80 : */ |