| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313 | /** * The MIT License (MIT) * Copyright (c) 2017-present Dmitry Soshnikov <dmitry.soshnikov@gmail.com> */'use strict';var NodePath = require('./node-path');/** * Does an actual AST traversal, using visitor pattern, * and calling set of callbacks. * * Based on https://github.com/olov/ast-traverse * * Expects AST in Mozilla Parser API: nodes which are supposed to be * handled should have `type` property. * * @param Object root - a root node to start traversal from. * * @param Object options - an object with set of callbacks: * *   - `pre(node, parent, prop, index)` - a hook called on node enter *   - `post`(node, parent, prop, index) - a hook called on node exit *   - `skipProperty(prop)` - a predicated whether a property should be skipped */function astTraverse(root) {  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};  var pre = options.pre;  var post = options.post;  var skipProperty = options.skipProperty;  function visit(node, parent, prop, idx) {    if (!node || typeof node.type !== 'string') {      return;    }    var res = undefined;    if (pre) {      res = pre(node, parent, prop, idx);    }    if (res !== false) {      // A node can be replaced during traversal, so we have to      // recalculate it from the parent, to avoid traversing "dead" nodes.      if (parent && parent[prop]) {        if (!isNaN(idx)) {          node = parent[prop][idx];        } else {          node = parent[prop];        }      }      for (var _prop in node) {        if (node.hasOwnProperty(_prop)) {          if (skipProperty ? skipProperty(_prop, node) : _prop[0] === '$') {            continue;          }          var child = node[_prop];          // Collection node.          //          // NOTE: a node (or several nodes) can be removed or inserted          // during traversal.          //          // Current traversing index is stored on top of the          // `NodePath.traversingIndexStack`. The stack is used to support          // recursive nature of the traversal.          //          // In this case `NodePath.traversingIndex` (which we use here) is          // updated in the NodePath remove/insert methods.          //          if (Array.isArray(child)) {            var index = 0;            NodePath.traversingIndexStack.push(index);            while (index < child.length) {              visit(child[index], node, _prop, index);              index = NodePath.updateTraversingIndex(+1);            }            NodePath.traversingIndexStack.pop();          }          // Simple node.          else {              visit(child, node, _prop);            }        }      }    }    if (post) {      post(node, parent, prop, idx);    }  }  visit(root, null);}module.exports = {  /**   * Traverses an AST.   *   * @param Object ast - an AST node   *   * @param Object | Array<Object> handlers:   *   *   an object (or an array of objects)   *   *   Each such object contains a handler function per node.   *   In case of an array of handlers, they are applied in order.   *   A handler may return a transformed node (or a different type).   *   *   The per-node function may instead be an object with functions pre and post.   *   pre is called before visiting the node, post after.   *   If a handler is a function, it is treated as the pre function, with an empty post.   *   * @param Object options:   *   *   a config object, specifying traversal options:   *   *   `asNodes`: boolean - whether handlers should receives raw AST nodes   *   (false by default), instead of a `NodePath` wrapper. Note, by default   *   `NodePath` wrapper provides a set of convenient method to manipulate   *   a traversing AST, and also has access to all parents list. A raw   *   nodes traversal should be used in rare cases, when no `NodePath`   *   features are needed.   *   * Special hooks:   *   *   - `shouldRun(ast)` - a predicate determining whether the handler   *                        should be applied.   *   * NOTE: Multiple handlers are used as an optimization of applying all of   * them in one AST traversal pass.   */  traverse: function traverse(ast, handlers) {    var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : { asNodes: false };    if (!Array.isArray(handlers)) {      handlers = [handlers];    }    // Filter out handlers by result of `shouldRun`, if the method is present.    handlers = handlers.filter(function (handler) {      if (typeof handler.shouldRun !== 'function') {        return true;      }      return handler.shouldRun(ast);    });    NodePath.initRegistry();    // Allow handlers to initializer themselves.    handlers.forEach(function (handler) {      if (typeof handler.init === 'function') {        handler.init(ast);      }    });    function getPathFor(node, parent, prop, index) {      var parentPath = NodePath.getForNode(parent);      var nodePath = NodePath.getForNode(node, parentPath, prop, index);      return nodePath;    }    // Handle actual nodes.    astTraverse(ast, {      /**       * Handler on node enter.       */      pre: function pre(node, parent, prop, index) {        var nodePath = void 0;        if (!options.asNodes) {          nodePath = getPathFor(node, parent, prop, index);        }        var _iteratorNormalCompletion = true;        var _didIteratorError = false;        var _iteratorError = undefined;        try {          for (var _iterator = handlers[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {            var handler = _step.value;            // "Catch-all" `*` handler.            if (typeof handler['*'] === 'function') {              if (nodePath) {                // A path/node can be removed by some previous handler.                if (!nodePath.isRemoved()) {                  var handlerResult = handler['*'](nodePath);                  // Explicitly stop traversal.                  if (handlerResult === false) {                    return false;                  }                }              } else {                handler['*'](node, parent, prop, index);              }            }            // Per-node handler.            var handlerFuncPre = void 0;            if (typeof handler[node.type] === 'function') {              handlerFuncPre = handler[node.type];            } else if (typeof handler[node.type] === 'object' && typeof handler[node.type].pre === 'function') {              handlerFuncPre = handler[node.type].pre;            }            if (handlerFuncPre) {              if (nodePath) {                // A path/node can be removed by some previous handler.                if (!nodePath.isRemoved()) {                  var _handlerResult = handlerFuncPre.call(handler, nodePath);                  // Explicitly stop traversal.                  if (_handlerResult === false) {                    return false;                  }                }              } else {                handlerFuncPre.call(handler, node, parent, prop, index);              }            }          } // Loop over handlers        } catch (err) {          _didIteratorError = true;          _iteratorError = err;        } finally {          try {            if (!_iteratorNormalCompletion && _iterator.return) {              _iterator.return();            }          } finally {            if (_didIteratorError) {              throw _iteratorError;            }          }        }      },      // pre func      /**       * Handler on node exit.       */      post: function post(node, parent, prop, index) {        if (!node) {          return;        }        var nodePath = void 0;        if (!options.asNodes) {          nodePath = getPathFor(node, parent, prop, index);        }        var _iteratorNormalCompletion2 = true;        var _didIteratorError2 = false;        var _iteratorError2 = undefined;        try {          for (var _iterator2 = handlers[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {            var handler = _step2.value;            // Per-node handler.            var handlerFuncPost = void 0;            if (typeof handler[node.type] === 'object' && typeof handler[node.type].post === 'function') {              handlerFuncPost = handler[node.type].post;            }            if (handlerFuncPost) {              if (nodePath) {                // A path/node can be removed by some previous handler.                if (!nodePath.isRemoved()) {                  var handlerResult = handlerFuncPost.call(handler, nodePath);                  // Explicitly stop traversal.                  if (handlerResult === false) {                    return false;                  }                }              } else {                handlerFuncPost.call(handler, node, parent, prop, index);              }            }          } // Loop over handlers        } catch (err) {          _didIteratorError2 = true;          _iteratorError2 = err;        } finally {          try {            if (!_iteratorNormalCompletion2 && _iterator2.return) {              _iterator2.return();            }          } finally {            if (_didIteratorError2) {              throw _iteratorError2;            }          }        }      },      // post func      /**       * Skip locations by default.       */      skipProperty: function skipProperty(prop) {        return prop === 'loc';      }    });  }};
 |