Table of contents:
- Focus on what matters
- How to create your own Eslint rule
- Fixable rule
- How to create tests
- Conclusion
- References
Code review is an essential step in every good software development process. At this step, your team can learn, discuss, and detect problems related to code or business logic. A good code review requires attention to detail from the reviewer, and this is where I started to think more about DX (Developer Experience) and how to improve the code review to focus on what matters.
Focus on what matters
I want to remove as many noises as possible during the development process and code review, allowing the developer to focus on what matters. One of the ways to do it is to create a good set of lint rules for your project. It can force a specific code pattern, organize the code, and remove the responsibility from the developer to keep things clear.
Having a team with a good sense of ownership that works on refactoring and improving the DX is crucial.
For each repetitive mistake during the code review should have a pull-request similar to this one:
How to create your own Eslint rule
Setup the project
You can start by creating a new folder containing your eslint package and another project to test your eslint package.
~ mkdir my-eslint-rule
~ cd my-eslint-rule
~ mkdir package
~ cd package
Inside the package
folder, start a npm project
npm init
The project's name should contain the prefix eslint-plugin
, like eslint-plugin-my-custom-rule
.
Creating the rule
The rule should detect the code we want to transform, show an error or alert about the problem, and be auto-fixable.
The rule I'll create for this tutorial will look for console.log
calls and refactor it to console.info().
const moveConsoleLogToInfo = (context) => {
return {
CallExpression: (node) => {
if (
node.callee.type === "MemberExpression" &&
node.callee.object.name === "console" &&
node.callee.property.name === "log"
) {
context.report({
node,
message: "Use console.info instead of console.log",
});
}
},
};
};
module.exports = { moveConsoleLogToInfo };
To understand this syntax, I recommend exploring AST Explorer. You will have a better view of how the AST of JavaScript works and how to correlate it with the Eslint syntax:
And then, we can import it into our index.js
file.
const { moveConsoleLogToInfo } = require("./move-console-log-to-info");
module.exports = {
rules: {
"move-console-log-to-info": {
create: moveConsoleLogToInfo,
},
},
};
At this point, we're ready to install our new eslint rule on our test project.
Outside the package
folder, initiate another npm project.
npm init
To install our package, we can tell NPM the package directory. In our case, it will be:
npm install --save-dev ./package
And now you can see your package in package.json
:
And then, install lint:
npm install -D eslint
Create .eslintrc.js
:
module.exports = {
env: {
node: true,
},
parserOptions: {
ecmaVersion: 2018,
sourceType: "module",
},
extends: ["eslint:recommended"],
plugins: ["my-custom-rule"],
rules: {
"my-custom-rule/move-console-log-to-info": "error"
},
};
Back to your test file, and type console.log
to see the magic:
But our rule cannot be fixed by itself. Let's make it possible.
Fixable rule
First, back to package/index.js
:
const { moveConsoleLogToInfo } = require("./move-console-log-to-info");
module.exports = {
rules: {
"move-console-log-to-info": {
defaultOptions: [],
meta: {
type: "problem",
messages: {
defaultMessage: "Move console.log to logger.info",
},
fixable: "code",
schema: [],
},
create: moveConsoleLogToInfo,
},
},
};
and move-console-log-to-info.js
:
const moveConsoleLogToInfo = (context) => {
return {
CallExpression: (node) => {
if (
node.callee.type === "MemberExpression" &&
node.callee.object.name === "console" &&
node.callee.property.name === "log"
) {
context.report({
node,
message: "Use console.info instead of console.log",
fix: (fixer) => fixer.replaceText(node.callee.property, "info"),
});
}
},
};
};
module.exports = { moveConsoleLogToInfo };
Now, install the package again and restart the eslint server:
And it will keep the content inside the log()
How to create tests
Having tests for your rule will help ensure that your package does not introduce any regression. We'll use Eslint and Jest for this.
Eslint provides RuleTester and Jest because it runs without additional configuration and works well with valid and invalid RuleTester syntax.
Test folder
I won't discuss the folder architecture you'll use to organize your tests. You can check out how I organized a real-world Eslint rule here.
You must follow the name file pattern name.spec.js
because Jest will look for .spec files.
// move-console-log-to-info.spec.js
const { RuleTester } = require("eslint");
const { rules } = require("../index");
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 } });
ruleTester.run("move-console-log-to-info", rules["move-console-log-to-info"], {
valid: [
{
code: 'console.info("My own Eslint rule is working!")',
},
],
invalid: [
{
code: 'console.log("My own Eslint rule is working!")',
errors: [
{
message: "Use console.info instead of console.log",
type: "CallExpression",
},
],
output: 'console.info("My own Eslint rule is working!")',
},
],
});
Conclusion
Now you understand why it's important to care about DX, how to use Eslint to make developers focus on what matters with a custom Eslint rule, and how to create automated tests to ensure your rule keeps working well.
If you liked what you saw today, please react to this post and do not forget to follow me on my socials:
Top comments (0)