Make linting great again!

Being a front-end developer I’m always looking for tools that enable a shortest possible feedback loop, and I believe linting shouldn’t be an exception. Of course, there are IDE and text editor integrations and you definitely should use them. On the other side, there are cloud-based code quality tools, like CodeClimate and others or your custom CI tool and you should probably use them too. And there is a gap in between.

Imagine the following: you have made a PR to the repository with such a check enabled and quickly committed some code. A few minutes later, when you’re on a different task already, you receive an email saying that CI step is failing due to a forgotten semicolon! Switching tasks, fixing, committing, waiting for the CI to pass… “I wish I could lint before committing the changes to the repository” — I thought many many times.

And no, even having the IDE integration in place didn’t prevent me and my colleagues on different projects from committing a code that contained linting errors. Sometimes it wasn’t a big deal, but a few times it took me a few hours to find a bug after such commits. What a time waste that could be easily prevented if we were not allowed to put 💩 code into the repository!

Pre-commit hook

“Just set up a pre-commit hook” you might think at this point. At least I did. In git, you can “hook” and run custom scripts at almost any time point of the workflow be it commit, push or merge. This is a very powerful yet underrated tool. Most probably because they are

  • quite hard to setup and manage,
  • almost impossible to share across the team.

There is an awesome pre-commit package on npm, though, that solves all these issues. It allows you to run arbitrary scripts from your package.json before the commit will be authored. Here is how easy it is to install a pre-commit hook that runs ESLint on all *.js files:

// package.json
...
"scripts": {
"eslint": "eslint *.js"
}
"pre-commit": "eslint"
...

That does the job, but on bigger projects, it might take seconds to complete which is way too long. Another problem here is that it will display all linting errors and not just those from the files you’ve changed.

Wouldn’t it be great to run the linter against the changed files I’m about to commit? It would solve both problems: usually, we don’t put lots of files in one commit (if you do, please consider splitting your commits into smaller ones). That is possible with a custom shell script, but this is tedious. Instead, I’d like to declare the pre-commit linting configuration in package.json along with other tasks.

Introducing lint-staged

Lint-staged is a Node.js script that allows you to run arbitrary scripts against currently staged files. In git, a file is being “staged” after you have “added” it to a commit. This allows you to granularly pick the files that will go into your next commit.

The installation and setup are straightforward:

npm install --save-dev lint-staged pre-commit

and add a list of commands with glob patterns to run these commands against:

// package.json
...
"lint-staged": {
"*.js": "eslint",
"*.css": "stylelint"
}
...

This will run ESLint against *.js files and stylelint against *.css files that are currently staged. If any of these checks fail (process exits with non-zero code), the commit will be aborted.

And since it is using npm run command underneath, it’s even possible to run tasks from package.json (for example, to pass parameters to linters). Say, if you use SCSS syntax, you have to enable scss syntax in stylelint by passing a corresponding argument. This is possible by creating the following configuration:

// package.json
...
"scripts" {
"lint-staged": "lint-staged",
"stylelint-staged": "stylelint --syntax scss"
},
"lint-staged": {
"*.js": "eslint",
"*.scss": "stylelint-staged"
},
"pre-commit": "lint-staged"
...

lint-staged source code is on GitHub: https://github.com/okonet/lint-staged. If you have problems or suggestions, please open an issue or send me a PR and let me know what you think.