You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
805 lines
26 KiB
805 lines
26 KiB
/* |
|
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', |
|
ChainExpression: 'ChainExpression', |
|
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', |
|
PrivateIdentifier: 'PrivateIdentifier', |
|
Program: 'Program', |
|
Property: 'Property', |
|
PropertyDefinition: 'PropertyDefinition', |
|
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'], |
|
ChainExpression: ['expression'], |
|
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'], |
|
PrivateIdentifier: [], |
|
Program: ['body'], |
|
Property: ['key', 'value'], |
|
PropertyDefinition: ['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; |
|
} |
|
|
|
function candidateExistsInLeaveList(leavelist, candidate) { |
|
for (var i = leavelist.length - 1; i >= 0; --i) { |
|
if (leavelist[i].node === candidate) { |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
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 (candidateExistsInLeaveList(leavelist, 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)) { |
|
if (candidateExistsInLeaveList(leavelist, candidate)) { |
|
continue; |
|
} |
|
|
|
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.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 : */
|
|
|