abstract-syntax-tree
Abstract Syntax Tree
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. cherow 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
const { parse } = require('abstract-syntax-tree')
const source = 'const answer = 42'
const tree = parse(source)
console.log(tree)
The library uses cherow to create an estree compatible abstract syntax tree. All cherow parsing options can be passed to the parse method.
const { parse } = require('abstract-syntax-tree')
const source = 'const answer = 42'
const tree = parse(source, {
loc: true,
ranges: true
})
console.log(tree)
generate
const { parse, generate } = require('abstract-syntax-tree')
const source = 'const answer = 42'
const tree = parse(source)
console.log(generate(tree))
The library uses astring to generate the source code. All astring generate options can be passed to the generate method.
walk
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
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
const { parse, traverse } = require('abstract-syntax-tree')
const source = 'const answer = 42'
const tree = parse(source)
traverse(tree, {
enter (node) {},
leave (node) {}
})
replace
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
const { parse, remove, generate } = require('abstract-syntax-tree')
const source = '"use strict"; const b = 4;'
const tree = parse(source)
remove(tree, { type: '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 lets you to push 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'
}
]
}
})
It also lets you pass raw strings that 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 lets you unshift 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 you the body of the root node.
source
Gives you 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 you the source map of the source code.
Setters
body
Lets you set 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