abstract-syntax-tree
A library for working with abstract syntax trees.
Table of Contents
Background
An abstract syntax tree is a way to represent the source code. In case of this library it is represented in the estree format.
For example, the following source code:
const answer = 42
Has the following representation:
{
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "answer"
},
"init": {
"type": "Literal",
"value": 42
}
}
],
"kind": "const"
}
]
}
The goal of this library is to consolidate common abstract syntax tree operations in one place. It uses a variety of libriaries under the hood based on their performance and flexibility, e.g. meriyah for parsing and astring for source code generation.
The library exposes a set of utility methods that can be useful for analysis or transformation of abstract syntax trees. It supports functional and object-oriented programming style.
Install
npm install abstract-syntax-tree
Usage
const { parse, find } = require('abstract-syntax-tree')
const source = 'const answer = 42'
const tree = parse(source)
console.log(find(tree, 'Literal'))
const AbstractSyntaxTree = require('abstract-syntax-tree')
const source = 'const answer = 42'
const tree = new AbstractSyntaxTree(source)
console.log(tree.find('Literal'))
API
Static Methods
parse
The library uses meriyah to create an estree compatible abstract syntax tree. All meriyah parsing options can be passed to the parse method.
const { parse } = require('abstract-syntax-tree')
const source = 'const answer = 42'
const tree = parse(source)
console.log(tree)
const { parse } = require('abstract-syntax-tree')
const source = 'const answer = 42'
const tree = parse(source, {
loc: true,
ranges: true
})
console.log(tree)
generate
The library uses astring to generate the source code. All astring generate options can be passed to the generate method.
const { parse, generate } = require('abstract-syntax-tree')
const source = 'const answer = 42'
const tree = parse(source)
console.log(generate(tree))
walk
Walk method is a thin layer over estraverse.
const { parse, walk } = require('abstract-syntax-tree')
const source = 'const answer = 42'
const tree = parse(source)
walk(tree, (node, parent) => {
console.log(node)
console.log(parent)
})
find
Find supports two traversal methods. You can pass an esquery compatible selector or pass an object that will be compared to every node in the tree. The method returns an array of nodes.
const { parse, find } = require('abstract-syntax-tree')
const source = 'const answer = 42'
const tree = parse(source)
console.log(find(tree, 'VariableDeclaration'))
console.log(find(tree, { type: 'VariableDeclaration' }))
traverse
Traverse method accepts a configuration object with enter and leave callbacks. It allows multiple transformations in one traversal.
const { parse, traverse } = require('abstract-syntax-tree')
const source = 'const answer = 42'
const tree = parse(source)
traverse(tree, {
enter (node) {},
leave (node) {}
})
replace
Replace extends estraverse by handling replacement of give node with multiple nodes. It will also remove given node if null
is returned.
const { parse, replace } = require('abstract-syntax-tree')
const source = 'const answer = 42'
const tree = parse(source)
replace(tree, {
enter (node) {
if (node.type === 'VariableDeclaration') {
node.kind = 'let'
}
return node
}
})
remove
Remove uses estraverse and ensures that no useless nodes are left in the tree. It accepts a string, object or callback as the matching strategy.
const { parse, remove, generate } = require('abstract-syntax-tree')
const source = '"use strict"; const b = 4;'
const tree = parse(source)
remove(tree, 'Literal[value="use strict"]')
console.log(generate(tree))
each
const { parse, each } = require('abstract-syntax-tree')
const source = 'const foo = 1; const bar = 2;'
const tree = parse(source)
each(tree, 'VariableDeclaration', node => {
console.log(node)
})
first
const { parse, first } = require('abstract-syntax-tree')
const source = 'const answer = 42'
const tree = parse(source)
console.log(first(tree, 'VariableDeclaration'))
last
const { parse, last } = require('abstract-syntax-tree')
const source = 'const answer = 42'
const tree = parse(source)
console.log(last(tree, 'VariableDeclaration'))
reduce
const { parse, reduce } = require('abstract-syntax-tree')
const source = 'const a = 1, b = 2'
const tree = parse(source)
const value = reduce(tree, (sum, node) => {
if (node.type === 'Literal') {
sum += node.value
}
return sum
}, 0)
console.log(value)
has
const { parse, has } = require('abstract-syntax-tree')
const source = 'const answer = 42'
const tree = parse(source)
console.log(has(tree, 'VariableDeclaration'))
console.log(has(tree, { type: 'VariableDeclaration' }))
count
const { parse, count } = require('abstract-syntax-tree')
const source = 'const answer = 42'
const tree = parse(source)
console.log(count(tree, 'VariableDeclaration'))
console.log(count(tree, { type: 'VariableDeclaration' }))
append
Append pushes nodes to the body of the abstract syntax tree. It accepts estree nodes as input.
const { parse, append } = require('abstract-syntax-tree')
const source = 'const answer = 42'
const tree = parse(source)
append(tree, {
type: 'ExpressionStatement',
expression: {
type: "CallExpression",
callee: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'console'
},
property: {
type: 'Identifier',
name: 'log'
},
computed: false
},
arguments: [
{
type: 'Identifier',
name: 'answer'
}
]
}
})
Strings will be converted into abstract syntax tree under the hood. Please note that this approach might make the code run a bit slower due to an extra interpretation step.
const { parse, append } = require('abstract-syntax-tree')
const source = 'const answer = 42'
const tree = parse(source)
append(tree, 'console.log(answer)')
prepend
Prepend unshifts nodes to the body of the abstract syntax tree. Accepts estree nodes or strings as input, same as append.
const { parse, prepend } = require('abstract-syntax-tree')
const source = 'const a = 1;'
const tree = parse(source)
prepend(tree, {
type: 'ExpressionStatement',
expression: {
type: 'Literal',
value: 'use strict'
}
})
equal
const { equal } = require('abstract-syntax-tree')
console.log(equal({ type: 'Literal', value: 42 }, { type: 'Literal', value: 42 }))
console.log(equal({ type: 'Literal', value: 41 }, { type: 'Literal', value: 42 }))
template
const { template } = require('abstract-syntax-tree')
const literal = template(42)
const nodes = template('const foo = <%= bar %>;', { bar: { type: 'Literal', value: 1 } })
Instance Methods
Almost all of the static methods (excluding parse, generate, template and equal) have their instance equivalents. There are few extra instance methods:
mark
const AbstractSyntaxTree = require('abstract-syntax-tree')
const tree = new AbstractSyntaxTree('const a = 1')
tree.mark()
console.log(tree.first('Program').cid)
console.log(tree.first('VariableDeclaration').cid)
wrap
const AbstractSyntaxTree = require('abstract-syntax-tree')
const source = 'const a = 1'
const tree = new AbstractSyntaxTree(source)
tree.wrap(body => {
return [
{
type: 'ExpressionStatement',
expression: {
type: 'CallExpression',
callee: {
type: 'FunctionExpression',
params: [],
body: {
type: 'BlockStatement',
body
}
},
arguments: []
}
}
]
})
unwrap
const AbstractSyntaxTree = require('abstract-syntax-tree')
const source = '(function () { console.log(1); }())'
const tree = new AbstractSyntaxTree(source)
tree.unwrap()
console.log(tree.source)
Getters
body
Gives the body of the root node.
source
Gives access to the source code representation of the abstract syntax tree.
const AbstractSyntaxTree = require('abstract-syntax-tree')
const source = 'const foo = "bar";'
const tree = new AbstractSyntaxTree(source)
console.log(tree.source)
map
Gives the source map of the source code.
Setters
body
Sets the body of the root node.
Maintainers
@emilos.
Contributing
All contributions are highly appreciated! Open an issue or a submit PR.
The lib follows the tdd approach and is expected to have a high code coverage. Please follow the Contributor Covenant Code of Conduct.
License
MIT © buxlabs