lint-staged + JetBrains IDE

UPD: There is a easier way to do it. Just check the official documentation of lint-staged.

OK, we need some English here.

When you use lint-tools like ESLint or Stylelint you also want to get auto-fix of small mistakes like spaces, new lines, case, etc. And you usually want to do it before committing.

There is an awesome tool for this — lint-staged. It can run linters on git staged files. But it’s completely useless for JetBrains IDEs like WebStorm, PhpStorm, etc, because they don’t use ’staged’ in git. The IDEs manipulate staged parts by themselves. There is a ticket in JetBrains YouTrack, but these guys haven’t fixed it yet. And it looks like they don’t want to fix it at all.

I voted for this bug half of a year ago because I couldn’t run lint-staged (they also have an issue about it). After that, I have been receiving an email from YouTrack every week, and I feel that I should write about the solution that I found.

How to do black magic

Disclaimer: this solution is a dirty workaround that you don’t want to see in your repo, but it’s the only way that I can offer you.

First of all, create an example project in your IDE with npm and linters that you need. Then install pre-commit-hook plugin in your IDE. And now you should add some bash-magic.

Create a folder ’lint’ and add js and scss files there:

js:

#!/usr/bin/env bash

set -o errexit

fix=$([ -z $1 ] && echo '' || echo '--fix')
files=${1:-"example"}

eslint $fix --cache -c .eslintrc.json --ext .js $files

scss:

#!/usr/bin/env bash

set -o errexit

fix=$([ -z $1 ] && echo '' || echo '--fix')
files=${1:-"example/**/*.scss"}

stylelint $fix "$files" --syntax scss --cache --config .stylelintrc.json

Here you see two interesting parts. First, a dynamic key fix that we set only when there are some files passed as the first argument. We need it because sometimes we want just lint, without fixing (e. g. on CI).

Second, a dynamic value of files. We also set it only when we have them as the first argument. Otherwise we lint all files.

Now create another one file in ’lint’ folder — each-file:

#!/usr/bin/env bash

set -o errexit

function getGlob {
  # linters want to get glob instead of just a list of files
  local IFS=",";

  if [ "$#" -gt 1 ]; then
    echo "{$*}"
  else
    echo "$*"
  fi
}

scssFiles=()
jsFiles=()
isError=0

for file in "$@"; do
  # skip removed files
  if [ ! -f "$file" ]; then
    continue
  fi

  if [[ "$file" =~ .*$(pwd)\/example\/.*\.js$ ]]; then
    jsFiles+=("$file")
  elif [[ "$file" =~ .*$(pwd)\/example\/.*\.scss$ ]]; then
    scssFiles+=("$file")
  fi
done

if [ -n "$scssFiles" ]; then
  ./lint/scss $(getGlob "${scssFiles[@]}") || isError=$?
fi

if [ -n "$jsFiles" ]; then
  ./lint/js $(getGlob "${jsFiles[@]}") || isError=$?
fi

exit $isError

This uber-file runs linters for passed files (and do some magic for creating globs, but nevermind).

Now we need to create a file for pre-commit-hook that we installed before. Let’s call it pre-commit-hook.sh and save into ’lint’ folder:

#!/usr/bin/env bash

export PATH=./node_modules/.bin:/usr/local/bin:$PATH

./lint/each-file "$@"

It’s pretty simple and just passes all arguments to each-file.

And now the final step. We need to install lint-staged and husky, and write some npm scripts. Install them and add this to your package.json:

"scripts": {
  "lint": "[[ -f 'pre-commit-hook.sh' ]] || lint-staged",
  "lint-js": "bash ./lint/js",
  "lint-scss": "bash ./lint/scss",
  "precommit": "npm run lint"
},
"lint-staged": {
  "*": ["bash ./lint/each-file", "git add"]
}

And this to your .gitignore:

# Pre-commit hook
pre-commit-hook.sh

Now your project is ready to work with linters and lint-staged. If you or someone from your team uses any JetBrains IDE — just copy-paste pre-commit-hook.sh from ’lint’ folder to project root locally and IDE will run pre-commit-hook plugin for each commit.

So, if you commit using JetBrains IDE, pre-commit-hook plugin will run linters for changed files (because it will get only them from IDE). If somebody commits using CLI, lint-staged will run linters for staged files.

Examples

Let’s imagine that we have folder ’example’ with two files: example-1.scss and example-2.scss with this code (we use two files instead of one only for checking that linter works fine with both):

// try to commit this file with lint mistakes that can be fixed by Stylelint

.example {
  color: #F00;
}

Stylelint must fix uppercase of color and replace ’#F00’ to ’#f00’ if everything works fine.

Now, we have two ways how to test it.

You use JetBrains IDE

Copy file pre-commit-hook.sh from ’lint’ folder to project root. Then try to commit file using IDE:

Note: option ’Run Git hooks’ must be checked.

Press ’Commit’ and let the plugin work:

After that let’s check committed files:

The color was fixed!

You use a console or any other tool for git

Note: you must remove pre-commit-hook.sh from project root if it’s there.

Just add files to stage and commit them as usual:

And now let’s check that changes are here and files have been committed with them:

It works.

Conclusion

I hope that someday JetBrains will fix it and we can throw all of this horrible code away. But today it works in this way, and I’ve created a repo with a template and some examples. Check this out, it must be more useful than this article:

github.com/igoradamenko/lint-staged-jetbrains-ide-example

It’s my first article in English, so if you see some mistakes, please drop me a line about them.

2018
Популярное