document object model for ast nodes, with parent references, queries and validation


Nodes is a dom-like library for spidermonkey-type ast objects, generated by Esprima or any compatible ast generator. Its purpose is to make it easy to work with ast nodes, by providing a dom-like environment with references to parentNodes, query-selector searches and type validations.

The idea is that you provide an ast json object, and you get back a document-like object. This package does not require esprima, nor does it enforce any specific esprima version.

It supports the full es6 specification.

The file in the root of this project, spec.json, has been manually generated by using estree as a reference. The json specification is used programmatically to set up the fake multiple inheritance for the nodes classes.

Document creation:

var nodes = require('nodes');
var parse = require('esprima').parse;
var ast = parse(javascriptString);
var program =; // generate our document 

It is also possible to create new ast nodes using the classses provided by nodes directly:

var types = require('nodes').types;
var identifier = new types.Identifier({ name:  })
var declaration = new types.VariableDeclaration({ kind: "var" });
var declarator = new types.VariableDeclarator;

Nodes also exports a syntax object, which holds every type as a string.

var syntax = require('nodes').syntax;
syntax.Identifier === "Identifier"; // true 

Whenever you update any node or list, which also happens at creation, parentNodes references are saved to child nodes:

var expression = program.body[0];
expression.parentNode === program.body;
expression.expression.parentNode === expression;
// esprima always creates a program, just interested in creating a node for the declaration 
var declaration ='var x = 0;').body[0]);
declaration.parentNode === program.body;

Each node property is validated against rules defined by the Mozilla Parser API:

declaration.declarations[0].id = 10; // Error! Declarators id must be Identifiers 

Same goes for lists:

program.body.push({type: "Identifier", name: "asd"}));
// Error! program body only accepts Statements 

nodes implements css-like queries for any ast nodes.

Say you want to get all the Identifiers in a program:

var identifiers ='#Identifier');
// [Identifier, Identifier, Identifier, Identifier] 

Or maybe you are interested in the names only?

var identifiers ='#Identifier > name');
// ['a', 'b', 'c', 'd'] 

An ID selector in this instance is equivalent to [type=id].

Direct children:

var id = program.find('#FunctionDeclaration > id');
// Identifier 

note: find is like search, but ends the traversal when it finds the first result.

Any level:

var id ='#FunctionDeclaration id');
// [Identifier, Identifier, ...] 

It also supports generic types like #Function, #Statement, #Expression or #Pattern, in case you want to filter by the base type. For instance, #FunctionExpression, #FunctionDeclaration and #ArrowFunctionExpression will all react to #Function.

parent combinators:

var functionDeclaration = id.find('< #FunctionDeclaration');
// FunctionDeclaration 

parent method, for direct parents. this works like matchesSelector in dom, and also supports expression sequences:

var functionDeclaration = id.parent('#FunctionDeclaration');
// FunctionDeclaration 

parents query, for any parents, same as parent() but keeps traversing:

var functions = id.parents('#Function');
// [FunctionDeclaration, FunctionExpression, ...] 

:declaration pseudo class to find any declaration'#Identifier:declaration > name');'#Identifier:declaration(someVarName)')

:reference pseudo class to find any reference'#Identifier:reference');'#Identifier:reference(someName)')

scope, scopes methods, works like parent / parents, but only cares about scopes.

id.scope(); // Program, even though a FunctionDeclaration id has the FunctionDeclaration as parent. 

:scope pseudo class':scope'); // all the scopes (Program and any Function) 

You can also use attribute selectors or classNames in the queries to check if nodes have / match specific properties. Works pretty much like in the dom.

search / parents / scopes return a BaseList instance, which is an Array-Like object. Just like lists (e.g. program.body) you can run sub queries off of them.

var functions ='#Function');'id');

Multiple queries are also supported:

var functions ='#FunctionExpression, #FunctionDeclaration');

Every list gets decomposed to its nodes:

var bodyElements ='body');
// a BaseList of body nodes. 

Document serialization is automatic, and no special steps are needed:

var generate = require('escodegen').generate;

If you need to you can use toJSON to convert the document back to json format:

var object = program.toJSON();

There is a toString() method to generate json, which is just a shortcut to JSON.stringify.

var jsonString = program.toString()