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
- 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). - create property is a function that returns an object. That object has properties that store the callback functions for ESLint.
- The callback functions represent
node types
,selectors
, orevents
and they will be triggered by ESLint at some point.
The basic structure of a rule looks like this:
{
meta: {},
create: (context) => {
return {};
}
}
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,
},
});
}
},
}),
}
Inside meta
, you can specify type
(string) as problem
, suggestion
, 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"
},
// ...
}
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)
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.
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:
.js
files in the project..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