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).
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
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'],
};
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': {},
};
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: [],
},
},
};
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'))
});
}
},
};
},
},
};
Let's take a look at all things here step by step:
-
create
method will accept acontext
parameter that will be later used to create a report about a found issue. - This method will return a new
ImportDeclaration
rule. If you are interested in other rules check out official docs - We are checking if a certain node import contains a query (in our case
bad-import-declaration
) - 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 afixer
as a param and then thisfixer
's method calledreplaceText
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'))
});
}
},
};
},
},
};
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',
},
};
If we implemented everything correctly following line:
Should be replaced and look like following:
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.
Top comments (7)
Love that this exists! I just installed it but it wasn't working and decided to check the docs, and it appears that the syntax may have changed somewhat (notably the name of the plugin
"plugins": ["local-rules"],
and the way that you can set severity for individual rules"rules": {"local-rules/disallow-identifiers": "error"}
npmjs.com/package/eslint-plugin-lo...
I see, thanks for sharing it! I have written this article some time ago (2 years) and since then Eslint managed to create a guide and their own packages to solve this problem. Hooray! :)
This is the fifth article I've looked at for this (plus asking Chat GPT 😉) and this was exactly what I was after, thanks for writing it Jakub!
I am glad you liked it!
Is it possible to write a custom local rule without needing the
eslint-plugin-local-rules
package?Hey, good question. I published this article two years ago so based on your comment I decided to check it.
I think that right now there is an example (finally) on eslint page about building a custom rule -> eslint.org/docs/latest/extend/cust...
Thanks for checking. Yep, managed to solve this. Your article helped as well