|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782 |
- /*
- 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',
- 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',
- Program: 'Program',
- Property: 'Property',
- 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'],
- 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'],
- Program: ['body'],
- Property: ['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;
- }
-
- 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 (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)) {
- 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.version = require('./package.json').version;
- 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 : */
|