# 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
Copied!
2
3
4
5
6
7
8
and for that we are going to use recast/ast-types builders:
const B = recast.types.builders;
Copied!
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 )) ]);
Copied!
2
3
4
5
6
7
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 new window) and especially the file def/core.ts (opens new window)) module helps a lot
to understand the ast
API.
For instance, the following is the definition of VariableDeclaration (opens new window):
def("VariableDeclaration") .bases("Declaration") .build("kind", "declarations") .field("kind", or("var", "let", "const")) .field("declarations", [def("VariableDeclarator")]);
Copied!
2
3
4
5
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( ... )) ]);
Copied!
and this is the definition of VariableDeclarator (opens new window):
def("VariableDeclarator") .bases("Node") .build("id", "init") .field("id", def("Pattern")) .field("init", or(def("Expression"), null), defaults["null"]);
Copied!
2
3
4
5
that it has the fields id
and init
:
B.variableDeclarator(add.id, B.functionExpression( ... )
Copied!
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. */
Copied!
2
3
4
5
6
7
8
9
10
11
12
13
14
That tell us that the FunctionExpression (opens new window) 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"]);
Copied!
2
3
4
5
6
7
and that is why we build the FunctionExpression
this way:
B.functionExpression(null, add.params, add.body )
Copied!
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
Copied!
2
3
4
5
6
7
8
# Rasengar AST-builder
A good way to build an ast-types tree is to use the Rasengar tool
On the left panel we write the code:
const add = function(b, a) { return a * b; };
Copied!
2
3
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};' }
Copied!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20