DEV Community

Cover image for Writing local rules for ESLint
Jakub Andrzejewski
Jakub Andrzejewski

Posted on

Writing local rules for ESLint

ESLint is a great tool that allowed me to write better code by following the already defined principles for clean code and syntax. However, you may run into situations where the already published ESLint packages and rules will not work (i.e. you will have a code that would need to be replaced due to some business requirements). This is exactly the case that happened to me few weeks ago.

I needed to create an ESLint rule that would basically replace one string (import of the package) with another (custom defined by me). Seems like a relatively simple task. So I did exactly what probably all developers would do at this point. I typed "ESLint replace import with something else" and I was shocked that I could not find any resources that would help me. Maybe it is just me not being able to google properly or the appropriate article or documentation was created already but I had to create the following code all by myself by guessing (JavaScript did not help).

So, below you will see a code sample that is a local ESLint rule that will allow you to replace one import statement with another (defined by you).

Meme

Code

First, let's install a package that would allow us to write local rules:

yarn add --dev eslint-plugin-local-rules # npm install --save-dev eslint-plugin-local-rules
Enter fullscreen mode Exit fullscreen mode

It will allow us to write local rules without a need to publish them as npm packages.

Next, let's add it to the plugins array

// .eslintrc.js

module.exports = {
  plugins: ['eslint-plugin-local-rules'],
};
Enter fullscreen mode Exit fullscreen mode

Now, into the local rule itself. This is the biggest part of the tutorial code so I will try to explain each section step by step so that you will learn what I had to verify myself by guessing :D. At the end you will see a full file with all necessary declarations for your local rule to work.

First, at the top of the file you will see a module.exports and inside of it a string key called replace-bad-import. This is the name of your local rule that will be needed in the eslintrc file later.

// eslint-local-rules.js

module.exports = {
  'replace-bad-import': {},
};
Enter fullscreen mode Exit fullscreen mode

In the meta configuration, let's define information about our local module like description, category, etc. This is more informative way so it is not that important for now.

// eslint-local-rules.js

module.exports = {
  'replace-bad-import': {
    meta: {
      fixable: "code",
      docs: {
        description: 'My awesome ESLint local rule that will replace an import declaration with something else',
        category: 'Possible Errors',
        recommended: false,
      },
      schema: [],
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

Now, into the final part of the local rule which is the create method:

// eslint-local-rules.js

module.exports = {
  'replace-bad-import': {
    create(context) {
      return {
        ImportDeclaration(node) {
          if(node.source.value.includes('bad-import-declaration')) {
            context.report({
              node,
              message: 'Use proper import',
              fix: fixer => fixer.replaceText(node, node.specifiers.map(specifier =>`import ${specifier.local.name} from 'good-import-declaration';`,).join('\n'))
            });
          }
        },
      };
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

Let's take a look at all things here step by step:

  1. create method will accept a context parameter that will be later used to create a report about a found issue.
  2. This method will return a new ImportDeclaration rule. If you are interested in other rules check out official docs
  3. We are checking if a certain node import contains a query (in our case bad-import-declaration)
  4. Inside this if statement we are generating a new report by calling a method from the context object with the following parameters:
  • node: actual node (something like stack trace) place that triggered the rule
  • message: a message that should be displayed after running a rule and finding the issue
  • fix: a fixer method that will be used to fix the import statement. In this case it is a method that uses a fixer as a param and then this fixer's method called replaceText is called with a current node and a new value that should be added instead the node.

Below, you can see the full code of the rule:

// eslint-local-rules.js

module.exports = {
  'replace-bad-import': {
    meta: {
      fixable: "code",
      docs: {
        description: 'My awesome ESLint local rule that will replace an import declaration with something else',
        category: 'Possible Errors',
        recommended: false,
      },
      schema: [],
    },
    create(context) {
      return {
        ImportDeclaration(node) {
          if(node.source.value.includes('bad-import-declaration')) {
            context.report({
              node,
              message: 'Use proper import',
              fix: fixer => fixer.replaceText(node, node.specifiers.map(specifier =>`import ${specifier.local.name} from 'good-import-declaration';`,).join('\n'))
            });
          }
        },
      };
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

The final step here is to add our rule to the .eslintrc.js file.

// .eslintrc.js

module.exports = {
  rules: {
    'local-rules/replace-bad-import': 'warn',
  },
};
Enter fullscreen mode Exit fullscreen mode

If we implemented everything correctly following line:

Bad Import Declaration

Should be replaced and look like following:

Good Import Declaration

Summary

Well done! You have just created a local ESLint rule that will replace one text with another. Keep in mind that it is just a beginning of the power of ESLint but this should give you a solid start in terms of building local rules.

Discussion (0)