Your First ESLint Rule Applied To Create-React-App
Published: Aug 18, 2020
Last updated: Aug 18, 2020
Following along with some "Hello, World!" examples for ESLint has been surprisingly more difficult than I would like to admit. Whether it is the nature of the Abstract Syntax Tree (AST traversal), or setting up the actual rule in a project from start-to-finish, the "Hello, World!" of the ESLint world has not been the most pleasant experience for me.
That being said, light bulbs look to go off on how it works once all of it comes together and the documentation has a moment of clarity for me during this. This project is going to run a small example to demonstrate the clarity I came in hope that following along with it with do the same for you.
You do not need to know React to follow along. I've chosen to use Create React App to demonstrate a working example of getting your first rule up and going. Let's begin!
Setup
We are going to create a CRA app and another folder to host our ESLint rules that we will install.
mkdir eslint-rules-example cd eslint-rules-example # Create a react app called "demo-app" npx create-react-app demo-app # Create a folder for our ESLint rules mkdir rules
Now that we have our two sub-directories set up (one to house the React app, the other for the rules), let's take a quick jump over the AST Explorer
AST Explorer
I've gone into ASTs before, so I won't cover that topic again, but the long and short of the AST Explorer website is that it will parse code and turn it into an AST based the parser that you choose.
It also has the option to run a transformation. This is amazing for testing codemods or, more appropriately today, ESLint rules.
From the toolbar in the AST Explorer, choose JavaScript, then babel-eslint
as the parser, turn on Transform
and select ESLint
from the dropdown as the transformer.
In the top-left box, add the following code:
import AnotherPackage from "ratchet"; import _ from "lodash"; import LastPackage from "last-package"; const _ = require("lodash");
You will see on the top-right that a tree will be generated. What is even cooler - if you click on a particular word or part of the code, it will highlight the current selection.
We are going to make a rule that does not allow you to use the Lodash package. I have opted to go with this rule as it seems like it will be an easy way to illustrate a rule that everyone can understand.
There are two ways to require the package that we will cover: importing and requiring the lodash package.
// imports import _ from "lodash"; // requires const _ = require("lodash");
We won't go in-depth for locking every lodash module - just the main package.
If you click on the import
part of import _ from 'lodash'
, you will see it highlights the ImportDeclaration
block. We can use this knowledge to write our rule.
On the bottom-left box, add the following code:
export default function (context) { return { ImportDeclaration(node) { if (node.source.value === "lodash") { context.report({ node, message: "Do not use Lodash", fix: function (fixer) { return fixer.replaceText(node, ""); }, }); } }, }; }
What we are saying here is to accept a context
param from whatever calls this function, then return an object that follows the visitor pattern. Again, I won't bamboozle you with terms, but essentially this object takes functions that align to the name of the AST node, and once it comes across this node, it will run a function that we can use to apply our rules.
As you saw, the ImportDeclaration
node type from the top-right box is the name of the function from the object we are returning on the bottom-left. In this function, we are telling it to take the node, find the source.value
from it (which you can also read through on the top-right explorer box) and basically "fix" it by replacing it with nothing.
Currently on the bottom right, you should get this back:
// Lint rule not fired. // Fixed output follows: // -------------------------------------------------------------------------------- import AnotherPackage from "ratchet"; import LastPackage from "last-package"; const _ = require("lodash");
Our fix
function is only applying to the import
right now. If we remove the fix
function altogether, the bottom-right will show the following:
// Do not use Lodash (at 2:1) import _ from "lodash"; // ^ // Fixed output follows: // -------------------------------------------------------------------------------- import AnotherPackage from "ratchet"; import _ from "lodash"; import LastPackage from "last-package"; const _ = require("lodash");
Awesome! We can clarify it is working. I am not going to go too deep into replace the CallExpression
, but I've taken a different approach here to replace the matching node's grandparent and that code is as follows:
export default function (context) { return { ImportDeclaration(node) { if (node.source.value === "lodash") { context.report({ node, message: "Do not use Lodash", }); } }, CallExpression(node) { if ( node.callee.name === "require" && node.arguments.some((arg) => arg.value === "lodash") ) { context.report({ node, message: "Do not use Lodash", fix: function (fixer) { // node.parent.parent to replace the entire line return fixer.replaceText(node.parent.parent, ""); }, }); } }, }; }
As an example: here is my screen after adding all of the above:
AST Explorer for ESLint
Now that we have code to replace both the import
and require
statement, let's head back to our code and see it in action!
Adding the rule
Back in our rules
folder, let's run the following:
mkdir eslint-plugin-no-lodash cd eslint-plugin-no-lodash # Initialise a NPM project yarn init -y mkdir lib lib/rules touch lib/rules/no-lodash.js index.js
Right now we are just adding some files to follow conventions.
Inside of lib/rules/no-lodash.js
, we can alter the code we had in AST explorer to be the following:
/** * @fileoverview Rule to disallow Lodash * @author Dennis O'Keeffe */ "use strict"; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ module.exports = { meta: { type: "suggestion", docs: { description: "disallow Lodash", category: "Possible Errors", recommended: true, }, fixable: "code", schema: [], // no options }, create: function (context) { return { ImportDeclaration(node) { if (node.source.value === "lodash") { context.report({ node, message: "Do not use Lodash", fix: function (fixer) { return fixer.replaceText(node, ""); }, }); } }, CallExpression(node) { if ( node.callee.name === "require" && node.arguments.some((arg) => arg.value === "lodash") ) { context.report({ node, message: "Do not use Lodash", fix: function (fixer) { // node.parent.parent to replace the entire line return fixer.replaceText(node.parent.parent, ""); }, }); } }, }; }, };
The function
we had before now goes under the exported create
property. There is also a meta
property that I won't go into, but as you can see it provides meta data if you wish.
Back in index.js
we can now add the following:
const noLodash = require("./lib/rules/no-lodash"); module.exports = { rules: { "no-lodash": noLodash, }, };
Here we are following more conventions, but the exported object from our index.js
file is that we can add our rules under the rules
property.
As a last part, ensure that you're package.json
file has the following:
{ "name": "eslint-plugin-no-lodash", "version": "1.0.0", "main": "index.js", "license": "MIT" }
We are now ready to apply this in our React App!
Setting up the new rule in our React App
Let's change into our demo-app
folder and install our package:
yarn add ../rules/eslint-plugin-no-lodash
This will install our new rule locally.
Update App.js
to simply add import _ from 'lodash'
.
import React from "react"; // Add this line here import _ from "lodash"; import logo from "./logo.svg"; import "./App.css"; function App() { const arr = [1, 2]; _.map(arr, () => true); return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.js</code> and save to reload. </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> </header> </div> ); } export default App;
Note: there is no need to install Lodash first. We are simply testing the rule.
To finalise our setup, we need to make some changes to React app's package.json
so that the eslintConfig
key has the following:
{ "eslintConfig": { "extends": "react-app", "rules": { "no-lodash/no-lodash": "warn" }, "plugins": ["no-lodash"] } }
We add the no-lodash
from the eslint-plugin-no-lodash
name and then we import the rule no-lodash
that we stated in the rules/eslint-plugin-no-lodash/index.js
file.
Once that is done, we can now start our app! From the Create React App docs on extending ESLint ConfigEXTEND_ESLINT
to be true, so we can start our app like so:
# Run this from the demo-app folder EXTEND_ESLINT=true yarn start
Once the app starts up, you will see that our rule has successfully been added!
ESLint warnings
Conclusion
It has been a little bit of a whirlwind to getting this ESLint rule in, but hopefully it can take you from A to Z in a working fashion.
Have a play around afterwards to get a feel, but use tools like AST Explorer to your benefit and, if you are like me, returning to the ESLint docs after getting the working example in will lighten up how it all works and how to apply it all.
Resources and Further Reading
Image credit: Blake Connally
Dennis O'Keeffe
Melbourne, Australia
1,200+ PEOPLE ALREADY JOINED ❤️️
Get fresh posts + news direct to your inbox.
No spam. We only send you relevant content.
Your First ESLint Rule Applied To Create-React-App
Introduction