How To Write Your First ESLint Plugin
Tags: eslint, plugin
Published: 2019-07-14 09:25:05


This article serves as a simple introductory guide to ESLint plugins, how they work, how to set up, write and use one.

## What are ESLint Plugins

It's an extension for ESLint that will enforce rules, that are not implemented into the ESLint core. For example, one of the most popular plugins, `eslint-plugin-babel`, supports experimental features and linter that are not in the ESLint core.
Plugins usually stored as separate NPM modules, which exports `rule` object, where `key` is a name, and `value` is another object with the methods that enforce the rule:

```javascript
module.exports = {
    rules: {
        "rule-name": {
            create: function (context) {
                // rule implementation ...
            }
        }
    }
};
```
## Why should you care writing your own?

There are a lot of plugins released, and chances for finding one you need are high. But there might be cases when you need to have rule very specific to your codebase.
Recently our development team decided to enforce the role for async function naming. Meaning, If a function returns a promise or declared as an `async`, it must have a suffix to its name: `function someFunctionAsync() {}.`
Let's take this example for the tutorial and write a plugin that will warn us about wrong function naming.

## Creating a plugin

For this tutorial, we will create a local plugin package and use it in the simple node package. The project structure will look like:
```
plugin-tutorial 
│
└───my-eslint-rules
│   │   package.json
│   │   index.js
│   
└───node-app
    │   package.json
    │   index.js
```
Start by create the main folder `mkdir plugin-tutorial && cd plugin-tutorial`

### Setup plugin package

Each valid plugin should meet the following criteria:
* Be separate NPM package
* Follow name format of `eslint-plugin-<plugin-name>`
* Export `rule` object

1. Create plugin package:
`mkdir my-eslint-rules && cd my-eslint-rules && npm init --yes`
2. Name the package in `package.json`:
`"name": "eslint-plugin-my-eslint-rules"`
3. Create `index.js` that exports the `rules` object with the custom rule, let's say `async-func-name`:
```javascript
module.exports = {
  rules: {
    "async-func-name": {
      create: function (context) {
        return { /* ...rule methods */ }
      }
    }
  }
};
```

### Writing the rule
For constructing and testing the rule, we will use a tool [AST explorer](https://astexplorer.net). The AST stands for an abstract syntax tree or just syntax tree, and it is a representation of the source code in a programming language.

Setup AST Explorer by selecting parser to `eslint-babel` and transformer to `ESLint v4`.

By now you should see four windows in the explorer:
* top left window will be used to write a source code
* top right window is the explorer of the source code. When you hover on expressions, you should see the highlighted parts in your code
* bottom left is the rule
* bottom right is the output after the rule, that runs against the code

![](https://i.imgur.com/Vc1M86u.png)

The rule is a function that takes a `context` object, that has additional functionality and information that is relevant to the context of the rule.
The main method is `context.report()` which publishes warning or error. It takes one argument, object, and can have the following properties: message, message, loc, data, fix. We will use a simple example of the report object:
```javascript
context.report({
    node: node,
    message: "Async function name must end in 'Async'"
});
```

The rule must return an object with methods that ESLint calls to “visit” nodes while traversing the syntax tree of source code. In our example, we have one method, `FunctionDeclaration`, which takes one argument `node` object. The object holds function information, such as type, name, body, locations of each value.

To check if function name has an `Async` suffix we need access the name, which is in the `id` object of `FunctionDelacarion` node: `node.id.name`.

So the main logic of the rule should be to check if the function has `async` property and if the name does not include `Async` suffix, call `context.report().`

After applying the rule, the output in the AST explorer should warn you with the message:
![](https://i.imgur.com/6i9sCDR.png)


After composing the rule in the explorer and making sure it captures rule conditions, move the logic back to the `index.js` of the plugin package, and that completes plugin:
```javascript
module.exports = {
  rules: {
    "async-func-name": {
      create: function (context) {
        return {
          FunctionDeclaration(node) {
            if (node.async && !/Async$/.test(node.id.name)) {
              context.report({
                node,
                message: "Async function name must end in 'Async'"
              });
            }
          }
        }
      }
    }
  }
};
```
### Applying plugin to node project

First let's setup the node project:
* from `plugin-tutorial` run `mkdir node-app && cd node-app && npm init --yes && touch index.js` and add the same function used in the AST explorer to the `index.js`:
```javascript
async function myFunction() {
    return "";
}
```
* install ESLint `npm i eslint --save-dev`
* install the plugin you created: `npm i ../my-eslint-rules --save-dev`
* tell app to use ESLint and plugin by creating configuration file `.eslintrc`:
```json
{
  "parserOptions": {
    "ecmaVersion": 2018
  },
  "rules": {
    "my-eslint-rules/async-func-name": "warn"
  },
  "plugins": ["my-eslint-rules"]
}
```
* run ESLint command from the terminal and node app folder with command: `./node_modules/.bin/eslint index.js`

That's pretty much it. You should see an ESLint warning after running it in the terminal if you set the wrong name for an async function.

The important part of using plugins in the project is the `.eslintrc` configuration file. The rules from plugin should follow naming `"<plugin-name>/<rule-name>": [warn/error]`. And the plugin name should be added to the `plugins` field in an array.

## Different method for writing plugins
The more effortless way write plugins is by using Yeoman generator. You need to install `yo` and `generator-eslint` packages and run `yo` command from the terminal. It will generate the plugin template with a more structured project, includes test setup.

## Where you might struggle
The plugin example in this tutorial was done using common js, meaning there was no import modules or new js syntax.
To use es6 language features, such as imports, in custom plugins you will need to install additional`@babel-core, @babel/node, @babel/preset-env, babel-eslint, babel-register` and add the following configuration to the:
* `.eslintrc`
```json
{
  "parser": "babel-eslint",
  "parserOptions": {
    "ecmaVersion": 6,
    "sourceType": "module",
    "ecmaFeatures": {
      "modules": true
    }
  }
}
```
* `.babelrc`
```json
{
  "presets": ["@babel/preset-env"]
}
```

## Summary
We are using ESLint daily, but most do not know how it works in the background. Hope that now you have a better understanding of how ESLint works, how to write your custom plugins and use abstract syntax tree.