Custom ESLint rules #3/5: creating a new rule
To save our teammates from eating pineapple pizza, we decided to create a custom ESLint plugin with a dedicated rule. In the previous bite, we set up a sample monorepo and now it's time to create our rule!
How to write a custom ESLint rule
At this point, we need to define our custom ESLint ruleset. We will focus on eslint-plugin-pizza
folder.
We have to decide our rule name. According to naming conventions, we should use something like no-pineapple-pizza
. Let's go for it and create all the relevant files.
yummy-pizza $ touch packages/eslint-plugin-pizza/rules/no-pineapple-pizza.js packages/eslint-plugin-pizza/index.js
This is what we have:
yummy-pizza/
├─ packages/
│ ├─ eslint-plugin-pizza/
│ │ ├─ rules/
│ │ │ ├─ no-pineapple-pizza.js
│ │ ├─ package.json
│ │ ├─ index.js
│ ├─ app/
│ │ ├─ src/
│ │ ├─ package.json
├─ package.json
rules/no-pineapple-pizza.js
is our ruleindex.js
is where our rules are exported
It is time to write our rule! To create it, my suggestion is to take advantage of the Abstract Syntax Tree Explorer tool and understand what is the best way to match our pineapple code. Also, we need to provide some metadata to our rule. For the detailed list of metadata to use, refer to the official docs.
In our example, we want to match the eatPineapplePizza
function. If we feed AST Explorer with a sample code, it will tell us how to build our rule. Place the cursor upon the incriminated text and see what is highlighted on the right-hand side. For a way more detailed guide on how to use AST, you can check this blog post that I found extremely helpful.
So after playing a bit with it, I ended up with the following code:
// yummy-pizza/packages/eslint-plugin-pizza/rule/no-pineapple-pizza.js
const rule = {
meta: {
type: "problem", // our rule is detecting an error
fixable: "code", // our rule can be automatically fixed
docs: {
description: "Don't eat pineapple pizza.", // short rule description
category: "Pizza ruleset",
},
},
create: function (context) {
return {
// this code is generated using AST
CallExpression(node) {
if (
node.callee.type === "Identifier" &&
node.callee.name === "eatPineapplePizza"
) {
context.report({
node,
// message to display to pineapple pizza eaters
message:
"A notification was sent to the Italian police. They're coming. Pineapple pizza is illegal, please eat pizza Margherita instead",
fix(fixer) {
// we provide an automatic fix: replace 'usePineapplePizza' with 'usePizzaMargherita'
return fixer.replaceText(node.callee, "eatPizzaMargherita");
},
});
}
},
};
},
};
export default rule;
Finally, edit index.js
file to export the new rule.
// yummy-pizza/packages/eslint-plugin-pizza/index.js
import noPineapplePizzaRule from "./rules/no-pineapple-pizza"
const plugin = {
rules: {
"no-pineapple-pizza": noPineapplePizzaRule,
},
};
export default plugin;
Our rule is ready!
In the next bite, we'll learn how to test it to ensure it works properly.
References
Subscribe to my newsletter
Read articles from Gabriele Buffolino directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Gabriele Buffolino
Gabriele Buffolino
A collaborative Front End Software Developer with 17 years of experience, including 7 years focused on cutting-edge web development. Driven by a passion for continual learning.