How to create a custom ESLint configuration and shareable plugin? PART-1
My first article on Medium. Suggestions, appreciations, criticism are welcome, ‘coz that’s how we learn. 😇
Developers do write a lot of codes, and managing large codes is easier when the codes are formatted in a good way. A well-formatted code is easy to read, maintain and debug. We use plugins that make our formatting jobs easier. One such plugin is ESLint which helps Javascript developers to format the code easily.
ESLint comes with a lot of built-in configurations which can be used hassle-free. There are times we may need to have our own custom rules. In this article, I’ll help you understand how we can write custom ESLint rules/configurations.
Let’s consider a rule where we restrict the use of color literals within Javascript files(or React components) and let’s name the rule as:
no-color-literals
We added this rule to avoid writing color properties at the component level stylings in our React application. So, we’ll consider adding the rule for files having extensions .js and .jsx
This is PART-1, where we will create our custom ESLint configuration. Stay tuned for PART-2, where we’ll see how to make this configuration a shareable ESLint plugin and install across JS projects.
See to PART-2 here.
STEP 1:
Using Abstract Syntax Tree
Before we write our custom ESLint rules, we must understand how our code is structured. For this, we’ll take the help of ASTExplorer. AST Explorer helps us to analyze the structure of our code. We’ll paste a small code snippet containing our style object which will cover both React’s JSX syntaxes as well as VanillaJS files.
const pageStyles = {
textColor: {
color:'#123',
},
};const MyComp = ({})=>{
return (
<div
style={{background:'black'}}
>
</div>
);
};
The above code has two parts, one containing a style object pageStyles object which has another object, textColor containing key-value pair.
The next part is a simple React component, having an inline style object, again containing key-value pair.
The key is a Javascript-style property, and the value is a hex-color-code/color value.
Let’s paste the above code snippet in the ASTExplorer and check how our code is structured.
Notice here, at the right, texts in the blue color, Property, Identifier, Literal are all nodes.
Hovering over Property node(at right) highlights color: ‘#123'. Here, color is an Identifier, and ‘#123’ is a Literal.
This is the same for the inline style object as well. Check the below image:
We’ll be needing these details later. Now let’s set up our environment to write and test the configuration.
STEP 2:
Setting up the environment to write and test the configuration
- Create a folder
eslint-custom-config
and open with your favorite code editor. - Inside the
eslint-custom-config
folder, run the command
npm init -y
to create apackage.json
file - Install ESLint locally using the command
npm i eslint
Additionally, you can install ESLlint globally usingnpm i -g eslint
- In the same folder, create a file with the name
.eslintrc.js
and paste the following configurations:
module.exports = {
'env': {
'browser': true,
'commonjs': true,
'es2021': true
},
'extends': 'eslint:recommended',
'parserOptions': {
'ecmaVersion': 12
},
'rules': {
'indent': ['error', 4],
'linebreak-style': ['error','unix'],
'quotes': ['error','single'],
'semi': ['error','always']
}
};
5. To create an ESLint manual rule, create a folder rules
and inside the rules
folder, create a file named no-color-literals.js
6. Let’s create another folder src
which will be holding the files we need to test.
7. Create a file test.js
inside the src
folder, and paste the below contents:
const pageStyles = {
textColor: {
color: '#333'
}
};
console.log(pageStyles);
So far our folder structure should look like this:
8. To run the lint check, let’s set up the script. Navigate to package.json
and add "lint":"node_modules/.bin/eslint src --ruledir rules"
under the scripts. The package.json
should look something like this:
This will check for linting errors in the files inside thesrc
folder with no-color-literals
rule.
9. In the no-color-literals.js
file, paste the following code:
module.exports = {
create(context){
return {
// our logic goes here
Property(node){
/* node refers to the Property node from Abstract Syntax Tree that we saw on ASTExplorer.
node.key refers to Identifier and node.value refers to Literals
*/ if(!node){ // if node doesn't exist, no need to check
return;
}
if(
node.key.name == 'color' && node.value.value.toString().includes('#')
){/* if key is 'color' and value specified includes '#', report the error */
context.report({
node: node.value,
data: {
name: node.value.value
},
message: "Unexpected: '{{name}}'. Color literals are not allowed", });
}
}
}
}
}
The context.report
method takes an object with few properties and reports the error whenever the lint rule fails.
node
is the exact node(location) where the lint error has occurred.
data
is used to pass some data. Here, we are having name property, which holds the value of the color
property
message
is used to pass some information so that the developer knows exactly what is expected to be done.
We have successfully created our configuration to check if the color property is taking any hex color code as color literal. We’ll modify our logic later to cover more scenarios, but first, let’s implement this rule in our .eslintrc.js
file.
12. Now that we have created our custom configuration, let’s plug it in with the ESLint configuration file. In the .eslintrc.js
file, under the rules
add our custom rules as "no-color-literals": "error"
as below:
11. Time to test:
Run the command npm run lint
in the root directory, you should see the error in the terminal.
As of now, we have restricted the use of hex-color codes for the color property. The logic we have written is very crude, and it needs to be refined.
STEP 3:
Modifying our logic to restrict the usage of all color literals (hex, rgb, hsl, white, black, etc) in all Javascript style properties accepting colors(ex: border, background, color, etc)
- Collecting all the style properties:
const PROPERTIES = ['background', 'backgroundColor', 'border', 'borderColor', 'borderBottom', 'borderBottomColor', 'borderLeft', 'borderLeftColor', 'borderRightColor', 'borderRight', 'borderTop', 'borderTopColor', 'color' ];
2. Collecting all the possible color literals:
const COLORS = ['black', 'silver', 'gray', 'white', 'maroon', 'red', 'purple', 'fuchsia', 'green', 'lime', 'olive', 'aqua', 'teal', 'blue', 'navy', 'yellow', 'orange', 'aliceblue', 'antiquewhite', 'aquamarine', 'azure', 'beige', 'bisque', 'blanchedalmond', 'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgreen', 'darkgrey', 'darkkhaki', 'darkmagenta', 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray', 'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dimgrey', 'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'greenyellow', 'grey', 'honeydew', 'hotpink', 'indianred', 'indigo', 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', 'lightgray', 'lightgreen', 'lightgrey', 'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslategray', 'lightslategrey', 'lightsteelblue', 'lightyellow', 'limegreen', 'linen', 'magenta', 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin', 'navajowhite', 'oldlace', 'olivedrab', 'orangered', 'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'rosybrown', 'royalblue', 'saddlebrown','salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'skyblue', 'slateblue', 'slategray', 'slategrey', 'snow', 'springgreen', 'steelblue', 'tan', 'thistle', 'tomato', 'turquoise', 'violet','wheat', 'whitesmoke', 'yellowgreen', 'rebeccapurple','#', 'rgb', 'rgba', 'hsl', 'hsla'
]
3. Creating a utility method to check:
a. If the property used resides in the PROPERTIES array.
function checkProperties(propertyName) {
if (propertyName) {
return PROPERTIES.some((property) => property === propertyName);
}
return false;
}
b. if the value is a hex-code(containing #), or rgb, rgba, hsl, hsla, or any other color value.
function checkValues(value) {
if (value) {
return COLORS.some((colorFormat) => value.toString().includes(colorFormat));
}
return false;
}
4. Re-writing our logic:
Property(node) {
if (!node || !node.key || !node.value) {
return;
} const colorPropertyMatched = checkProperties(node.key.name);
const valueHasColorLiterals = checkValues(node.value.value);
if (colorPropertyMatched && valueHasColorLiterals) {
context.report({
node: node.value,
message: "Unexpected: '{{name}}'. Color literals are not allowed",
data: {
name: node.value.value
}
});
}
}
colorPropertyMatched
: When the style property exists in the PROPERTIES array, it will hold the value true
valueHasColorLiterals
: When the value specified to the style object is one of a kind mentioned in the COLORS array, it will hold the value true
In case both colorPropertyMatched
and valueHasColorLiterals
are true, we have a Property node, having key as Javascript style property with a value containing color literals. In this case, ESLint will report us the error with the message that we added already.
As of now, the no-color-lierals.js
file would look like this:
Let’s modify the style object in the test.js
file to test for few more scenarios:
const pageStyles = {
textColor: {
color: '#333',
background:'white',
borderTop:'1px solid rgb(255, 0, 0)',
borderBottom:'1px solid hsl(0, 100%, 50%)',
}
};
console.log(pageStyles);
Now run the command npm run lint
to see the magic!
Voila! We have successfully added our lint rule to restrict color literals in our Javascript style properties. This will not affect your css
or scss
files.
Final Step (optional)
To add this rule for files with jsx
extension, we need to tweak our lint
script a bit in the pacakge.json
file.
"lint": "node_modules/.bin/eslint src --rulesdir rules --ext js,jsx"
Now we can add a dummy React component having inline style, and test the lint rule.
How to create a custom ESLint configuration and shareable plugin? PART-2