Creating a simple ESLint plugin to glance at AST
Published: 2021-06-30 09:12:41

In JavaScript world, ESLint is the most popular linter.
In this article, I’ll explain how ESLint analyzes our code by creating an ESLint plugin.

## How ESLint works?
You might already know how it works.
But first, let me show you how ESLint works.

Let’s say we have a project like this. And ESLint has been installed via npm.
```
├──.eslintrc.json // ESLint configuration
├── node_modules
├── package-lock.json
├── package.json
└── src
    └── index.js
```

You can use ESLint to `src/index.js` like this
```bash
npx eslint src
```

As the result, an error was shown.
```bash
/Users/xxx/foo/src/index.js
  1:7  error  'foo' is assigned a value but never used  no-unused-vars
```

This is the code inside the `src/index.js`.
```javascript
const foo = () => "baz"
```

`foo` is defined but it’s not called from anywhere. That’s why the error happened.

This is the basic usage of ESLint.
From the next section, i'll create a plugin to see how ESLint detects such errors. 

## What’s a plugin?

The purpose of using plugins is to add additional rules.
ESLint has lots of rules. `no-unused-vars` that we saw above is one of the rules.
But if you want to apply an additional rule that is not available in the official rule, you can add it by creating a plugin. (There are other several ways)

But how ESLint analyze source code to check if the source code aligns with rules or not?

## AST(Abstract Syntax Tree)

ESLint uses AST (Abstract Syntax Tree) to analyze JavaScript code.What’s AST?

AST represents source code structure.For example, AST of `const hoge = 0;` is like this. (You can see AST easily with [AST Explorer](https://astexplorer.net/))

```
{
  "type": "File",
  "start": 0,
  "end": 16,
  "loc": {
    "start": {
      "line": 1,
      "column": 0
    },
    "end": {
      "line": 2,
      "column": 0
    }
  },
  "errors": [],
  "program": {
    "type": "Program",
    "start": 0,
    "end": 16,
    "loc": {
      "start": {
        "line": 1,
        "column": 0
      },
      "end": {
        "line": 2,
        "column": 0
      }
    },
    "sourceType": "module",
    "interpreter": null,
    "body": [
      {
        "type": "VariableDeclaration",
        "start": 0,
        "end": 15,
        "loc": {
          "start": {
            "line": 1,
            "column": 0
          },
          "end": {
            "line": 1,
            "column": 15
          }
        },
        "declarations": [
          {
            "type": "VariableDeclarator",
            "start": 6,
            "end": 14,
            "loc": {
              "start": {
                "line": 1,
                "column": 6
              },
              "end": {
                "line": 1,
                "column": 14
              }
            },
            "id": {
              "type": "Identifier",
              "start": 6,
              "end": 10,
              "loc": {
                "start": {
                  "line": 1,
                  "column": 6
                },
                "end": {
                  "line": 1,
                  "column": 10
                },
                "identifierName": "hoge"
              },
              "name": "hoge"
            },
            "init": {
              "type": "NumericLiteral",
              "start": 13,
              "end": 14,
              "loc": {
                "start": {
                  "line": 1,
                  "column": 13
                },
                "end": {
                  "line": 1,
                  "column": 14
                }
              },
              "extra": {
                "rawValue": 0,
                "raw": "0"
              },
              "value": 0
            }
          }
        ],
        "kind": "const"
      }
    ],
    "directives": []
  },
  "comments": []
}
```

By using AST, we can get detailed information about the source code.It’s convenient to analyze source code rather than analyze plain text.


## Creating a plugin

I created a plugin.
https://github.com/osuke/eslint-plugin-later-becomes-never

And the structure is simple like this.
```
├── lib
│   └── index.js
├── package.json
```

This plugin finds the string **TODO** in comment blocks and complains about it.

Let’s say you have the below code in your project

```javascript
// Todo: will change variable name later
let hoge = 0;
```

ESLint shows an error like this

```bash
/Users/xxx/eslint-test/src/index.js
   1:1  error  Later becomes never  later-becomes-never/no-todo
```
You can see the rule `no-todo` which comes from `later-becomes-never` plugin.

To define rules for the plugin, using AST is necessary.
The definition of the rule is in `lib/index.js`.

```javascript
module.exports = {
  rules: {
    "no-todo": {
      create: function (context) {
        return {
          Program: (node) => {
            node.comments.forEach((comment) => {
              if (comment.value.toLowerCase().indexOf("todo") !== -1) {
                context.report({
                  loc: comment.loc,
                  message: "Later becomes never",
                });
              }
            });
          },
        };
      },
    },
  },
};
```

In the file, a rule named “no-todo“ is defined.We can manipulate AST by using node.Comments are extracted from the source code by using AST. (Line7)

After that, I checked if each comment has a string todo or not. (Line8)
If it has, an error message is shown (Line9 - 12)

## Using the plugin
Let’s use ESLint with the plugin to the below JavaScript.Line 1 is supposed to have an error. Line 3 is not.

```javascript
// Todo: will change variable name later
let hoge = 0;
// doit
```

This is the result. It works :)
```bash
/Users/xxx/eslint-test/index.js
  1:1  error  Later becomes never  later-becomes-never/no-todo
✖ 1 problem (1 error, 0 warnings)
```