2 Copyright (C) 2012-2013 Yusuke Suzuki <utatane.tea@gmail.com>
3 Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com>
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
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.
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.
25 /*jslint vars:false, bitwise:true*/
27 /*global exports:true, define:true*/
28 (function (root, factory) {
31 // Universal Module Definition (UMD) to support AMD, CommonJS/Node.js,
32 // and plain browser loading,
33 if (typeof define === 'function' && define.amd) {
34 define(['exports'], factory);
35 } else if (typeof exports !== 'undefined') {
38 factory((root.estraverse = {}));
40 }(this, function clone(exports) {
53 function ignoreJSHintError() { }
55 isArray = Array.isArray;
57 isArray = function isArray(array) {
58 return Object.prototype.toString.call(array) === '[object Array]';
62 function deepCopy(obj) {
63 var ret = {}, key, val;
65 if (obj.hasOwnProperty(key)) {
67 if (typeof val === 'object' && val !== null) {
68 ret[key] = deepCopy(val);
77 function shallowCopy(obj) {
80 if (obj.hasOwnProperty(key)) {
86 ignoreJSHintError(shallowCopy);
88 // based on LLVM libc++ upper_bound / lower_bound
91 function upperBound(array, func) {
92 var diff, len, i, current;
100 if (func(array[current])) {
110 function lowerBound(array, func) {
111 var diff, len, i, current;
119 if (func(array[current])) {
128 ignoreJSHintError(lowerBound);
130 objectCreate = Object.create || (function () {
133 return function (o) {
139 objectKeys = Object.keys || function (o) {
147 function extend(to, from) {
148 var keys = objectKeys(from), key, i, len;
149 for (i = 0, len = keys.length; i < len; i += 1) {
157 AssignmentExpression: 'AssignmentExpression',
158 ArrayExpression: 'ArrayExpression',
159 ArrayPattern: 'ArrayPattern',
160 ArrowFunctionExpression: 'ArrowFunctionExpression',
161 AwaitExpression: 'AwaitExpression', // CAUTION: It's deferred to ES7.
162 BlockStatement: 'BlockStatement',
163 BinaryExpression: 'BinaryExpression',
164 BreakStatement: 'BreakStatement',
165 CallExpression: 'CallExpression',
166 CatchClause: 'CatchClause',
167 ClassBody: 'ClassBody',
168 ClassDeclaration: 'ClassDeclaration',
169 ClassExpression: 'ClassExpression',
170 ComprehensionBlock: 'ComprehensionBlock', // CAUTION: It's deferred to ES7.
171 ComprehensionExpression: 'ComprehensionExpression', // CAUTION: It's deferred to ES7.
172 ConditionalExpression: 'ConditionalExpression',
173 ContinueStatement: 'ContinueStatement',
174 DebuggerStatement: 'DebuggerStatement',
175 DirectiveStatement: 'DirectiveStatement',
176 DoWhileStatement: 'DoWhileStatement',
177 EmptyStatement: 'EmptyStatement',
178 ExportBatchSpecifier: 'ExportBatchSpecifier',
179 ExportDeclaration: 'ExportDeclaration',
180 ExportSpecifier: 'ExportSpecifier',
181 ExpressionStatement: 'ExpressionStatement',
182 ForStatement: 'ForStatement',
183 ForInStatement: 'ForInStatement',
184 ForOfStatement: 'ForOfStatement',
185 FunctionDeclaration: 'FunctionDeclaration',
186 FunctionExpression: 'FunctionExpression',
187 GeneratorExpression: 'GeneratorExpression', // CAUTION: It's deferred to ES7.
188 Identifier: 'Identifier',
189 IfStatement: 'IfStatement',
190 ImportDeclaration: 'ImportDeclaration',
191 ImportDefaultSpecifier: 'ImportDefaultSpecifier',
192 ImportNamespaceSpecifier: 'ImportNamespaceSpecifier',
193 ImportSpecifier: 'ImportSpecifier',
195 LabeledStatement: 'LabeledStatement',
196 LogicalExpression: 'LogicalExpression',
197 MemberExpression: 'MemberExpression',
198 MethodDefinition: 'MethodDefinition',
199 ModuleSpecifier: 'ModuleSpecifier',
200 NewExpression: 'NewExpression',
201 ObjectExpression: 'ObjectExpression',
202 ObjectPattern: 'ObjectPattern',
204 Property: 'Property',
205 ReturnStatement: 'ReturnStatement',
206 SequenceExpression: 'SequenceExpression',
207 SpreadElement: 'SpreadElement',
208 SwitchStatement: 'SwitchStatement',
209 SwitchCase: 'SwitchCase',
210 TaggedTemplateExpression: 'TaggedTemplateExpression',
211 TemplateElement: 'TemplateElement',
212 TemplateLiteral: 'TemplateLiteral',
213 ThisExpression: 'ThisExpression',
214 ThrowStatement: 'ThrowStatement',
215 TryStatement: 'TryStatement',
216 UnaryExpression: 'UnaryExpression',
217 UpdateExpression: 'UpdateExpression',
218 VariableDeclaration: 'VariableDeclaration',
219 VariableDeclarator: 'VariableDeclarator',
220 WhileStatement: 'WhileStatement',
221 WithStatement: 'WithStatement',
222 YieldExpression: 'YieldExpression'
226 AssignmentExpression: ['left', 'right'],
227 ArrayExpression: ['elements'],
228 ArrayPattern: ['elements'],
229 ArrowFunctionExpression: ['params', 'defaults', 'rest', 'body'],
230 AwaitExpression: ['argument'], // CAUTION: It's deferred to ES7.
231 BlockStatement: ['body'],
232 BinaryExpression: ['left', 'right'],
233 BreakStatement: ['label'],
234 CallExpression: ['callee', 'arguments'],
235 CatchClause: ['param', 'body'],
237 ClassDeclaration: ['id', 'body', 'superClass'],
238 ClassExpression: ['id', 'body', 'superClass'],
239 ComprehensionBlock: ['left', 'right'], // CAUTION: It's deferred to ES7.
240 ComprehensionExpression: ['blocks', 'filter', 'body'], // CAUTION: It's deferred to ES7.
241 ConditionalExpression: ['test', 'consequent', 'alternate'],
242 ContinueStatement: ['label'],
243 DebuggerStatement: [],
244 DirectiveStatement: [],
245 DoWhileStatement: ['body', 'test'],
247 ExportBatchSpecifier: [],
248 ExportDeclaration: ['declaration', 'specifiers', 'source'],
249 ExportSpecifier: ['id', 'name'],
250 ExpressionStatement: ['expression'],
251 ForStatement: ['init', 'test', 'update', 'body'],
252 ForInStatement: ['left', 'right', 'body'],
253 ForOfStatement: ['left', 'right', 'body'],
254 FunctionDeclaration: ['id', 'params', 'defaults', 'rest', 'body'],
255 FunctionExpression: ['id', 'params', 'defaults', 'rest', 'body'],
256 GeneratorExpression: ['blocks', 'filter', 'body'], // CAUTION: It's deferred to ES7.
258 IfStatement: ['test', 'consequent', 'alternate'],
259 ImportDeclaration: ['specifiers', 'source'],
260 ImportDefaultSpecifier: ['id'],
261 ImportNamespaceSpecifier: ['id'],
262 ImportSpecifier: ['id', 'name'],
264 LabeledStatement: ['label', 'body'],
265 LogicalExpression: ['left', 'right'],
266 MemberExpression: ['object', 'property'],
267 MethodDefinition: ['key', 'value'],
269 NewExpression: ['callee', 'arguments'],
270 ObjectExpression: ['properties'],
271 ObjectPattern: ['properties'],
273 Property: ['key', 'value'],
274 ReturnStatement: ['argument'],
275 SequenceExpression: ['expressions'],
276 SpreadElement: ['argument'],
277 SwitchStatement: ['discriminant', 'cases'],
278 SwitchCase: ['test', 'consequent'],
279 TaggedTemplateExpression: ['tag', 'quasi'],
281 TemplateLiteral: ['quasis', 'expressions'],
283 ThrowStatement: ['argument'],
284 TryStatement: ['block', 'handlers', 'handler', 'guardedHandlers', 'finalizer'],
285 UnaryExpression: ['argument'],
286 UpdateExpression: ['argument'],
287 VariableDeclaration: ['declarations'],
288 VariableDeclarator: ['id', 'init'],
289 WhileStatement: ['test', 'body'],
290 WithStatement: ['object', 'body'],
291 YieldExpression: ['argument']
305 function Reference(parent, key) {
306 this.parent = parent;
310 Reference.prototype.replace = function replace(node) {
311 this.parent[this.key] = node;
314 Reference.prototype.remove = function remove() {
315 if (isArray(this.parent)) {
316 this.parent.splice(this.key, 1);
324 function Element(node, path, wrap, ref) {
331 function Controller() { }
334 // return property path array from root to current node
335 Controller.prototype.path = function path() {
336 var i, iz, j, jz, result, element;
338 function addToPath(result, path) {
340 for (j = 0, jz = path.length; j < jz; ++j) {
341 result.push(path[j]);
349 if (!this.__current.path) {
353 // first node is sentinel, second node is root element
355 for (i = 2, iz = this.__leavelist.length; i < iz; ++i) {
356 element = this.__leavelist[i];
357 addToPath(result, element.path);
359 addToPath(result, this.__current.path);
364 // return type of current node
365 Controller.prototype.type = function () {
366 var node = this.current();
367 return node.type || this.__current.wrap;
371 // return array of parent elements
372 Controller.prototype.parents = function parents() {
375 // first node is sentinel
377 for (i = 1, iz = this.__leavelist.length; i < iz; ++i) {
378 result.push(this.__leavelist[i].node);
385 // return current node
386 Controller.prototype.current = function current() {
387 return this.__current.node;
390 Controller.prototype.__execute = function __execute(callback, element) {
391 var previous, result;
395 previous = this.__current;
396 this.__current = element;
399 result = callback.call(this, element.node, this.__leavelist[this.__leavelist.length - 1].node);
401 this.__current = previous;
407 // notify control skip / break
408 Controller.prototype.notify = function notify(flag) {
413 // skip child nodes of current node
414 Controller.prototype.skip = function () {
420 Controller.prototype['break'] = function () {
426 Controller.prototype.remove = function () {
430 Controller.prototype.__initialize = function(root, visitor) {
431 this.visitor = visitor;
433 this.__worklist = [];
434 this.__leavelist = [];
435 this.__current = null;
437 this.__fallback = visitor.fallback === 'iteration';
438 this.__keys = VisitorKeys;
440 this.__keys = extend(objectCreate(this.__keys), visitor.keys);
444 function isNode(node) {
448 return typeof node === 'object' && typeof node.type === 'string';
451 function isProperty(nodeType, key) {
452 return (nodeType === Syntax.ObjectExpression || nodeType === Syntax.ObjectPattern) && 'properties' === key;
455 Controller.prototype.traverse = function traverse(root, visitor) {
469 this.__initialize(root, visitor);
474 worklist = this.__worklist;
475 leavelist = this.__leavelist;
478 worklist.push(new Element(root, null, null, null));
479 leavelist.push(new Element(null, null, null, null));
481 while (worklist.length) {
482 element = worklist.pop();
484 if (element === sentinel) {
485 element = leavelist.pop();
487 ret = this.__execute(visitor.leave, element);
489 if (this.__state === BREAK || ret === BREAK) {
497 ret = this.__execute(visitor.enter, element);
499 if (this.__state === BREAK || ret === BREAK) {
503 worklist.push(sentinel);
504 leavelist.push(element);
506 if (this.__state === SKIP || ret === SKIP) {
511 nodeType = element.wrap || node.type;
512 candidates = this.__keys[nodeType];
514 if (this.__fallback) {
515 candidates = objectKeys(node);
517 throw new Error('Unknown node type ' + nodeType + '.');
521 current = candidates.length;
522 while ((current -= 1) >= 0) {
523 key = candidates[current];
524 candidate = node[key];
529 if (isArray(candidate)) {
530 current2 = candidate.length;
531 while ((current2 -= 1) >= 0) {
532 if (!candidate[current2]) {
535 if (isProperty(nodeType, candidates[current])) {
536 element = new Element(candidate[current2], [key, current2], 'Property', null);
537 } else if (isNode(candidate[current2])) {
538 element = new Element(candidate[current2], [key, current2], null, null);
542 worklist.push(element);
544 } else if (isNode(candidate)) {
545 worklist.push(new Element(candidate, key, null, null));
552 Controller.prototype.replace = function replace(root, visitor) {
553 function removeElem(element) {
559 if (element.ref.remove()) {
560 // When the reference is an element of an array.
561 key = element.ref.key;
562 parent = element.ref.parent;
564 // If removed from array, then decrease following items' keys.
567 nextElem = worklist[i];
568 if (nextElem.ref && nextElem.ref.parent === parent) {
569 if (nextElem.ref.key < key) {
592 this.__initialize(root, visitor);
597 worklist = this.__worklist;
598 leavelist = this.__leavelist;
604 element = new Element(root, null, null, new Reference(outer, 'root'));
605 worklist.push(element);
606 leavelist.push(element);
608 while (worklist.length) {
609 element = worklist.pop();
611 if (element === sentinel) {
612 element = leavelist.pop();
614 target = this.__execute(visitor.leave, element);
616 // node may be replaced with null,
617 // so distinguish between undefined and null in this place
618 if (target !== undefined && target !== BREAK && target !== SKIP && target !== REMOVE) {
620 element.ref.replace(target);
623 if (this.__state === REMOVE || target === REMOVE) {
627 if (this.__state === BREAK || target === BREAK) {
633 target = this.__execute(visitor.enter, element);
635 // node may be replaced with null,
636 // so distinguish between undefined and null in this place
637 if (target !== undefined && target !== BREAK && target !== SKIP && target !== REMOVE) {
639 element.ref.replace(target);
640 element.node = target;
643 if (this.__state === REMOVE || target === REMOVE) {
648 if (this.__state === BREAK || target === BREAK) {
658 worklist.push(sentinel);
659 leavelist.push(element);
661 if (this.__state === SKIP || target === SKIP) {
665 nodeType = element.wrap || node.type;
666 candidates = this.__keys[nodeType];
668 if (this.__fallback) {
669 candidates = objectKeys(node);
671 throw new Error('Unknown node type ' + nodeType + '.');
675 current = candidates.length;
676 while ((current -= 1) >= 0) {
677 key = candidates[current];
678 candidate = node[key];
683 if (isArray(candidate)) {
684 current2 = candidate.length;
685 while ((current2 -= 1) >= 0) {
686 if (!candidate[current2]) {
689 if (isProperty(nodeType, candidates[current])) {
690 element = new Element(candidate[current2], [key, current2], 'Property', new Reference(candidate, current2));
691 } else if (isNode(candidate[current2])) {
692 element = new Element(candidate[current2], [key, current2], null, new Reference(candidate, current2));
696 worklist.push(element);
698 } else if (isNode(candidate)) {
699 worklist.push(new Element(candidate, key, null, new Reference(node, key)));
707 function traverse(root, visitor) {
708 var controller = new Controller();
709 return controller.traverse(root, visitor);
712 function replace(root, visitor) {
713 var controller = new Controller();
714 return controller.replace(root, visitor);
717 function extendCommentRange(comment, tokens) {
720 target = upperBound(tokens, function search(token) {
721 return token.range[0] > comment.range[0];
724 comment.extendedRange = [comment.range[0], comment.range[1]];
726 if (target !== tokens.length) {
727 comment.extendedRange[1] = tokens[target].range[0];
732 comment.extendedRange[0] = tokens[target].range[1];
738 function attachComments(tree, providedComments, tokens) {
739 // At first, we should calculate extended comment ranges.
740 var comments = [], comment, len, i, cursor;
743 throw new Error('attachComments needs range information');
746 // tokens array is empty, we attach comments to tree as 'leadingComments'
747 if (!tokens.length) {
748 if (providedComments.length) {
749 for (i = 0, len = providedComments.length; i < len; i += 1) {
750 comment = deepCopy(providedComments[i]);
751 comment.extendedRange = [0, tree.range[0]];
752 comments.push(comment);
754 tree.leadingComments = comments;
759 for (i = 0, len = providedComments.length; i < len; i += 1) {
760 comments.push(extendCommentRange(deepCopy(providedComments[i]), tokens));
763 // This is based on John Freeman's implementation.
766 enter: function (node) {
769 while (cursor < comments.length) {
770 comment = comments[cursor];
771 if (comment.extendedRange[1] > node.range[0]) {
775 if (comment.extendedRange[1] === node.range[0]) {
776 if (!node.leadingComments) {
777 node.leadingComments = [];
779 node.leadingComments.push(comment);
780 comments.splice(cursor, 1);
786 // already out of owned node
787 if (cursor === comments.length) {
788 return VisitorOption.Break;
791 if (comments[cursor].extendedRange[0] > node.range[1]) {
792 return VisitorOption.Skip;
799 leave: function (node) {
802 while (cursor < comments.length) {
803 comment = comments[cursor];
804 if (node.range[1] < comment.extendedRange[0]) {
808 if (node.range[1] === comment.extendedRange[0]) {
809 if (!node.trailingComments) {
810 node.trailingComments = [];
812 node.trailingComments.push(comment);
813 comments.splice(cursor, 1);
819 // already out of owned node
820 if (cursor === comments.length) {
821 return VisitorOption.Break;
824 if (comments[cursor].extendedRange[0] > node.range[1]) {
825 return VisitorOption.Skip;
833 exports.version = '1.8.1-dev';
834 exports.Syntax = Syntax;
835 exports.traverse = traverse;
836 exports.replace = replace;
837 exports.attachComments = attachComments;
838 exports.VisitorKeys = VisitorKeys;
839 exports.VisitorOption = VisitorOption;
840 exports.Controller = Controller;
841 exports.cloneEnvironment = function () { return clone({}); };
845 /* vim: set sw=4 ts=4 et tw=80 : */