DEV Community

Volodymyr Yepishev
Volodymyr Yepishev

Posted on • Updated on

Create a custom eslint rule with typescript

Creating custom eslint rules can often come handy. I enjoy using typescript, so I decided I'd share a way how to use its superpowers to create custom eslint rules.

Step one

Create a repo and initialise an npm package in it:

mkdir my-awesome-eslint-rule && cd my-awesome-eslint-rule && npm init -y
Enter fullscreen mode Exit fullscreen mode

Make sure your package name starts with eslint-plugin-, 'cos this is the way, i.e.:

{
    "name": "eslint-plugin-my-awesome-rule",
    ...
Enter fullscreen mode Exit fullscreen mode

Install the dependencies (we won't touch @types/estree in this tutorial, but it's quite handy for types when writing eslint rules, so I keep it here):

npm i -D  @types/eslint @types/estree @types/node tsup typescript
Enter fullscreen mode Exit fullscreen mode

Basically we're installing three packages with types definitions, typescript and a the simplest and fastest way to bundle your TypeScript libraries :)

Step two

We'll be seriously developing, so we'll have a src and dist folders, for the source and produced output respectively, so let's configure our package.json accordingly:

{
    "name": "eslint-plugin-my-awesome-rule",
    "main": "./dist/index.js",
    "files": [
    "dist/**"
    ],
    ...
Enter fullscreen mode Exit fullscreen mode

Now let's create the src folder with an index.ts file as the entry point to our future rule:

module.exports = {
    rules: {
        // our rules will go here
    },
};
Enter fullscreen mode Exit fullscreen mode

Step three

Let's create a silly simple rule in our src folder. Note, that a rule is a function that accepts context and returns rule listener. Both these types we can get from the eslint.
For the sake of demonstration let's create a rule that questions every import:

// ./src/my-awesome-rule.ts
import { Rule } from "eslint";

export function myAwesomeRule(context: Rule.RuleContext): Rule.RuleListener {
    return {
        ImportDeclaration(node) {
            context.report({
                node,
                message: 'Are you sure about this?'
            })
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Now let's import it into our entry file and plug it in:

// ./src/index.ts
import { myAwesomeRule } from './my-awesome-rule';

module.exports = {
    rules: {
      'my-silly-rule': {
        create: myAwesomeRule,
      },
    },
};
Enter fullscreen mode Exit fullscreen mode

Step four

Let's transpile it to js to make it usable. Add a build command to the scripts section in the package.json file. We'll be using tsup, which is absolutely awesome for this:

...
"scripts": {
    "build": "tsup src/index.ts --no-splitting --minify"
},
...
Enter fullscreen mode Exit fullscreen mode

Run the command to get a minified tiny version of your rule in dist folder.

Step five

Install and enjoy, you can install it directly from the dist folder, run npm pack to create an archive with your new rule or publish it to npm with npm publish.
After installing, don't forget to plug it into your .eslintrc, by adding it to the plugins section and activating its rule:

{
    "plugins": ["my-awesome-rule"],
    "rules": {
      "my-awesome-rule/my-silly-rule": "warn"
    }
  ...

Enter fullscreen mode Exit fullscreen mode

Have fun :)

Top comments (4)

Collapse
 
alexandrzavalii profile image
Alex Zavalii

I also had to add meta object

module.exports = {
  rules: {
    'my-silly-rule': {
      meta: {
        type: "problem", // `problem`, `suggestion`, or `layout`
        docs: {
          description: "rule to disallow stories to be a js file",
          recommended: false,
          url: null, // URL to the documentation page for this rule
        },
        fixable: null, // Or `code` or `whitespace`
        schema: [], // Add a schema if the rule has options
        messages: {
          avoidName: "Are you sure about this?",
        },
      },
      create: myAwesomeRule,
    },
  },
};

Enter fullscreen mode Exit fullscreen mode
Collapse
 
bwca profile image
Volodymyr Yepishev

Reviewing the article, I now think it'd be more neat to have the module.exports file a bit simpler, so the rules property looks like 'my-silly-rule': myAwesomeRule, so that myAwesomeRule completely encapsulates everything related to the rule, not only the create property, while the exports file is just for mapping of string selectors to rules.

But yea, the main point is, with typescript it is less cryptic what should be there :)

Collapse
 
vuki656 profile image
Domagoj

Very helpful, thanks.

Btw, there is a typo in import { myAwesomeRule } from './my-aswesome-rule';

Collapse
 
bwca profile image
Volodymyr Yepishev

Fixed, thanks! :D