Tree Transformations
Understanding the AST Types API

Understanding the AST Types API

Writing an ast-types builder

Consider the following code we are going to process with recast:

const recast = require("recast");
const code = `
  function add(a, b) {
    return a - b;
  }
`;
const ast = recast.parse(code);
const add = ast.program.body[0]; // The node of the add function declaration

and for that we are going to use recast/ast-types builders:

const B = recast.types.builders;

Since we want to convert the function add declaration in a function expression, we build this AST:

ast.program.body[0] = B.variableDeclaration("const", [
  B.variableDeclarator(add.id, B.functionExpression(
    null, // Anonymize the function expression.
    add.params,
    add.body
  ))
]);

Now the question is: How to be sure about the API of the builders? How to build the call to a builder?

I found the module ast-types (opens in a new tab) and especially the file def/core.ts (opens in a new tab)) module helps a lot to understand the ast API.

For instance, the following is the definition of VariableDeclaration (opens in a new tab):

def("VariableDeclaration")
    .bases("Declaration")
    .build("kind", "declarations")
    .field("kind", or("var", "let", "const"))
    .field("declarations", [def("VariableDeclarator")]);

tell us that a variable declaration has two fields kind and declarations which is an array of VariableDeclarator:

ast.program.body[0] = B.variableDeclaration("const", [ B.variableDeclarator( ... )) ]);

and this is the definition of VariableDeclarator (opens in a new tab):

 def("VariableDeclarator")
    .bases("Node")
    .build("id", "init")
    .field("id", def("Pattern"))
    .field("init", or(def("Expression"), null), defaults["null"]);

that it has the fields id and init:

B.variableDeclarator(add.id, B.functionExpression( ... )

Expression is a sort of abstract class:

  def("Expression").bases("Node");
 
  def("FunctionExpression")
    .bases("Function", "Expression")
    .build("id", "params", "body");
 
  def("ThisExpression").bases("Expression").build();
 
  def("ArrayExpression")
    .bases("Expression")
    .build("elements")
    .field("elements", [or(def("Expression"), null)]);
 
  /* etc. */

That tell us that the FunctionExpression (opens in a new tab) is a Expression and a kind of function and that to build a FunctionExpression node we have to specify the "id", "params" and "body".

Here is the description of Function nodes:

def("Function")
    .bases("Node")
    .field("id", or(def("Identifier"), null), defaults["null"])
    .field("params", [def("Pattern")])
    .field("body", def("BlockStatement"))
    .field("generator", Boolean, defaults["false"])
    .field("async", Boolean, defaults["false"]);

and that is why we build the FunctionExpression this way:

B.functionExpression(null, add.params, add.body )

Where add is the node containing the original function:

const recast = require("recast");
const code = `
  function add(a, b) {
    return a - b;
  }
`;
const ast = recast.parse(code);
const add = ast.program.body[0]; // The node of the add function declaration

Rasengar AST-builder

A good way to build an ast-types tree is to use the Rasengar tool

/images/rasengar-ast-builder.png (opens in a new tab)

On the left panel we write the code:

const add = function(b, a) {
    return a * b;
  };

and the panel below appears the build expression, so that we can use it:

> recast = require("recast") 
> j = recast.types.builders // Now we paste the contents of the panel
> ast = j.variableDeclaration("const", [j.variableDeclarator(
...   j.identifier("add"),
...   j.functionExpression(null, [j.identifier("b"), j.identifier("a")], j.blockStatement([
...     j.returnStatement(j.binaryExpression("*", j.identifier("a"), j.identifier("b")))
...   ]))
... )]);
{
  type: 'VariableDeclaration',
  kind: 'const',
  declarations: [ { type: 'VariableDeclarator',
      id: [Object], init: [Object], loc: null, comments: null
  }],
  loc: null, comments: null
}
> recast.print(ast)
PrintResult {
  code: 'const add = function(b, a) {\n    return a * b;\n};'
}

References