Like everything in software development, its best to learn by doing. In this blog series we will go over what is happening, under the hood, when you lint your JavaScript files by writing our very own custom eslint rule!
Setting the stage
Like many I am always searching for answers to life's greatest questions. Also like many, I can narrow these questions down to the three most elusive:
- What happens when we die?
- Do aliens exist?
- How does ESLint work?
Due to recent advances in modern sciences we can finally begin to understand the most important of these questions: How does ESLint work?
TL:DR
I have no idea what I am talking about.
Abstract Syntax Trees
When you run ESLint, the first thing it does is create an Abstract Syntax Tree (AST) of your code. Basically an AST is a description of your codes syntax. A tool can use this description to evaluate your codes structure.
ESLint is not the only tool that uses ASTs. The TypeScript parser creates an AST in its own format to check for incomplete code, type checking, and if you have used the any
type implicitly (hehe).
For this blog we will focus on how ESLint uses an AST to understand your code.
Learn by doing
The best way to understand what an AST is.. is to simply play with them over on AST Explorer. I like to switch the right side panel to json
as I find it easier to follow, but you do you.
You can follow along with this example:
// Simply setting a variable `foo` to a string 'bar'
const foo = 'bar';
ESLint's AST format, ESTree, would represent this line of code as:
{
"type": "Program",
"start": 0,
"end": 16,
"body": [
{
"type": "VariableDeclaration",
"start": 0,
"end": 16,
"declarations": [
{
"type": "VariableDeclarator",
"start": 6,
"end": 15,
"id": {
"type": "Identifier",
"start": 6,
"end": 9,
"name": "foo"
},
"init": {
"type": "Identifier",
"start": 12,
"end": 15,
"name": "bar"
}
}
],
"kind": "const"
}
],
"sourceType": "module"
}
You can gather a lot of information from this. To help we can break it down, then build it back up while we describe whats going on.
The Root
{
"type": "Program",
"start": 0,
"end": 16,
"body": [
// Not important right now
],
"sourceType": "module"
}
The root of this object has a type
of 'Program'. This is standard and encapsulates the the rest of the program code. It shows what byte the program code starts and ends, in the file, as well as describes the type of structure your file has, like whether it is a commonJS file or a ES module file.
The Body
{
"type": "Program",
"start": 0,
"end": 16,
"body": [
{
"type": "VariableDeclaration",
"start": 0,
"end": 16,
"declarations": [
// Not important right now
],
"kind": "const"
}
],
"sourceType": "module"
}
In the body
of the program you have an array of objects. This is where everything you write lives. Since we are only declaring a variable in our program, there is only one object whos type is of "VariableDeclaration"
. If we were to add a function to the file, a new object would pop up in the body
array (specifically it would be a "FunctionDeclaration"
object).
Looking back at the "VariableDeclaration"
object you will notice it has the familiar start
and end
, as well the kind
of variable it is (its a constant).
The Meat
{
"type": "Program",
"start": 0,
"end": 16,
"body": [
{
"type": "VariableDeclaration",
"start": 0,
"end": 16,
"declarations": [
{
"type": "VariableDeclarator",
"start": 6,
"end": 15,
"id": {
"type": "Identifier",
"start": 6,
"end": 9,
"name": "foo"
},
"init": {
"type": "Identifier",
"start": 12,
"end": 15,
"name": "bar"
}
}
],
"kind": "const"
}
],
"sourceType": "module"
}
Now to bring it all back by adding the meat, the "declarations"
property. Inside of this property is where the meat is. The id
object is where the name of our variable is housed. Likewise, the init
property is where the value our variable was initialized too is house. Each of these nested objects also has a type
property and their own start
and end
properties. Can you see a pattern?
That's all an AST is. It simply describes what and where the individual pieces of your code are. This is the map ESLint uses to run its rules off of.
In part 2 of this series we will show how ESLint does this by creating rules of our own.
Top comments (0)