Abstract Syntax Trees
Intro
I recently decided to watch a great FrontEnd Masters course by Kent C Dodds on ASTs, (abstract syntax trees), and their application in Babel and EsLint. This blogpost documents my key takeaways from this course.
What is an AST
At its core, an AST is just a JSON object. A parser will take your code and convert it into an AST, (which you could say is similar to taking HTML and converting it into a DOM). There are many different parsers available, which will all create slightly different ASTs with different properties. These ASTs can then be queried and transformed.
Babel and EsLint
Babel is a tool typically used to transpile new javascript syntax into an earlier version of the language which can be read by older browsers. EsLint is a tool, which when run against your code, reports predefined coding standard violations. Both these tools achieve this using ASTs. They each use different parsers which convert your code into an AST and then have their own transformers to report back once the AST has been queried.
Plugins
Both Babel and EsLint have a plugin architecture which allows modules to be written and hooked into their transformers. Some examples are:
- babel-plugin-ramda / babel-plugin-lodash— which will filter out any unused functions in your bundled code.
- eslint-plugin-react — lints your react code.
- eslint-plugin-jsx-a11y — lints for accessibility needs.
- eslint-plugin-security — lints for security violations.
Tools
joinjs — has a nice tool for visualising ASTs in a tree structure.
astExplorer — is a tool for developing code that uses ASTs, (similar to jsFiddle), code is parsed into an AST and then queried and transformed. They have different parsers and transformers you can choose from.
Example
We want to write an EsLint plugin which will report when a `console.log()` statement is left in code. Disclaimer, this is only an example and will not work for a number of `console` statements in your code, (say for when `console` is a key in an object literal).
Code
We only want the linter to report if the console.log statement is called, not our own abstracted log function.
//valid
log()//invalid
console.log()
AST extract
This code when parsed, (using babel-eslint-7.2.3 in this example), will create an AST.
{
"type": "ExpressionStatement",
//other properties...
"expression": {
"type": "CallExpression",
//other properties...
"callee": {
"type": "MemberExpression",
//other properties...
"object": {
"type": "Identifier",
//other properties...
"name": "console",
},
"property": {
"type": "Identifier",
"start": 33,
//other properties...
"name": "log",
},
//other properties...
},
//other properties...
},
//other properties...
}Transform
Using the EsLint transformer, (ESLint v4–4.0.0), we can query the AST and report based on our queries. We export a function that accepts a context argument. This context is the EsLint transform API, which we call the report function on, when we find an EsLint violation. We query the AST using the visitor pattern. The visitor pattern is a way to traverse your AST. It can be compared to how you would use css selectors to query the DOM. Calling `Identifier` will get all Identifier key properties in the AST. We then check if the node name is equal to console and report to the context if true.
export default function(context) {
return {
Identifier(node) {
if(node.name === 'console'){
context.report(node, 'Left in log statement');
}
}
};
};Output
// Left in log statement (at 5:1)
console.log()
// ^looksLike function
Kent also demonstrated a `lookLikes` function, which allows you to specify the shape of the data to filter on.
!looksLike(node, {
node: {
callee: {
type: 'MemberExpression',
object: {
name: 'console',
},
},
},
})