Skip to content

cutiful/traverse-the-universe

Repository files navigation

traverse-the-universe

Yet another ESTree AST traversal/modification library, making use of this binding and generators. Supports ES2022 via rules generated from ESTree formal data.

Installation

NPM:

npm install traverse-the-universe

Yarn:

yarn add traverse-the-universe

Browsers (UMD):

<script src="https://unpkg.com/traverse-the-universe"></script>
<script>
  traverseTheUniverse(ast, callback);
</script>

Usage

Generate the abstract syntax tree using acorn or a compatible library. Then, call traverse(ast, callback), where callback is a function that accepts node as the only argument. Call methods on this to manipulate the tree.

If callback is a generator, the part after yield will be called before exiting the node. Only one yield is allowed.

Examples

import traverse from "traverse-the-universe";
// const traverse = require("traverse-the-universe");

traverse(ast, function (node) {
  console.log(node.type); // Identifier
  console.log(this.node.type); // equivalent to the line above, might be a bit slower

  console.log(this.path); // [ "body", 0, "body", "body", 0, "expression", "callee", "object" ]
  console.log(this.key); // "object"
  console.log(this.parentNode?.type); // MemberExpression

  console.log(this.ancestors); // returns an array of parent nodes
  console.log(this.ancestors.map((a) => a.type)); // ["Program", "WhileStatement", "BlockStatement", "ExpressionStatement", "CallExpression", "MemberExpression"]

  this.skip(); // go to the next node on the next iteration, skipping the children of the current node

  const newNode = { ...node, name: "newName" };
  this.replace(newNode); // replace the current node with a new one, visit its children afterwards
  this.replace(newNode, true); // same, but without visiting its children
});

Using generators to run code before leaving each node:

import traverse from "traverse-the-universe";
// const traverse = require("traverse-the-universe");

traverse(ast, function* (node) {
  console.log(node.type);

  yield;

  // will be executed before leaving the node (i. e. after all its children have been visited)
  console.log(node.type);
});

Inserting nodes before or after the current one:

import traverse from "traverse-the-universe";
// const traverse = require("traverse-the-universe");

const logExpression = {
  type: "ExpressionStatement",
  expression: {
    type: "CallExpression",
    callee: {
      type: "MemberExpression",
      object: { type: "Identifier", name: "console" },
      property: { type: "Identifier", name: "log" },
      computed: false,
      optional: false,
    },
    arguments: [
      {
        type: "Literal",
        value: "variable declaration",
        raw: "'variable declaration'",
      },
    ],
    optional: false,
  },
};
traverse(ast, function (node) {
  if (
    this.parentNode?.type === "BlockStatement" &&
    node.type === "VariableDeclaration"
  ) {
    this.insertBefore(logExpression); // insert the node before the current one
    this.insertAfter(logExpression); // insert the node after the current one, visit the children of the current node and the inserted node afterwards
    this.insertAfter(logExpression, true); // same, but skipping both the current and inserted nodes
  }
});

Documentation

Classes

TraversalState

The traversal state which the callback will be bound to. Use this inside the callback to access its methods.

Functions

traverse(ast, callback)

Traverses the supplied AST. Goes into every child Node.

TraversalState

The traversal state which the callback will be bound to. Use this inside the callback to access its methods.

Kind: global class

traversalState.path

Returns the path to the current node as an array of keys. E. g.: [ "body", 0, "body", "body", 0, "expression", "callee", "object" ] is the path to the node at ast.body[0].body.body[0].expression.callee.object.

Kind: instance property of TraversalState

traversalState.node

Returns the current node.

Kind: instance property of TraversalState

traversalState.parentNode

Returns the parent node.

Kind: instance property of TraversalState

traversalState.parentElement

Returns the parent element. The difference between this method and parentNode is that the latter always returns an AST node, whereas this method may return an array of nodes. E. g. if the current node is located inside body of a BlockStatement.

Kind: instance property of TraversalState

traversalState.key

Returns the last element of the path.

Kind: instance property of TraversalState

traversalState.ancestors

Returns an array of ancestor nodes.

Kind: instance property of TraversalState

traversalState.getElementAt(path)

Returns the element at specified path.

Kind: instance method of TraversalState

Param Type
path array

traversalState.skip()

Skips the children of the current node in the next iteration.

Kind: instance method of TraversalState

traversalState.replace(node, skip)

Replaces the current node.

Kind: instance method of TraversalState

Param Type Description
node object The new node.
skip boolean If true, skips the new node.

traversalState.insertBefore(node)

Inserts a node before the current one. Only works if the current node is located inside an array (e. g. inside body of a BlockStatement).

Kind: instance method of TraversalState

Param Type Description
node object The new node.

traversalState.insertAfter(node, skipBoth)

Inserts a node after the current one. Only works if the current node is located inside an array (e. g. inside body of a BlockStatement).

Kind: instance method of TraversalState

Param Type Description
node object The new node.
skipBoth boolean If true, skips both the current and the new nodes.

traverse(ast, callback)

Traverses the supplied AST. Goes into every child Node.

Kind: global function

Param Description
ast ESTree-compatible AST to traverse.
callback Function to be called on each node. Is bound to TraversalState, so you can use e. g. this.replace(node).