What is focus? Concentration of power. When writing Javascript code, most of us focus on solving very complex problems. While dedicating intellectual capacity to the solution, small code quality mistakes are often made. It takes time and effort to fix these mistakes, provided that you notice them. If you don't, your teammates will definitely point them out. On your worst days, you may end up feeling embarrassed: despite the fact that you stick to the highest possible standards, you are still human and you make mistakes you should not be making.
Many people have realized in the past that code quality checks have to be automated. For this reason, Javascript linters were invented. Some of you may already use a linter like JsLint or JsHint. They are all great tools for increasing your accuracy. In this article, I will introduce you to an alternative: ESLint. ESLint provides you with full control over the linting process, including the application of your own custom rules.
Never Violate Agreements
Excellent code quality is a necessary condition for excellent maintainability. If you work in a team, in order to avoid confusing each other, you have to stick to standards. If you think you don't work in teams, think again! Unless you create hobby projects for yourself, you have to write code under the assumption that someone else will contribute to your code base. Your coding standards have to be enforced and automatized. This is where ESLint comes in: it gives you the freedom to focus on the problem you would like to solve, and let the rest happen on autopilot. You may make mistakes such as:
- small formatting and indentation errors
- forgetting to remove debuggers and console.log statements
- typos in variable names
- using the wrong type of quotes
- using
==
instead of===
This list is not exhaustive and can never be exhaustive. Some teams have specific agreements that no-one has ever thought about. ESLint gives you the chance to create and enforce these agreements via custom rules.
Get started
Similarly to my last post on ES6 modules and Webpack, you will need NodeJs to install ESLint.
1 2 3 |
npm install -g eslint |
You will also need a configuration file .eslintrc
, wherever you would like to run ESLint. The documentation describes the possible settings you can include in your JSON object in your configuration file. It would be useless to include a list here as new options are continuously added as new versions come out.
I will write an example file with a couple of injected errors ESLint should catch. This function should calculate n!
according to the recursive definition n! = n * (n - 1)!
, including the exit criterion 0! = 1
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
function factorial( n ) { debugger; if( typeof n !== 'number' || n % 1 !== 0 || n < 0 ) return { errorcode : 1, error : 'n should be a positive integer' }; else if( n == 0 ) { return 1 } else return n * factorial( n - 1 ) } |
How many errors can you spot in this code? Regardless of the quality standards you stick to, there is one error that does not even let the code execute.
Automatic semicolon insertion and the return statement: Semicolons are inserted automatically at the end of each line where the statement is not supposed to continue. This happens to our first return
statement too. The code
1 2 3 4 5 6 7 |
return { errorcode : 1, error : 'n should be a positive integer' }; |
becomes
1 2 3 4 5 6 7 |
return; { errorcode : 1, error : 'n should be a positive integer' }; |
after the semicolon insertion. This results in a syntax error. Let's verify the error with ESLint:
1 2 3 4 5 6 7 8 |
$ eslint factorial.js factorial.js 7:18 error Unexpected token : ✖ 1 problem (1 error, 0 warnings) |
The solution to this problem is that the open brace of the returned object has to be on the same line as the return statement. After making this change, ESLint can start detecting other problems:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
$ eslint factorial.js factorial.js 1:0 error Missing "use strict" statement strict 1:9 error factorial is defined but never used no-unused-vars 2:4 error Unexpected 'debugger' statement no-debugger 3:4 error Expected { after 'if' condition curly 3:21 error Strings must use doublequote quotes 6:19 error Strings must use doublequote quotes 8:9 error Expected { after 'else' curly 8:15 error Expected '===' and instead saw '==' eqeqeq 10:16 error Missing semicolon semi 13:37 error Missing semicolon semi 14:1 error Newline required at end of file but not found eol-last ✖ 11 problems (11 errors, 0 warnings) |
Eleven linting errors. We have some work to do.
First of all, I did not use strict mode. We can easily address this problem by adding "use strict"
to the beginning of the file. It is good practice anyway.
We were also told that factorial
was never used in the code. ESLint is intelligent enough to detect that a recursive call does not count as a usage. For the sake of simplicity, I will encapsulate factorial
function in a self-invoking function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var utils = {}; (function( context ) { "use strict"; function factorial( n ) { debugger; if( typeof n !== 'number' || n % 1 !== 0 || n < 0 ) return { errorcode: 1, error: 'n should be a positive integer' }; else if( n == 0 ) { return 1 } else return n * factorial( n - 1 ) } context.factorial = factorial; })( utils ); |
The variable utils
is only there for demonstration purposes. Let's continue with the errors.
An unexpected debugger
statement was found in the code. I have seen people push debugger
statements to remote repositories and it's not a nice experience to load an application just to find out that it enters debug mode under some circumstances. ESLint is intelligent enough to detect debuggers, therefore you will never have to worry about pushing a forgotten debugger
statement.
Warning: Even if executing ESLint is an integral part of your workflow, it should not be made responsible for handling
debugger
statements. You have to have a security net making sure that debuggers never end up on production. When preparing production code, make sure you automatically remove alldebugger
statements.
We have two errors with the name curly
. ESLint does not like code of format
1 2 3 4 |
if ( condition ) statement; |
The reason is that it is very easy to shut off our brain and write the following:
1 2 3 4 5 |
if ( condition ) statement1; statement2; |
In practice, statement2
is executed regardless the value of the condition as only statement1
belongs to the if statement. Although this error has never happened to me, it could happen to others, so I mostly respect the curly
rule. The only exception could be the shorthand
1 2 3 |
if( condition ) return; |
This shorthand is more compact, more readable, and the above reasoning does not apply as we cannot trick ourselves with wrong indentation.
The semi
errors indicate missing semicolons. Although Javascript inserts them automatically, it is still good practice to go the extra mile (extra millimeter to be exact) and write those semicolons explicitly. Just like free text, Javascript code should also be readable. Imagine reading this article without full stops. It would not be convenient, right?
The rule eol-last
is not a big deal. It just indicates the lack of terminating new line at the end of the file. Let's add it.
We are almost done. If you prefer strict equality to ==
, don't turn eqeqeq
off! I erroneously wrote the condition n == 0
. Let's correct it to n === 0
.
Our beautified code looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
var utils = {}; (function( context ) { "use strict"; function factorial( n ) { if( typeof n !== 'number' || n % 1 !== 0 || n < 0 ) { return { errorcode: 1, error: 'n should be a positive integer' }; } else if( n == 0 ) { return 1; } else { return n * factorial( n - 1 ); } } context.factorial = factorial; })( utils ); |
Another linting reveals that most errors are gone:
1 2 3 4 5 6 7 8 9 |
$ eslint factorial.js factorial.js 8:25 error Strings must use doublequote quotes 12:23 error Strings must use doublequote quotes ✖ 2 problems (2 errors, 0 warnings) |
Let's make the conscious decision that this rule is not for us because we prefer single quotes to double quotes. We can easily change our preferences by adding a rule to .eslintrc
.
1 2 3 4 5 6 7 8 9 10 |
{ "env": { "browser": 1 }, "rules": { "quotes": [2, "single", "avoid-escape"] } } |
The new rule specifies that we have to use single quotes for strings. Double quotes are only tolerated inside a string for the purpose of avoiding escaping. Let's run the linter again!
1 2 3 4 5 6 7 8 |
$ eslint factorial.js factorial.js 5:4 error Strings must use singlequote quotes ✖ 1 problem (1 error, 0 warnings) |
Surprisingly, we used single quotes as well as double quotes in this function. Mixing the two is not advised as it indicates lack of consistency. The guilty double codes were added with the string "use strict"
. After changing the quotes and running ESLint again, all errors will disappear.
Let's add a built in ESLint rule to our configuration file. As you might have noticed, I used the form if( condition )
. Some coding standards require a space between the keyword and the open parenthesis so that it is easier to tell the difference between a statement and a function. Let's add this rule to the .eslintrc
file.
1 2 3 4 5 6 7 8 9 10 11 |
{ "env": { "browser": 1 }, "rules": { "quotes": [2, "single", "avoid-escape"], "space-after-keywords": 2 } } |
You can find the space-after-keywords
rule in the list of all ESLint rules in the official documentation.
Re-linting the code gives you two new errors.
1 2 3 4 5 6 7 8 9 |
$ eslint factorial.js factorial.js 8:8 error Keyword "if" must be followed by whitespace space-after-keywords 15:13 error Keyword "if" must be followed by whitespace space-after-keywords ✖ 2 problems (2 errors, 0 warnings) |
Fixing the indentation errors will make linting succeed. Let's have a look at the final result.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
var utils = {}; (function( context ) { 'use strict'; function factorial( n ) { if ( typeof n !== 'number' || n % 1 !== 0 || n < 0 ) { return { errorcode: 1, error: 'n should be a positive integer' }; } else if ( n === 0 ) { return 1; } else { return n * factorial( n - 1 ); } } context.factorial = factorial; })( utils ); |
The code is a lot nicer to read than at the start of the article, isn't it?
Always Use a Linter
Automation has a key role in increasing productivity and accuracy. If you want to write maintainable code, make linting a must for yourself and your team! A safety net that catches your errors gives you the freedom to focus on finding the most efficient, elegant and maintainable solution to the problems you face with.
I have very ambitious goals with ESLint, namely covering my coding standards as much as possible. This will definitely include writing some custom rules. If you get stuck with ESLint or you would like to read about a specific topic related to linting, write a comment below.