DEV Community

Fábio Englert Moutinho for Bitovi

Posted on • Edited on • Originally published at bitovi.com

How to Create Custom ESLint Rules (It's Not as Hard as You Think...)

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 (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:

{
  meta: {},
  create: (context) => {
    return {};
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

{
  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,
          },
        });
      }
    },
  }),
}
Enter fullscreen mode Exit fullscreen mode

Inside meta, you can specify type (string) as problemsuggestion, 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.

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 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:

{
  "plugins": ["xgh"],
  "rules": {
    "xgh/no-testing": "error"
  },
  // ...
}
Enter fullscreen mode Exit fullscreen mode

If you would like to take a closer look at the xgh eslint plugin, here's the git repository.

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!

Originally posted in Bitovi Blog

Top comments (2)

Collapse
 
devtghosh profile image
Devjyoti Ghosh

Hey, I want to create a rule about if new files are created make sure they are in TS. How would I go about doing that?

I am facing this issue because my codebase is half in JS and half in TS and we aren't looking to migrating everything just yet. We just want to make sure new files are created in TS.

Collapse
 
fabioemoutinho profile image
Fábio Englert Moutinho

Hi Devjyoti! The first thing I would do is to create a rule that throws a warning for every .js file in the repo.
The next thing is to find out if there is a way to create a rule that:

  1. takes a number as an argument which represents the cap of .js files in the project.
  2. counts the total number of .js files and compares it with the argument. If it is greater than the argument, throw an error.

Another possible solution is to use the first rule I mentioned paired with the eslint.org/docs/user-guide/command... option and allow warnings until it reaches the .js file number cap