1 /*
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.
24 */
25 /*jslint vars:false, bitwise:true*/
26 /*jshint indent:4*/
27 /*global exports:true, define:true*/
28 (function (root, factory) {
29 'use strict';
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') {
36 factory(exports);
37 } else {
38 factory((root.estraverse = {}));
39 }
40 }(this, function (exports) {
41 'use strict';
43 var Syntax,
44 isArray,
45 VisitorOption,
46 VisitorKeys,
47 BREAK,
48 SKIP;
50 Syntax = {
51 AssignmentExpression: 'AssignmentExpression',
52 ArrayExpression: 'ArrayExpression',
53 ArrayPattern: 'ArrayPattern',
54 ArrowFunctionExpression: 'ArrowFunctionExpression',
55 BlockStatement: 'BlockStatement',
56 BinaryExpression: 'BinaryExpression',
57 BreakStatement: 'BreakStatement',
58 CallExpression: 'CallExpression',
59 CatchClause: 'CatchClause',
60 ClassBody: 'ClassBody',
61 ClassDeclaration: 'ClassDeclaration',
62 ClassExpression: 'ClassExpression',
63 ConditionalExpression: 'ConditionalExpression',
64 ContinueStatement: 'ContinueStatement',
65 DebuggerStatement: 'DebuggerStatement',
66 DirectiveStatement: 'DirectiveStatement',
67 DoWhileStatement: 'DoWhileStatement',
68 EmptyStatement: 'EmptyStatement',
69 ExpressionStatement: 'ExpressionStatement',
70 ForStatement: 'ForStatement',
71 ForInStatement: 'ForInStatement',
72 FunctionDeclaration: 'FunctionDeclaration',
73 FunctionExpression: 'FunctionExpression',
74 Identifier: 'Identifier',
75 IfStatement: 'IfStatement',
76 Literal: 'Literal',
77 LabeledStatement: 'LabeledStatement',
78 LogicalExpression: 'LogicalExpression',
79 MemberExpression: 'MemberExpression',
80 MethodDefinition: 'MethodDefinition',
81 NewExpression: 'NewExpression',
82 ObjectExpression: 'ObjectExpression',
83 ObjectPattern: 'ObjectPattern',
84 Program: 'Program',
85 Property: 'Property',
86 ReturnStatement: 'ReturnStatement',
87 SequenceExpression: 'SequenceExpression',
88 SwitchStatement: 'SwitchStatement',
89 SwitchCase: 'SwitchCase',
90 ThisExpression: 'ThisExpression',
91 ThrowStatement: 'ThrowStatement',
92 TryStatement: 'TryStatement',
93 UnaryExpression: 'UnaryExpression',
94 UpdateExpression: 'UpdateExpression',
95 VariableDeclaration: 'VariableDeclaration',
96 VariableDeclarator: 'VariableDeclarator',
97 WhileStatement: 'WhileStatement',
98 WithStatement: 'WithStatement',
99 YieldExpression: 'YieldExpression'
100 };
102 function ignoreJSHintError() { }
104 isArray = Array.isArray;
105 if (!isArray) {
106 isArray = function isArray(array) {
107 return Object.prototype.toString.call(array) === '[object Array]';
108 };
109 }
111 function deepCopy(obj) {
112 var ret = {}, key, val;
113 for (key in obj) {
114 if (obj.hasOwnProperty(key)) {
115 val = obj[key];
116 if (typeof val === 'object' && val !== null) {
117 ret[key] = deepCopy(val);
118 } else {
119 ret[key] = val;
120 }
121 }
122 }
123 return ret;
124 }
126 function shallowCopy(obj) {
127 var ret = {}, key;
128 for (key in obj) {
129 if (obj.hasOwnProperty(key)) {
130 ret[key] = obj[key];
131 }
132 }
133 return ret;
134 }
135 ignoreJSHintError(shallowCopy);
137 // based on LLVM libc++ upper_bound / lower_bound
138 // MIT License
140 function upperBound(array, func) {
141 var diff, len, i, current;
143 len = array.length;
144 i = 0;
146 while (len) {
147 diff = len >>> 1;
148 current = i + diff;
149 if (func(array[current])) {
150 len = diff;
151 } else {
152 i = current + 1;
153 len -= diff + 1;
154 }
155 }
156 return i;
157 }
159 function lowerBound(array, func) {
160 var diff, len, i, current;
162 len = array.length;
163 i = 0;
165 while (len) {
166 diff = len >>> 1;
167 current = i + diff;
168 if (func(array[current])) {
169 i = current + 1;
170 len -= diff + 1;
171 } else {
172 len = diff;
173 }
174 }
175 return i;
176 }
177 ignoreJSHintError(lowerBound);
179 VisitorKeys = {
180 AssignmentExpression: ['left', 'right'],
181 ArrayExpression: ['elements'],
182 ArrayPattern: ['elements'],
183 ArrowFunctionExpression: ['params', 'defaults', 'rest', 'body'],
184 BlockStatement: ['body'],
185 BinaryExpression: ['left', 'right'],
186 BreakStatement: ['label'],
187 CallExpression: ['callee', 'arguments'],
188 CatchClause: ['param', 'body'],
189 ClassBody: ['body'],
190 ClassDeclaration: ['id', 'body', 'superClass'],
191 ClassExpression: ['id', 'body', 'superClass'],
192 ConditionalExpression: ['test', 'consequent', 'alternate'],
193 ContinueStatement: ['label'],
194 DebuggerStatement: [],
195 DirectiveStatement: [],
196 DoWhileStatement: ['body', 'test'],
197 EmptyStatement: [],
198 ExpressionStatement: ['expression'],
199 ForStatement: ['init', 'test', 'update', 'body'],
200 ForInStatement: ['left', 'right', 'body'],
201 ForOfStatement: ['left', 'right', 'body'],
202 FunctionDeclaration: ['id', 'params', 'defaults', 'rest', 'body'],
203 FunctionExpression: ['id', 'params', 'defaults', 'rest', 'body'],
204 Identifier: [],
205 IfStatement: ['test', 'consequent', 'alternate'],
206 Literal: [],
207 LabeledStatement: ['label', 'body'],
208 LogicalExpression: ['left', 'right'],
209 MemberExpression: ['object', 'property'],
210 MethodDefinition: ['key', 'value'],
211 NewExpression: ['callee', 'arguments'],
212 ObjectExpression: ['properties'],
213 ObjectPattern: ['properties'],
214 Program: ['body'],
215 Property: ['key', 'value'],
216 ReturnStatement: ['argument'],
217 SequenceExpression: ['expressions'],
218 SwitchStatement: ['discriminant', 'cases'],
219 SwitchCase: ['test', 'consequent'],
220 ThisExpression: [],
221 ThrowStatement: ['argument'],
222 TryStatement: ['block', 'handlers', 'handler', 'guardedHandlers', 'finalizer'],
223 UnaryExpression: ['argument'],
224 UpdateExpression: ['argument'],
225 VariableDeclaration: ['declarations'],
226 VariableDeclarator: ['id', 'init'],
227 WhileStatement: ['test', 'body'],
228 WithStatement: ['object', 'body'],
229 YieldExpression: ['argument']
230 };
232 // unique id
233 BREAK = {};
234 SKIP = {};
236 VisitorOption = {
237 Break: BREAK,
238 Skip: SKIP
239 };
241 function Reference(parent, key) {
242 this.parent = parent;
243 this.key = key;
244 }
246 Reference.prototype.replace = function replace(node) {
247 this.parent[this.key] = node;
248 };
250 function Element(node, path, wrap, ref) {
251 this.node = node;
252 this.path = path;
253 this.wrap = wrap;
254 this.ref = ref;
255 }
257 function Controller() { }
259 // API:
260 // return property path array from root to current node
261 Controller.prototype.path = function path() {
262 var i, iz, j, jz, result, element;
264 function addToPath(result, path) {
265 if (isArray(path)) {
266 for (j = 0, jz = path.length; j < jz; ++j) {
267 result.push(path[j]);
268 }
269 } else {
270 result.push(path);
271 }
272 }
274 // root node
275 if (!this.__current.path) {
276 return null;
277 }
279 // first node is sentinel, second node is root element
280 result = [];
281 for (i = 2, iz = this.__leavelist.length; i < iz; ++i) {
282 element = this.__leavelist[i];
283 addToPath(result, element.path);
284 }
285 addToPath(result, this.__current.path);
286 return result;
287 };
289 // API:
290 // return array of parent elements
291 Controller.prototype.parents = function parents() {
292 var i, iz, result;
294 // first node is sentinel
295 result = [];
296 for (i = 1, iz = this.__leavelist.length; i < iz; ++i) {
297 result.push(this.__leavelist[i].node);
298 }
300 return result;
301 };
303 // API:
304 // return current node
305 Controller.prototype.current = function current() {
306 return this.__current.node;
307 };
309 Controller.prototype.__execute = function __execute(callback, element) {
310 var previous, result;
312 result = undefined;
314 previous = this.__current;
315 this.__current = element;
316 this.__state = null;
317 if (callback) {
318 result = callback.call(this, element.node, this.__leavelist[this.__leavelist.length - 1].node);
319 }
320 this.__current = previous;
322 return result;
323 };
325 // API:
326 // notify control skip / break
327 Controller.prototype.notify = function notify(flag) {
328 this.__state = flag;
329 };
331 // API:
332 // skip child nodes of current node
333 Controller.prototype.skip = function () {
334 this.notify(SKIP);
335 };
337 // API:
338 // break traversals
339 Controller.prototype['break'] = function () {
340 this.notify(BREAK);
341 };
343 Controller.prototype.__initialize = function(root, visitor) {
344 this.visitor = visitor;
345 this.root = root;
346 this.__worklist = [];
347 this.__leavelist = [];
348 this.__current = null;
349 this.__state = null;
350 };
352 Controller.prototype.traverse = function traverse(root, visitor) {
353 var worklist,
354 leavelist,
355 element,
356 node,
357 nodeType,
358 ret,
359 key,
360 current,
361 current2,
362 candidates,
363 candidate,
364 sentinel;
366 this.__initialize(root, visitor);
368 sentinel = {};
370 // reference
371 worklist = this.__worklist;
372 leavelist = this.__leavelist;
374 // initialize
375 worklist.push(new Element(root, null, null, null));
376 leavelist.push(new Element(null, null, null, null));
378 while (worklist.length) {
379 element = worklist.pop();
381 if (element === sentinel) {
382 element = leavelist.pop();
384 ret = this.__execute(visitor.leave, element);
386 if (this.__state === BREAK || ret === BREAK) {
387 return;
388 }
389 continue;
390 }
392 if (element.node) {
394 ret = this.__execute(visitor.enter, element);
396 if (this.__state === BREAK || ret === BREAK) {
397 return;
398 }
400 worklist.push(sentinel);
401 leavelist.push(element);
403 if (this.__state === SKIP || ret === SKIP) {
404 continue;
405 }
407 node = element.node;
408 nodeType = element.wrap || node.type;
409 candidates = VisitorKeys[nodeType];
411 current = candidates.length;
412 while ((current -= 1) >= 0) {
413 key = candidates[current];
414 candidate = node[key];
415 if (!candidate) {
416 continue;
417 }
419 if (!isArray(candidate)) {
420 worklist.push(new Element(candidate, key, null, null));
421 continue;
422 }
424 current2 = candidate.length;
425 while ((current2 -= 1) >= 0) {
426 if (!candidate[current2]) {
427 continue;
428 }
429 if ((nodeType === Syntax.ObjectExpression || nodeType === Syntax.ObjectPattern) && 'properties' === candidates[current]) {
430 element = new Element(candidate[current2], [key, current2], 'Property', null);
431 } else {
432 element = new Element(candidate[current2], [key, current2], null, null);
433 }
434 worklist.push(element);
435 }
436 }
437 }
438 }
439 };
441 Controller.prototype.replace = function replace(root, visitor) {
442 var worklist,
443 leavelist,
444 node,
445 nodeType,
446 target,
447 element,
448 current,
449 current2,
450 candidates,
451 candidate,
452 sentinel,
453 outer,
454 key;
456 this.__initialize(root, visitor);
458 sentinel = {};
460 // reference
461 worklist = this.__worklist;
462 leavelist = this.__leavelist;
464 // initialize
465 outer = {
466 root: root
467 };
468 element = new Element(root, null, null, new Reference(outer, 'root'));
469 worklist.push(element);
470 leavelist.push(element);
472 while (worklist.length) {
473 element = worklist.pop();
475 if (element === sentinel) {
476 element = leavelist.pop();
478 target = this.__execute(visitor.leave, element);
480 // node may be replaced with null,
481 // so distinguish between undefined and null in this place
482 if (target !== undefined && target !== BREAK && target !== SKIP) {
483 // replace
484 element.ref.replace(target);
485 }
487 if (this.__state === BREAK || target === BREAK) {
488 return outer.root;
489 }
490 continue;
491 }
493 target = this.__execute(visitor.enter, element);
495 // node may be replaced with null,
496 // so distinguish between undefined and null in this place
497 if (target !== undefined && target !== BREAK && target !== SKIP) {
498 // replace
499 element.ref.replace(target);
500 element.node = target;
501 }
503 if (this.__state === BREAK || target === BREAK) {
504 return outer.root;
505 }
507 // node may be null
508 node = element.node;
509 if (!node) {
510 continue;
511 }
513 worklist.push(sentinel);
514 leavelist.push(element);
516 if (this.__state === SKIP || target === SKIP) {
517 continue;
518 }
520 nodeType = element.wrap || node.type;
521 candidates = VisitorKeys[nodeType];
523 current = candidates.length;
524 while ((current -= 1) >= 0) {
525 key = candidates[current];
526 candidate = node[key];
527 if (!candidate) {
528 continue;
529 }
531 if (!isArray(candidate)) {
532 worklist.push(new Element(candidate, key, null, new Reference(node, key)));
533 continue;
534 }
536 current2 = candidate.length;
537 while ((current2 -= 1) >= 0) {
538 if (!candidate[current2]) {
539 continue;
540 }
541 if (nodeType === Syntax.ObjectExpression && 'properties' === candidates[current]) {
542 element = new Element(candidate[current2], [key, current2], 'Property', new Reference(candidate, current2));
543 } else {
544 element = new Element(candidate[current2], [key, current2], null, new Reference(candidate, current2));
545 }
546 worklist.push(element);
547 }
548 }
549 }
551 return outer.root;
552 };
554 function traverse(root, visitor) {
555 var controller = new Controller();
556 return controller.traverse(root, visitor);
557 }
559 function replace(root, visitor) {
560 var controller = new Controller();
561 return controller.replace(root, visitor);
562 }
564 function extendCommentRange(comment, tokens) {
565 var target;
567 target = upperBound(tokens, function search(token) {
568 return token.range[0] > comment.range[0];
569 });
571 comment.extendedRange = [comment.range[0], comment.range[1]];
573 if (target !== tokens.length) {
574 comment.extendedRange[1] = tokens[target].range[0];
575 }
577 target -= 1;
578 if (target >= 0) {
579 comment.extendedRange[0] = tokens[target].range[1];
580 }
582 return comment;
583 }
585 function attachComments(tree, providedComments, tokens) {
586 // At first, we should calculate extended comment ranges.
587 var comments = [], comment, len, i, cursor;
589 if (!tree.range) {
590 throw new Error('attachComments needs range information');
591 }
593 // tokens array is empty, we attach comments to tree as 'leadingComments'
594 if (!tokens.length) {
595 if (providedComments.length) {
596 for (i = 0, len = providedComments.length; i < len; i += 1) {
597 comment = deepCopy(providedComments[i]);
598 comment.extendedRange = [0, tree.range[0]];
599 comments.push(comment);
600 }
601 tree.leadingComments = comments;
602 }
603 return tree;
604 }
606 for (i = 0, len = providedComments.length; i < len; i += 1) {
607 comments.push(extendCommentRange(deepCopy(providedComments[i]), tokens));
608 }
610 // This is based on John Freeman's implementation.
611 cursor = 0;
612 traverse(tree, {
613 enter: function (node) {
614 var comment;
616 while (cursor < comments.length) {
617 comment = comments[cursor];
618 if (comment.extendedRange[1] > node.range[0]) {
619 break;
620 }
622 if (comment.extendedRange[1] === node.range[0]) {
623 if (!node.leadingComments) {
624 node.leadingComments = [];
625 }
626 node.leadingComments.push(comment);
627 comments.splice(cursor, 1);
628 } else {
629 cursor += 1;
630 }
631 }
633 // already out of owned node
634 if (cursor === comments.length) {
635 return VisitorOption.Break;
636 }
638 if (comments[cursor].extendedRange[0] > node.range[1]) {
639 return VisitorOption.Skip;
640 }
641 }
642 });
644 cursor = 0;
645 traverse(tree, {
646 leave: function (node) {
647 var comment;
649 while (cursor < comments.length) {
650 comment = comments[cursor];
651 if (node.range[1] < comment.extendedRange[0]) {
652 break;
653 }
655 if (node.range[1] === comment.extendedRange[0]) {
656 if (!node.trailingComments) {
657 node.trailingComments = [];
658 }
659 node.trailingComments.push(comment);
660 comments.splice(cursor, 1);
661 } else {
662 cursor += 1;
663 }
664 }
666 // already out of owned node
667 if (cursor === comments.length) {
668 return VisitorOption.Break;
669 }
671 if (comments[cursor].extendedRange[0] > node.range[1]) {
672 return VisitorOption.Skip;
673 }
674 }
675 });
677 return tree;
678 }
680 exports.version = '1.5.1-dev';
681 exports.Syntax = Syntax;
682 exports.traverse = traverse;
683 exports.replace = replace;
684 exports.attachComments = attachComments;
685 exports.VisitorKeys = VisitorKeys;
686 exports.VisitorOption = VisitorOption;
687 exports.Controller = Controller;
688 }));
689 /* vim: set sw=4 ts=4 et tw=80 : */