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 (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 objectKeys(from).forEach(function (key) {
155 AssignmentExpression: 'AssignmentExpression',
156 ArrayExpression: 'ArrayExpression',
157 ArrayPattern: 'ArrayPattern',
158 ArrowFunctionExpression: 'ArrowFunctionExpression',
159 BlockStatement: 'BlockStatement',
160 BinaryExpression: 'BinaryExpression',
161 BreakStatement: 'BreakStatement',
162 CallExpression: 'CallExpression',
163 CatchClause: 'CatchClause',
164 ClassBody: 'ClassBody',
165 ClassDeclaration: 'ClassDeclaration',
166 ClassExpression: 'ClassExpression',
167 ComprehensionBlock: 'ComprehensionBlock', // CAUTION: It's deferred to ES7.
168 ComprehensionExpression: 'ComprehensionExpression', // CAUTION: It's deferred to ES7.
169 ConditionalExpression: 'ConditionalExpression',
170 ContinueStatement: 'ContinueStatement',
171 DebuggerStatement: 'DebuggerStatement',
172 DirectiveStatement: 'DirectiveStatement',
173 DoWhileStatement: 'DoWhileStatement',
174 EmptyStatement: 'EmptyStatement',
175 ExportBatchSpecifier: 'ExportBatchSpecifier',
176 ExportDeclaration: 'ExportDeclaration',
177 ExportSpecifier: 'ExportSpecifier',
178 ExpressionStatement: 'ExpressionStatement',
179 ForStatement: 'ForStatement',
180 ForInStatement: 'ForInStatement',
181 ForOfStatement: 'ForOfStatement',
182 FunctionDeclaration: 'FunctionDeclaration',
183 FunctionExpression: 'FunctionExpression',
184 GeneratorExpression: 'GeneratorExpression', // CAUTION: It's deferred to ES7.
185 Identifier: 'Identifier',
186 IfStatement: 'IfStatement',
187 ImportDeclaration: 'ImportDeclaration',
188 ImportDefaultSpecifier: 'ImportDefaultSpecifier',
189 ImportNamespaceSpecifier: 'ImportNamespaceSpecifier',
190 ImportSpecifier: 'ImportSpecifier',
192 LabeledStatement: 'LabeledStatement',
193 LogicalExpression: 'LogicalExpression',
194 MemberExpression: 'MemberExpression',
195 MethodDefinition: 'MethodDefinition',
196 ModuleSpecifier: 'ModuleSpecifier',
197 NewExpression: 'NewExpression',
198 ObjectExpression: 'ObjectExpression',
199 ObjectPattern: 'ObjectPattern',
201 Property: 'Property',
202 ReturnStatement: 'ReturnStatement',
203 SequenceExpression: 'SequenceExpression',
204 SpreadElement: 'SpreadElement',
205 SwitchStatement: 'SwitchStatement',
206 SwitchCase: 'SwitchCase',
207 TaggedTemplateExpression: 'TaggedTemplateExpression',
208 TemplateElement: 'TemplateElement',
209 TemplateLiteral: 'TemplateLiteral',
210 ThisExpression: 'ThisExpression',
211 ThrowStatement: 'ThrowStatement',
212 TryStatement: 'TryStatement',
213 UnaryExpression: 'UnaryExpression',
214 UpdateExpression: 'UpdateExpression',
215 VariableDeclaration: 'VariableDeclaration',
216 VariableDeclarator: 'VariableDeclarator',
217 WhileStatement: 'WhileStatement',
218 WithStatement: 'WithStatement',
219 YieldExpression: 'YieldExpression'
223 AssignmentExpression: ['left', 'right'],
224 ArrayExpression: ['elements'],
225 ArrayPattern: ['elements'],
226 ArrowFunctionExpression: ['params', 'defaults', 'rest', 'body'],
227 BlockStatement: ['body'],
228 BinaryExpression: ['left', 'right'],
229 BreakStatement: ['label'],
230 CallExpression: ['callee', 'arguments'],
231 CatchClause: ['param', 'body'],
233 ClassDeclaration: ['id', 'body', 'superClass'],
234 ClassExpression: ['id', 'body', 'superClass'],
235 ComprehensionBlock: ['left', 'right'], // CAUTION: It's deferred to ES7.
236 ComprehensionExpression: ['blocks', 'filter', 'body'], // CAUTION: It's deferred to ES7.
237 ConditionalExpression: ['test', 'consequent', 'alternate'],
238 ContinueStatement: ['label'],
239 DebuggerStatement: [],
240 DirectiveStatement: [],
241 DoWhileStatement: ['body', 'test'],
243 ExportBatchSpecifier: [],
244 ExportDeclaration: ['declaration', 'specifiers', 'source'],
245 ExportSpecifier: ['id', 'name'],
246 ExpressionStatement: ['expression'],
247 ForStatement: ['init', 'test', 'update', 'body'],
248 ForInStatement: ['left', 'right', 'body'],
249 ForOfStatement: ['left', 'right', 'body'],
250 FunctionDeclaration: ['id', 'params', 'defaults', 'rest', 'body'],
251 FunctionExpression: ['id', 'params', 'defaults', 'rest', 'body'],
252 GeneratorExpression: ['blocks', 'filter', 'body'], // CAUTION: It's deferred to ES7.
254 IfStatement: ['test', 'consequent', 'alternate'],
255 ImportDeclaration: ['specifiers', 'source'],
256 ImportDefaultSpecifier: ['id'],
257 ImportNamespaceSpecifier: ['id'],
258 ImportSpecifier: ['id', 'name'],
260 LabeledStatement: ['label', 'body'],
261 LogicalExpression: ['left', 'right'],
262 MemberExpression: ['object', 'property'],
263 MethodDefinition: ['key', 'value'],
265 NewExpression: ['callee', 'arguments'],
266 ObjectExpression: ['properties'],
267 ObjectPattern: ['properties'],
269 Property: ['key', 'value'],
270 ReturnStatement: ['argument'],
271 SequenceExpression: ['expressions'],
272 SpreadElement: ['argument'],
273 SwitchStatement: ['discriminant', 'cases'],
274 SwitchCase: ['test', 'consequent'],
275 TaggedTemplateExpression: ['tag', 'quasi'],
277 TemplateLiteral: ['quasis', 'expressions'],
279 ThrowStatement: ['argument'],
280 TryStatement: ['block', 'handlers', 'handler', 'guardedHandlers', 'finalizer'],
281 UnaryExpression: ['argument'],
282 UpdateExpression: ['argument'],
283 VariableDeclaration: ['declarations'],
284 VariableDeclarator: ['id', 'init'],
285 WhileStatement: ['test', 'body'],
286 WithStatement: ['object', 'body'],
287 YieldExpression: ['argument']
301 function Reference(parent, key) {
302 this.parent = parent;
306 Reference.prototype.replace = function replace(node) {
307 this.parent[this.key] = node;
310 Reference.prototype.remove = function remove() {
311 if (isArray(this.parent)) {
312 this.parent.splice(this.key, 1);
320 function Element(node, path, wrap, ref) {
327 function Controller() { }
330 // return property path array from root to current node
331 Controller.prototype.path = function path() {
332 var i, iz, j, jz, result, element;
334 function addToPath(result, path) {
336 for (j = 0, jz = path.length; j < jz; ++j) {
337 result.push(path[j]);
345 if (!this.__current.path) {
349 // first node is sentinel, second node is root element
351 for (i = 2, iz = this.__leavelist.length; i < iz; ++i) {
352 element = this.__leavelist[i];
353 addToPath(result, element.path);
355 addToPath(result, this.__current.path);
360 // return type of current node
361 Controller.prototype.type = function () {
362 var node = this.current();
363 return node.type || this.__current.wrap;
367 // return array of parent elements
368 Controller.prototype.parents = function parents() {
371 // first node is sentinel
373 for (i = 1, iz = this.__leavelist.length; i < iz; ++i) {
374 result.push(this.__leavelist[i].node);
381 // return current node
382 Controller.prototype.current = function current() {
383 return this.__current.node;
386 Controller.prototype.__execute = function __execute(callback, element) {
387 var previous, result;
391 previous = this.__current;
392 this.__current = element;
395 result = callback.call(this, element.node, this.__leavelist[this.__leavelist.length - 1].node);
397 this.__current = previous;
403 // notify control skip / break
404 Controller.prototype.notify = function notify(flag) {
409 // skip child nodes of current node
410 Controller.prototype.skip = function () {
416 Controller.prototype['break'] = function () {
422 Controller.prototype.remove = function () {
426 Controller.prototype.__initialize = function(root, visitor) {
427 this.visitor = visitor;
429 this.__worklist = [];
430 this.__leavelist = [];
431 this.__current = null;
433 this.__fallback = visitor.fallback === 'iteration';
434 this.__keys = VisitorKeys;
436 this.__keys = extend(objectCreate(this.__keys), visitor.keys);
440 function isNode(node) {
444 return typeof node === 'object' && typeof node.type === 'string';
447 function isProperty(nodeType, key) {
448 return (nodeType === Syntax.ObjectExpression || nodeType === Syntax.ObjectPattern) && 'properties' === key;
451 Controller.prototype.traverse = function traverse(root, visitor) {
465 this.__initialize(root, visitor);
470 worklist = this.__worklist;
471 leavelist = this.__leavelist;
474 worklist.push(new Element(root, null, null, null));
475 leavelist.push(new Element(null, null, null, null));
477 while (worklist.length) {
478 element = worklist.pop();
480 if (element === sentinel) {
481 element = leavelist.pop();
483 ret = this.__execute(visitor.leave, element);
485 if (this.__state === BREAK || ret === BREAK) {
493 ret = this.__execute(visitor.enter, element);
495 if (this.__state === BREAK || ret === BREAK) {
499 worklist.push(sentinel);
500 leavelist.push(element);
502 if (this.__state === SKIP || ret === SKIP) {
507 nodeType = element.wrap || node.type;
508 candidates = this.__keys[nodeType];
510 if (this.__fallback) {
511 candidates = objectKeys(node);
513 throw new Error('Unknown node type ' + nodeType + '.');
517 current = candidates.length;
518 while ((current -= 1) >= 0) {
519 key = candidates[current];
520 candidate = node[key];
525 if (isArray(candidate)) {
526 current2 = candidate.length;
527 while ((current2 -= 1) >= 0) {
528 if (!candidate[current2]) {
531 if (isProperty(nodeType, candidates[current])) {
532 element = new Element(candidate[current2], [key, current2], 'Property', null);
533 } else if (isNode(candidate[current2])) {
534 element = new Element(candidate[current2], [key, current2], null, null);
538 worklist.push(element);
540 } else if (isNode(candidate)) {
541 worklist.push(new Element(candidate, key, null, null));
548 Controller.prototype.replace = function replace(root, visitor) {
549 function removeElem(element) {
555 if (element.ref.remove()) {
556 // When the reference is an element of an array.
557 key = element.ref.key;
558 parent = element.ref.parent;
560 // If removed from array, then decrease following items' keys.
563 nextElem = worklist[i];
564 if (nextElem.ref && nextElem.ref.parent === parent) {
565 if (nextElem.ref.key < key) {
588 this.__initialize(root, visitor);
593 worklist = this.__worklist;
594 leavelist = this.__leavelist;
600 element = new Element(root, null, null, new Reference(outer, 'root'));
601 worklist.push(element);
602 leavelist.push(element);
604 while (worklist.length) {
605 element = worklist.pop();
607 if (element === sentinel) {
608 element = leavelist.pop();
610 target = this.__execute(visitor.leave, element);
612 // node may be replaced with null,
613 // so distinguish between undefined and null in this place
614 if (target !== undefined && target !== BREAK && target !== SKIP && target !== REMOVE) {
616 element.ref.replace(target);
619 if (this.__state === REMOVE || target === REMOVE) {
623 if (this.__state === BREAK || target === BREAK) {
629 target = this.__execute(visitor.enter, element);
631 // node may be replaced with null,
632 // so distinguish between undefined and null in this place
633 if (target !== undefined && target !== BREAK && target !== SKIP && target !== REMOVE) {
635 element.ref.replace(target);
636 element.node = target;
639 if (this.__state === REMOVE || target === REMOVE) {
644 if (this.__state === BREAK || target === BREAK) {
654 worklist.push(sentinel);
655 leavelist.push(element);
657 if (this.__state === SKIP || target === SKIP) {
661 nodeType = element.wrap || node.type;
662 candidates = this.__keys[nodeType];
664 if (this.__fallback) {
665 candidates = objectKeys(node);
667 throw new Error('Unknown node type ' + nodeType + '.');
671 current = candidates.length;
672 while ((current -= 1) >= 0) {
673 key = candidates[current];
674 candidate = node[key];
679 if (isArray(candidate)) {
680 current2 = candidate.length;
681 while ((current2 -= 1) >= 0) {
682 if (!candidate[current2]) {
685 if (isProperty(nodeType, candidates[current])) {
686 element = new Element(candidate[current2], [key, current2], 'Property', new Reference(candidate, current2));
687 } else if (isNode(candidate[current2])) {
688 element = new Element(candidate[current2], [key, current2], null, new Reference(candidate, current2));
692 worklist.push(element);
694 } else if (isNode(candidate)) {
695 worklist.push(new Element(candidate, key, null, new Reference(node, key)));
703 function traverse(root, visitor) {
704 var controller = new Controller();
705 return controller.traverse(root, visitor);
708 function replace(root, visitor) {
709 var controller = new Controller();
710 return controller.replace(root, visitor);
713 function extendCommentRange(comment, tokens) {
716 target = upperBound(tokens, function search(token) {
717 return token.range[0] > comment.range[0];
720 comment.extendedRange = [comment.range[0], comment.range[1]];
722 if (target !== tokens.length) {
723 comment.extendedRange[1] = tokens[target].range[0];
728 comment.extendedRange[0] = tokens[target].range[1];
734 function attachComments(tree, providedComments, tokens) {
735 // At first, we should calculate extended comment ranges.
736 var comments = [], comment, len, i, cursor;
739 throw new Error('attachComments needs range information');
742 // tokens array is empty, we attach comments to tree as 'leadingComments'
743 if (!tokens.length) {
744 if (providedComments.length) {
745 for (i = 0, len = providedComments.length; i < len; i += 1) {
746 comment = deepCopy(providedComments[i]);
747 comment.extendedRange = [0, tree.range[0]];
748 comments.push(comment);
750 tree.leadingComments = comments;
755 for (i = 0, len = providedComments.length; i < len; i += 1) {
756 comments.push(extendCommentRange(deepCopy(providedComments[i]), tokens));
759 // This is based on John Freeman's implementation.
762 enter: function (node) {
765 while (cursor < comments.length) {
766 comment = comments[cursor];
767 if (comment.extendedRange[1] > node.range[0]) {
771 if (comment.extendedRange[1] === node.range[0]) {
772 if (!node.leadingComments) {
773 node.leadingComments = [];
775 node.leadingComments.push(comment);
776 comments.splice(cursor, 1);
782 // already out of owned node
783 if (cursor === comments.length) {
784 return VisitorOption.Break;
787 if (comments[cursor].extendedRange[0] > node.range[1]) {
788 return VisitorOption.Skip;
795 leave: function (node) {
798 while (cursor < comments.length) {
799 comment = comments[cursor];
800 if (node.range[1] < comment.extendedRange[0]) {
804 if (node.range[1] === comment.extendedRange[0]) {
805 if (!node.trailingComments) {
806 node.trailingComments = [];
808 node.trailingComments.push(comment);
809 comments.splice(cursor, 1);
815 // already out of owned node
816 if (cursor === comments.length) {
817 return VisitorOption.Break;
820 if (comments[cursor].extendedRange[0] > node.range[1]) {
821 return VisitorOption.Skip;
829 exports.version = '1.8.0';
830 exports.Syntax = Syntax;
831 exports.traverse = traverse;
832 exports.replace = replace;
833 exports.attachComments = attachComments;
834 exports.VisitorKeys = VisitorKeys;
835 exports.VisitorOption = VisitorOption;
836 exports.Controller = Controller;
838 /* vim: set sw=4 ts=4 et tw=80 : */