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*/
28 (function clone(exports) {
38 function deepCopy(obj) {
39 var ret = {}, key, val;
41 if (obj.hasOwnProperty(key)) {
43 if (typeof val === 'object' && val !== null) {
44 ret[key] = deepCopy(val);
53 // based on LLVM libc++ upper_bound / lower_bound
56 function upperBound(array, func) {
57 var diff, len, i, current;
65 if (func(array[current])) {
76 AssignmentExpression: 'AssignmentExpression',
77 AssignmentPattern: 'AssignmentPattern',
78 ArrayExpression: 'ArrayExpression',
79 ArrayPattern: 'ArrayPattern',
80 ArrowFunctionExpression: 'ArrowFunctionExpression',
81 AwaitExpression: 'AwaitExpression', // CAUTION: It's deferred to ES7.
82 BlockStatement: 'BlockStatement',
83 BinaryExpression: 'BinaryExpression',
84 BreakStatement: 'BreakStatement',
85 CallExpression: 'CallExpression',
86 CatchClause: 'CatchClause',
87 ClassBody: 'ClassBody',
88 ClassDeclaration: 'ClassDeclaration',
89 ClassExpression: 'ClassExpression',
90 ComprehensionBlock: 'ComprehensionBlock', // CAUTION: It's deferred to ES7.
91 ComprehensionExpression: 'ComprehensionExpression', // CAUTION: It's deferred to ES7.
92 ConditionalExpression: 'ConditionalExpression',
93 ContinueStatement: 'ContinueStatement',
94 DebuggerStatement: 'DebuggerStatement',
95 DirectiveStatement: 'DirectiveStatement',
96 DoWhileStatement: 'DoWhileStatement',
97 EmptyStatement: 'EmptyStatement',
98 ExportAllDeclaration: 'ExportAllDeclaration',
99 ExportDefaultDeclaration: 'ExportDefaultDeclaration',
100 ExportNamedDeclaration: 'ExportNamedDeclaration',
101 ExportSpecifier: 'ExportSpecifier',
102 ExpressionStatement: 'ExpressionStatement',
103 ForStatement: 'ForStatement',
104 ForInStatement: 'ForInStatement',
105 ForOfStatement: 'ForOfStatement',
106 FunctionDeclaration: 'FunctionDeclaration',
107 FunctionExpression: 'FunctionExpression',
108 GeneratorExpression: 'GeneratorExpression', // CAUTION: It's deferred to ES7.
109 Identifier: 'Identifier',
110 IfStatement: 'IfStatement',
111 ImportExpression: 'ImportExpression',
112 ImportDeclaration: 'ImportDeclaration',
113 ImportDefaultSpecifier: 'ImportDefaultSpecifier',
114 ImportNamespaceSpecifier: 'ImportNamespaceSpecifier',
115 ImportSpecifier: 'ImportSpecifier',
117 LabeledStatement: 'LabeledStatement',
118 LogicalExpression: 'LogicalExpression',
119 MemberExpression: 'MemberExpression',
120 MetaProperty: 'MetaProperty',
121 MethodDefinition: 'MethodDefinition',
122 ModuleSpecifier: 'ModuleSpecifier',
123 NewExpression: 'NewExpression',
124 ObjectExpression: 'ObjectExpression',
125 ObjectPattern: 'ObjectPattern',
127 Property: 'Property',
128 RestElement: 'RestElement',
129 ReturnStatement: 'ReturnStatement',
130 SequenceExpression: 'SequenceExpression',
131 SpreadElement: 'SpreadElement',
133 SwitchStatement: 'SwitchStatement',
134 SwitchCase: 'SwitchCase',
135 TaggedTemplateExpression: 'TaggedTemplateExpression',
136 TemplateElement: 'TemplateElement',
137 TemplateLiteral: 'TemplateLiteral',
138 ThisExpression: 'ThisExpression',
139 ThrowStatement: 'ThrowStatement',
140 TryStatement: 'TryStatement',
141 UnaryExpression: 'UnaryExpression',
142 UpdateExpression: 'UpdateExpression',
143 VariableDeclaration: 'VariableDeclaration',
144 VariableDeclarator: 'VariableDeclarator',
145 WhileStatement: 'WhileStatement',
146 WithStatement: 'WithStatement',
147 YieldExpression: 'YieldExpression'
151 AssignmentExpression: ['left', 'right'],
152 AssignmentPattern: ['left', 'right'],
153 ArrayExpression: ['elements'],
154 ArrayPattern: ['elements'],
155 ArrowFunctionExpression: ['params', 'body'],
156 AwaitExpression: ['argument'], // CAUTION: It's deferred to ES7.
157 BlockStatement: ['body'],
158 BinaryExpression: ['left', 'right'],
159 BreakStatement: ['label'],
160 CallExpression: ['callee', 'arguments'],
161 CatchClause: ['param', 'body'],
163 ClassDeclaration: ['id', 'superClass', 'body'],
164 ClassExpression: ['id', 'superClass', 'body'],
165 ComprehensionBlock: ['left', 'right'], // CAUTION: It's deferred to ES7.
166 ComprehensionExpression: ['blocks', 'filter', 'body'], // CAUTION: It's deferred to ES7.
167 ConditionalExpression: ['test', 'consequent', 'alternate'],
168 ContinueStatement: ['label'],
169 DebuggerStatement: [],
170 DirectiveStatement: [],
171 DoWhileStatement: ['body', 'test'],
173 ExportAllDeclaration: ['source'],
174 ExportDefaultDeclaration: ['declaration'],
175 ExportNamedDeclaration: ['declaration', 'specifiers', 'source'],
176 ExportSpecifier: ['exported', 'local'],
177 ExpressionStatement: ['expression'],
178 ForStatement: ['init', 'test', 'update', 'body'],
179 ForInStatement: ['left', 'right', 'body'],
180 ForOfStatement: ['left', 'right', 'body'],
181 FunctionDeclaration: ['id', 'params', 'body'],
182 FunctionExpression: ['id', 'params', 'body'],
183 GeneratorExpression: ['blocks', 'filter', 'body'], // CAUTION: It's deferred to ES7.
185 IfStatement: ['test', 'consequent', 'alternate'],
186 ImportExpression: ['source'],
187 ImportDeclaration: ['specifiers', 'source'],
188 ImportDefaultSpecifier: ['local'],
189 ImportNamespaceSpecifier: ['local'],
190 ImportSpecifier: ['imported', 'local'],
192 LabeledStatement: ['label', 'body'],
193 LogicalExpression: ['left', 'right'],
194 MemberExpression: ['object', 'property'],
195 MetaProperty: ['meta', 'property'],
196 MethodDefinition: ['key', 'value'],
198 NewExpression: ['callee', 'arguments'],
199 ObjectExpression: ['properties'],
200 ObjectPattern: ['properties'],
202 Property: ['key', 'value'],
203 RestElement: [ 'argument' ],
204 ReturnStatement: ['argument'],
205 SequenceExpression: ['expressions'],
206 SpreadElement: ['argument'],
208 SwitchStatement: ['discriminant', 'cases'],
209 SwitchCase: ['test', 'consequent'],
210 TaggedTemplateExpression: ['tag', 'quasi'],
212 TemplateLiteral: ['quasis', 'expressions'],
214 ThrowStatement: ['argument'],
215 TryStatement: ['block', 'handler', 'finalizer'],
216 UnaryExpression: ['argument'],
217 UpdateExpression: ['argument'],
218 VariableDeclaration: ['declarations'],
219 VariableDeclarator: ['id', 'init'],
220 WhileStatement: ['test', 'body'],
221 WithStatement: ['object', 'body'],
222 YieldExpression: ['argument']
236 function Reference(parent, key) {
237 this.parent = parent;
241 Reference.prototype.replace = function replace(node) {
242 this.parent[this.key] = node;
245 Reference.prototype.remove = function remove() {
246 if (Array.isArray(this.parent)) {
247 this.parent.splice(this.key, 1);
255 function Element(node, path, wrap, ref) {
262 function Controller() { }
265 // return property path array from root to current node
266 Controller.prototype.path = function path() {
267 var i, iz, j, jz, result, element;
269 function addToPath(result, path) {
270 if (Array.isArray(path)) {
271 for (j = 0, jz = path.length; j < jz; ++j) {
272 result.push(path[j]);
280 if (!this.__current.path) {
284 // first node is sentinel, second node is root element
286 for (i = 2, iz = this.__leavelist.length; i < iz; ++i) {
287 element = this.__leavelist[i];
288 addToPath(result, element.path);
290 addToPath(result, this.__current.path);
295 // return type of current node
296 Controller.prototype.type = function () {
297 var node = this.current();
298 return node.type || this.__current.wrap;
302 // return array of parent elements
303 Controller.prototype.parents = function parents() {
306 // first node is sentinel
308 for (i = 1, iz = this.__leavelist.length; i < iz; ++i) {
309 result.push(this.__leavelist[i].node);
316 // return current node
317 Controller.prototype.current = function current() {
318 return this.__current.node;
321 Controller.prototype.__execute = function __execute(callback, element) {
322 var previous, result;
326 previous = this.__current;
327 this.__current = element;
330 result = callback.call(this, element.node, this.__leavelist[this.__leavelist.length - 1].node);
332 this.__current = previous;
338 // notify control skip / break
339 Controller.prototype.notify = function notify(flag) {
344 // skip child nodes of current node
345 Controller.prototype.skip = function () {
351 Controller.prototype['break'] = function () {
357 Controller.prototype.remove = function () {
361 Controller.prototype.__initialize = function(root, visitor) {
362 this.visitor = visitor;
364 this.__worklist = [];
365 this.__leavelist = [];
366 this.__current = null;
368 this.__fallback = null;
369 if (visitor.fallback === 'iteration') {
370 this.__fallback = Object.keys;
371 } else if (typeof visitor.fallback === 'function') {
372 this.__fallback = visitor.fallback;
375 this.__keys = VisitorKeys;
377 this.__keys = Object.assign(Object.create(this.__keys), visitor.keys);
381 function isNode(node) {
385 return typeof node === 'object' && typeof node.type === 'string';
388 function isProperty(nodeType, key) {
389 return (nodeType === Syntax.ObjectExpression || nodeType === Syntax.ObjectPattern) && 'properties' === key;
392 Controller.prototype.traverse = function traverse(root, visitor) {
406 this.__initialize(root, visitor);
411 worklist = this.__worklist;
412 leavelist = this.__leavelist;
415 worklist.push(new Element(root, null, null, null));
416 leavelist.push(new Element(null, null, null, null));
418 while (worklist.length) {
419 element = worklist.pop();
421 if (element === sentinel) {
422 element = leavelist.pop();
424 ret = this.__execute(visitor.leave, element);
426 if (this.__state === BREAK || ret === BREAK) {
434 ret = this.__execute(visitor.enter, element);
436 if (this.__state === BREAK || ret === BREAK) {
440 worklist.push(sentinel);
441 leavelist.push(element);
443 if (this.__state === SKIP || ret === SKIP) {
448 nodeType = node.type || element.wrap;
449 candidates = this.__keys[nodeType];
451 if (this.__fallback) {
452 candidates = this.__fallback(node);
454 throw new Error('Unknown node type ' + nodeType + '.');
458 current = candidates.length;
459 while ((current -= 1) >= 0) {
460 key = candidates[current];
461 candidate = node[key];
466 if (Array.isArray(candidate)) {
467 current2 = candidate.length;
468 while ((current2 -= 1) >= 0) {
469 if (!candidate[current2]) {
472 if (isProperty(nodeType, candidates[current])) {
473 element = new Element(candidate[current2], [key, current2], 'Property', null);
474 } else if (isNode(candidate[current2])) {
475 element = new Element(candidate[current2], [key, current2], null, null);
479 worklist.push(element);
481 } else if (isNode(candidate)) {
482 worklist.push(new Element(candidate, key, null, null));
489 Controller.prototype.replace = function replace(root, visitor) {
504 function removeElem(element) {
510 if (element.ref.remove()) {
511 // When the reference is an element of an array.
512 key = element.ref.key;
513 parent = element.ref.parent;
515 // If removed from array, then decrease following items' keys.
518 nextElem = worklist[i];
519 if (nextElem.ref && nextElem.ref.parent === parent) {
520 if (nextElem.ref.key < key) {
529 this.__initialize(root, visitor);
534 worklist = this.__worklist;
535 leavelist = this.__leavelist;
541 element = new Element(root, null, null, new Reference(outer, 'root'));
542 worklist.push(element);
543 leavelist.push(element);
545 while (worklist.length) {
546 element = worklist.pop();
548 if (element === sentinel) {
549 element = leavelist.pop();
551 target = this.__execute(visitor.leave, element);
553 // node may be replaced with null,
554 // so distinguish between undefined and null in this place
555 if (target !== undefined && target !== BREAK && target !== SKIP && target !== REMOVE) {
557 element.ref.replace(target);
560 if (this.__state === REMOVE || target === REMOVE) {
564 if (this.__state === BREAK || target === BREAK) {
570 target = this.__execute(visitor.enter, element);
572 // node may be replaced with null,
573 // so distinguish between undefined and null in this place
574 if (target !== undefined && target !== BREAK && target !== SKIP && target !== REMOVE) {
576 element.ref.replace(target);
577 element.node = target;
580 if (this.__state === REMOVE || target === REMOVE) {
585 if (this.__state === BREAK || target === BREAK) {
595 worklist.push(sentinel);
596 leavelist.push(element);
598 if (this.__state === SKIP || target === SKIP) {
602 nodeType = node.type || element.wrap;
603 candidates = this.__keys[nodeType];
605 if (this.__fallback) {
606 candidates = this.__fallback(node);
608 throw new Error('Unknown node type ' + nodeType + '.');
612 current = candidates.length;
613 while ((current -= 1) >= 0) {
614 key = candidates[current];
615 candidate = node[key];
620 if (Array.isArray(candidate)) {
621 current2 = candidate.length;
622 while ((current2 -= 1) >= 0) {
623 if (!candidate[current2]) {
626 if (isProperty(nodeType, candidates[current])) {
627 element = new Element(candidate[current2], [key, current2], 'Property', new Reference(candidate, current2));
628 } else if (isNode(candidate[current2])) {
629 element = new Element(candidate[current2], [key, current2], null, new Reference(candidate, current2));
633 worklist.push(element);
635 } else if (isNode(candidate)) {
636 worklist.push(new Element(candidate, key, null, new Reference(node, key)));
644 function traverse(root, visitor) {
645 var controller = new Controller();
646 return controller.traverse(root, visitor);
649 function replace(root, visitor) {
650 var controller = new Controller();
651 return controller.replace(root, visitor);
654 function extendCommentRange(comment, tokens) {
657 target = upperBound(tokens, function search(token) {
658 return token.range[0] > comment.range[0];
661 comment.extendedRange = [comment.range[0], comment.range[1]];
663 if (target !== tokens.length) {
664 comment.extendedRange[1] = tokens[target].range[0];
669 comment.extendedRange[0] = tokens[target].range[1];
675 function attachComments(tree, providedComments, tokens) {
676 // At first, we should calculate extended comment ranges.
677 var comments = [], comment, len, i, cursor;
680 throw new Error('attachComments needs range information');
683 // tokens array is empty, we attach comments to tree as 'leadingComments'
684 if (!tokens.length) {
685 if (providedComments.length) {
686 for (i = 0, len = providedComments.length; i < len; i += 1) {
687 comment = deepCopy(providedComments[i]);
688 comment.extendedRange = [0, tree.range[0]];
689 comments.push(comment);
691 tree.leadingComments = comments;
696 for (i = 0, len = providedComments.length; i < len; i += 1) {
697 comments.push(extendCommentRange(deepCopy(providedComments[i]), tokens));
700 // This is based on John Freeman's implementation.
703 enter: function (node) {
706 while (cursor < comments.length) {
707 comment = comments[cursor];
708 if (comment.extendedRange[1] > node.range[0]) {
712 if (comment.extendedRange[1] === node.range[0]) {
713 if (!node.leadingComments) {
714 node.leadingComments = [];
716 node.leadingComments.push(comment);
717 comments.splice(cursor, 1);
723 // already out of owned node
724 if (cursor === comments.length) {
725 return VisitorOption.Break;
728 if (comments[cursor].extendedRange[0] > node.range[1]) {
729 return VisitorOption.Skip;
736 leave: function (node) {
739 while (cursor < comments.length) {
740 comment = comments[cursor];
741 if (node.range[1] < comment.extendedRange[0]) {
745 if (node.range[1] === comment.extendedRange[0]) {
746 if (!node.trailingComments) {
747 node.trailingComments = [];
749 node.trailingComments.push(comment);
750 comments.splice(cursor, 1);
756 // already out of owned node
757 if (cursor === comments.length) {
758 return VisitorOption.Break;
761 if (comments[cursor].extendedRange[0] > node.range[1]) {
762 return VisitorOption.Skip;
770 exports.version = require('./package.json').version;
771 exports.Syntax = Syntax;
772 exports.traverse = traverse;
773 exports.replace = replace;
774 exports.attachComments = attachComments;
775 exports.VisitorKeys = VisitorKeys;
776 exports.VisitorOption = VisitorOption;
777 exports.Controller = Controller;
778 exports.cloneEnvironment = function () { return clone({}); };
782 /* vim: set sw=4 ts=4 et tw=80 : */