document object model for ast nodes, with parent references, queries and validation
npm install nodesspec.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.
js
var nodes = require('nodes');
var parse = require('esprima').parse;
var ast = parse(javascriptString);
var program = nodes.build(ast); // generate our document
`
creating new nodes
It is also possible to create new ast nodes using the classses provided by nodes directly:
`js
var types = require('nodes').types;
var identifier = new types.Identifier({ name: })
var declaration = new types.VariableDeclaration({ kind: "var" });
var declarator = new types.VariableDeclarator;
declaration.declarations.push(declarator)
`
syntax
Nodes also exports a syntax object, which holds every type as a string.
`js
var syntax = require('nodes').syntax;
syntax.Identifier === "Identifier"; // true
`
parentNodes
Whenever you update any node or list, which also happens at creation, parentNodes references are saved to child nodes:
`js
var expression = program.body[0];
expression.parentNode === program.body;
expression.expression.parentNode === expression;
`
`js
// esprima always creates a program, just interested in creating a node for the declaration
var declaration = nodes.build(parse('var x = 0;').body[0]);
parogram.body.push(declaration);
declaration.parentNode === program.body;
`
validation
Each node property is validated against rules defined by the Mozilla Parser API:
`js
declaration.declarations[0].id = 10; // Error! Declarators id must be Identifiers
`
Same goes for lists:
`js
program.body.push(nodes.build({type: "Identifier", name: "asd"}));
// Error! program body only accepts Statements
`
queries
nodes implements css-like queries for any ast nodes.
Say you want to get all the Identifiers in a program:
`js
var identifiers = program.search('#Identifier');
console.log(identifiers);
// [Identifier, Identifier, Identifier, Identifier]
`
Or maybe you are interested in the names only?
`js
var identifiers = program.search('#Identifier > name');
console.log(identifiers);
// ['a', 'b', 'c', 'd']
`
An ID selector in this instance is equivalent to [type=id].
Direct children:
`js
var id = program.find('#FunctionDeclaration > id');
console.log(id);
// Identifier
`
note: find is like search, but ends the traversal when it finds the first result.
Any level:
`js
var id = program.search('#FunctionDeclaration id');
console.log(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:
`js
var functionDeclaration = id.find('< #FunctionDeclaration');
console.log(functionDeclaration);
// FunctionDeclaration
`
parent method, for direct parents. this works like matchesSelector in dom, and also supports expression sequences:
`js
var functionDeclaration = id.parent('#FunctionDeclaration');
console.log(functionDeclaration);
// FunctionDeclaration
`
parents query, for any parents, same as parent() but keeps traversing:
`js
var functions = id.parents('#Function');
console.log(functions);
// [FunctionDeclaration, FunctionExpression, ...]
`
:declaration pseudo class to find any declaration
`js
program.search('#Identifier:declaration > name');
program.search('#Identifier:declaration(someVarName)')
`
:reference pseudo class to find any reference
`js
program.search('#Identifier:reference');
program.search('#Identifier:reference(someName)')
`
scope, scopes methods, works like parent / parents, but only cares about scopes.
`js
id.scope(); // Program, even though a FunctionDeclaration id has the FunctionDeclaration as parent.
`
:scope pseudo class
`js
program.search(':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.
`js
var functions = program.search('#Function');
functions.search('id');
`
Multiple queries are also supported:
`js
var functions = program.search('#FunctionExpression, #FunctionDeclaration');
`
Every list gets decomposed to its nodes:
`js
var bodyElements = program.search('body');
// a BaseList of body nodes.
`
serialization
Document serialization is automatic, and no special steps are needed:
`js
var generate = require('escodegen').generate;
generate(program);
`
If you need to you can use toJSON to convert the document back to json format:
`js
var object = program.toJSON();
`
There is a toString() method to generate json, which is just a shortcut to JSON.stringify.
`js
var jsonString = program.toString()
``