Linters Gonna Lint

Incremental lint, Prettier, immutable rules.

This is an update to my 1, 2, 3 linted blog post. Time is passing by and linting has changed a little bit, and my setup has changed a lot in these two years.

Setting up linting

The easiest linting setup is the one you don't have to do. Thus I recommend you use generator-node-bahmutov to setup your projects, which includes npm run lint step.

If you do have to setup linting yourself, I recommend using prettier-standard to format the code, and standard to lint it. My package.json looks like this

1
2
3
4
5
6
7
8
{
"scripts": {
"pretty": "prettier-standard src/*.js",
"prelint": "npm run pretty",
"lint": "standard --verbose --fix src/*.js",
"pretest": "npm run lint"
}
}

🎁 I have a very detailed blog post How to configure Prettier and VSCode showing how to use Prettier with ESLint

Specific environments

Often, there are differences in globals and settings between files. For example in my Mocha tests, there are describe, it, beforeEach, afterEach and other global functions. You can quickly let standard and eslint know to extend settings to cover these additions by specifying custom lint environment

1
/* eslint-env mocha */

There are environments for all major tools like browser, jest and many others

Incremental linting

As the project grows, it becomes harder to sit and wait while a lot of files are linted. Especially if most of them are unchanged! Thus it is nice to setup incremental linting on pre-commit hook. I recommend using husky and lint-staged for this. Just make sure to define a separate lint command for the pre-commit to call. Do not include filename wildcard - the changed files will be passed automatically from the pre-commit hook.

1
2
3
4
5
6
7
8
9
10
11
12
{
"lint-staged": {
"*.js": [
"precommit-lint",
"git add"
]
},
"scripts": {
"precommit": "lint-staged",
"precommit-lint": "prettier-standard"
}
}

In the above package.json, husky will execute npm run precommit command, which will call lint-staged module. This module will grab the list of staged files, select JavaScript ones against *.js (you can define multiple commands for different files!) and will run linter by calling npm run precommit-lint -- file1.js file2.js. Any files modified by the linter will be added to the Git staging area. This hook makes it extremely easy to lint files without noticeable delay on commit.

If you want to test it out, just stage some files and run the Git pre-commit hook

1
2
3
4
5
6
7
8
9
$ ./.git/hooks/pre-commit
husky > npm run -s precommit (node v6.8.1)

❯ Running tasks for *.jsx
✖ precommit-lint
git add
🚫 precommit-lint found some errors. Please fix them and try committing again.

... lint errors ...

If you want to lint both JS and JSX files, use minimatch syntax

1
2
3
4
5
{
"lint-staged": {
"*.{js,jsx}": "eslint"
}
}

Immutable data linting rules

There is one more plugin that I am trying out to improve my code. This is a set of Eslint rules to prevent data mutations eslint-plugin-immutable. While very strict, it does force me to write clear, pure code. A sample project using this plugin is git-last. Unfortunately, we cannot extend standard linter with additional rules, so we must bring the original eslint.

1
npm install -D eslint eslint-plugin-immutable

The create rules (it is recommended to disable var declarations as well). I have configured the eslint inside package.json.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"eslintConfig": {
"env": {
"es6": true
},
"plugins": [
"immutable"
],
"rules": {
"no-var": 2,
"immutable/no-let": 2,
"immutable/no-this": 2,
"immutable/no-mutation": 2
}
},
"scripts": {
"pretty": "prettier-standard 'src/*.js'",
"prelint": "npm run pretty",
"lint": "standard --verbose --fix src/*.js",
"lint-immutable": "eslint --fix src/*.js",
"pretest": "npm run lint && npm run lint-immutable"
}
}

Sometimes we do have to mutate the data, of course. I usually disable a specific rule for specific line

1
2
3
function gitLast() {...}
// eslint-disable-next-line immutable/no-mutation
module.exports = gitLast

Happy linting!