Tree Transformations
JSCodeshift API

The jscodeshift API

As already mentioned, jscodeshift also provides a wrapper around [recast][]. In order to properly use the jscodeshift API, one has to understand the basic building blocks of recast (and ASTs) as well.

Core Concepts

AST nodes

An AST node is a plain JavaScript object with a specific set of fields, in accordance with the [Mozilla Parser API][]. The primary way to identify nodes is via their type.

For example, string literals are represented via Literal nodes, which have the structure

// "foo"
{
  type: 'Literal',
  value: 'foo',
  raw: '"foo"'
}

It's OK to not know the structure of every AST node type. The [(esprima) AST explorer][ast-explorer] is an online tool to inspect the AST for a given piece of JS code.

Path objects

Recast itself relies heavily on [ast-types][] which defines methods to

  1. traverse the AST,
  2. access node fields and
  3. build new nodes.

ast-types wraps every AST node into a path object. Paths contain meta-information and helper methods to process AST nodes.

For example, the child-parent relationship between two nodes is not explicitly defined. Given a plain AST node, it is not possible to traverse the tree up. Given a path object however, the parent can be traversed to via path.parent.

For more information about the path object API, please have a look at [ast-types][].

Builders

To make creating AST nodes a bit simpler and "safer", ast-types defines a couple of builder methods, which are also exposed on jscodeshift.

For example, the following creates an AST equivalent to foo(bar):

// inside a module transform
var j = jscodeshift;
// foo(bar);
var ast = j.callExpression(
  j.identifier('foo'),
  [j.identifier('bar')]
);

::: danger jscodeshift Lowercase vs Uppercase fields

If you access a jscodeshift field starting with lowercase like `j.callExpression, it will return a build instance.

If you access a jscodeshift field starting with uppercase, it will return a predicate which is used to filter and check nodes. :::

The signature of each builder function is best learned by having a look at the definition files (opens in a new tab).

Collections and Traversal

In order to transform the AST, you have to traverse it and find the nodes that need to be changed. jscodeshift is built around the idea of [collections][] of paths and thus provides a different way of processing an AST than recast or ast-types.

  1. [Collections][] contain [nodepaths][],
  2. [nodepaths][] contain nodes, and
  3. nodes are what the AST is made of.

(opens in a new tab)

A collection has methods to process the nodes inside a collection, often resulting in a new collection

This results in a fluent interface, which can make the transform more readable.

[Collections][] are "typed" which means that the type of a collection is the "lowest" type all AST nodes in the collection have in common. That means you cannot call a method for a FunctionExpression collection on an Identifier collection.

Here is an example of how one would find/traverse all Identifier nodes with jscodeshift:

// jscodeshift
jscodeshift(src)
  .find(jscodeshift.Identifier)
  .forEach(function(path) {
    // do something with path
  });

The jscodeshift(src).find method (opens in a new tab) has two parameters type and filter.

The type parameter is a predicateType object:

{
    "name": "Name of the node",
    "kind": "PredicateType",
    "predicate": function(value, deep) { ... }
}

The filter parameter is optional and is a function or a Node. Not used in the former example. Here is an example of transformation using a filter:

export default (fileInfo, api) => {
    const j = api.jscodeshift;
    const root = j(fileInfo.source);
    const callExpressions = root.find(j.CallExpression, 
        { // filter 
            callee: {
                type: 'MemberExpression',
                object: { type: 'Identifier', name: 'console' },
            },
        }
    );
    callExpressions.remove();
    return root.toSource();
}

::: danger jscodeshift Lowercase vs Uppercase fields

If you access a jscodeshift field starting with lowercase like `j.callExpression, it will return a build instance.

If you access a jscodeshift field starting with uppercase, it will return a predicate which is used to filter and check nodes. :::

The call root.find(j.CallExpression returns a collection of [nodepaths][] containing just the nodes that are CallExpressions. Without the second filter option, The find would not just find the console CallExpressions, it would find every CallExpression in the source. To force greater specificity, we provide a second argument to .find: An object of additional parameters, specifying that we want the callee to be a MemberExpression and the object to be an Identifier with name equal to console.

See the full example in the folder remove-calls-to-console of the repo crguezl/hello-jscodeshift (opens in a new tab)

See the code of the class Collection in file Collection.js (opens in a new tab) and the API docs in Class: Collection (opens in a new tab) docs.

See its extensions (opens in a new tab).

Extensibility

jscodeshift provides an API to extend collections (opens in a new tab). By moving common operators into helper functions (which can be stored separately in other modules), a transform can be made more readable.

There are two types of extensions:

  1. generic extensions and
  2. type-specific extensions.

Generic extensions are applicable to all [collections][]. As such, they typically don't access specific node data, but rather traverse the AST from the nodes in the collection.

Type-specific extensions work only on specific node types and are not callable on differently typed [collections][].

jscodeshift.registerMethods Examples

Adding a method to all Identifiers

jscodeshift.registerMethods({
  logNames: function() {
    return this.forEach(function(path) {
      console.log(path.node.name);
    });
  }
}, jscodeshift.Identifier);

Inside the logNames function this refers to the current Collection.

Here is another example adding a method to all [collections][]

jscodeshift.registerMethods({
  findIdentifiers: function() {
    return this.find(jscodeshift.Identifier);
  }
});

Then we can use them this way:

jscodeshift(ast).findIdentifiers().logNames();
jscodeshift(ast).logNames(); // error, unless `ast` only consists of Identifier nodes

See an example

Passing options to [recast]

You may want to change some of the output settings (like setting ' instead of "). This can be done by passing config options to [recast].

.toSource({quote: 'single'}); // sets strings to use single quotes in transformed code.

You can also pass options to recast's parse method by passing an object to jscodeshift as second argument:

jscodeshift(source, {...})

More on config options here (opens in a new tab)

Unit Testing

Véase la sección Unit Testing

Examples from Write Code to Rewrite Your Code: jscodeshift tutorial

Read the tutorial at Write Code to Rewrite Your Code: jscodeshift (opens in a new tab) Examples: removing console.log, replacing imported method calls, from positional parameters to parameter object

Remove calls to console

Here you have the code of the example remove calls to console (opens in a new tab).

Exercise

Write a transformation remove-console-logs.js that only removes console.logs but not console.warn and others

Replacing imported method calls

Here is the code of the example Replacing imported method calls (opens in a new tab)

From positional parameters to parameter object

Code for the example From positional parameters to parameter object (opens in a new tab)

Example inserting console.log at the beginning of a function

See the code at the folder crguezl/hello-jscodeshift/prefix-functions (opens in a new tab) in the master branch

Trailing Commas

Example FunctionExpression to an ArrowFunctionExpression

Other Examples

JsCodeShift Documentation

See jscodeshift wiki: documentation (opens in a new tab) and crguezl/jscodeshift-api-docs (opens in a new tab) deployment

Recipes

!!!include(includes/jscodeshift-links.md)!!!

References

See the section references about AST transformations