How to Create Custom ESLint Rules (It's Not as Hard as You Think...)
Tags: eslint, javascript, typescript
Published: 2022-02-09 19:56:28

Most teams develop patterns or preferred ways of writing code, but it can be tedious to enforce adhering to those patterns, especially for new team members onboarding. To help this, we rely on linters for basic formatting, but did you know you can take preference-enforcement to the next level by writing your own lint rules?

As TypeScript developers, we use ESLint as it is capable of linting both TypeScript AND JavaScript files.

I'm going to teach you how to write custom ESLint rules that work for your team.

> ⚠️ Disclaimer: the rest of this post contains irony and sarcasm.

But it is probably very hard to code an ESLint rule, right? It sounds like something that only an extremely experienced developer could manage. 👀

And you are right, it would be very hard - _SPOILER: coding is hard_ - if ESLint did not provide an awesome API for us.

So it IS possible to write custom ESLint Rules. But we need a reason to write one.

## Why Write a Custom ESLint Rule?

Writing tests is boring. And you are writing tests for things that should work in the first place. Plus, what guarantee do you have that the tests themselves aren't flawed? You'd have to create tests for each test. And then tests for the test tests. You can see where this is leading: nowhere.

I've now established that tests are bad for your project. We are in [eXtreme Go Horse](https://gist.github.com/banaslee/4147370) (XGH) territory. 

XGH is clear on the testing matter:

> You better know what you are doing. And if you know what you are doing, why test? Tests are a waste of time. If code compiles, it’s enough.

The way to prevent anyone from wasting precious time writing tests is to write your own custom ESLint Rule that screams at you if your project has a .spec file. Sounds good!

## What to Know Before Writing a Custom ESLint Rule

1. At the end of the day when you are writing a Custom ESLint Rule, you are providing some metadata (`meta` property) and callback functions for ESLint to run (`create` property). 
2. create property is a function that returns an object. That object has properties that store the callback functions for ESLint.
3. The callback functions represent `node types`, `selectors`, or `events` and they will be triggered by ESLint at some point.

The basic structure of a rule looks like this:
```javascript
{
  meta: {},
  create: (context) => {
    return {};
  }
}
```

`context` (on line 3), is an object provided by ESLint that might have useful data or methods you can use in our callback function.

And that's enough context. 😄

## Behold the `xgh/no-testing` Rule!

Below is the full code for the `no-testing` rule object and an explanation of what's going on.
```javascript
{
  meta: {
    type: "problem",
    messages: {
      match:
        "Found '{{filename}}' .spec file. If you knew what you were doing, you wouldn't need to test it.",
    },
  },
  create: (context) => ({
    Program: (node) => {
      const filename = context.getFilename();
      if (filename.includes(".spec")) {
        context.report({
          node,
          messageId: "match",
          data: {
            filename,
          },
        });
      }
    },
  }),
}
```

Inside `meta`, you can specify `type` (string) as `problem`, `suggestion`, or `layout`. Choose `problem` because you want everyone to know that you should not waste time testing.

At line 9, the `create` function will return an object that has a `Program` property. That is the top level event that ESLint runs for every file. Read about it [here](https://eslint.org/docs/developer-guide/code-path-analysis).

The `Program` property is - you guessed it - a callback function. It takes a `node` parameter that you don’t care about, at least not today.

Here's the interesting part. The “no-testing” logic will kick in: at line 11, you store the current file’s name in a variable. Here the `context` comes in handy as it has a `getFileName` method.

On line 12, check if `filename` variable contains the string `.spec` in it. If it does, you know it is a test file, so you call the `context.report` method to let ESLint know there’s something wrong with our code.

Lines 15 to 18 forward relevant data as parameters to let ESLint know which message to show. `messageId` matches the `match` property inside `meta.messages`.

If you want to know more about ESLint Rules, read [this guide](https://eslint.org/docs/developer-guide/working-with-rules) from ESLint's official documentation. 

## How Do I Use This Wondrous Rule?

You can start using `xgh/no-testing` in your project right now!

Run the command `npm i -D eslint-plugin-xgh` to install the ESLint plugin that wraps the `no-testing` rule.

All you have to do now is enable the rule in your project.

Inside your ESLint config file:
```javascript
{
  "plugins": ["xgh"],
  "rules": {
    "xgh/no-testing": "error"
  },
  // ...
}
```

If you would like to take a closer look at the `xgh` eslint plugin, [here's the git repository](https://github.com/fabioemoutinho/xgh).

## All Kidding Aside...

At Bitovi, we ABSOLUTELY recommend writing unit tests. This tongue-in-cheek post is to show you the possibilities available to better lint your code by writing custom rules. If you need help writing your own rules or learning better coding practices, [please reach out](https://www.bitovi.com/frontend-javascript-consulting/angular-consulting?hsLang=en-us)!

> Originally posted in [Bitovi Blog](https://www.bitovi.com/blog/how-to-create-custom-eslint-rules-its-not-as-hard-as-you-think)