Fast JSHint Git Pre-Commit Hook

If you use a linter like JSHint for your JavaScript projects, here is a way to automatically ensure all code is linted before commiting.

You could have a very simple pre-commit hook like this:

#!/bin/sh
#
# Lints files with jshint before commiting them.

jshint  

However, if you have a large project linting the entire project can become very slow. This will make git commit very slow and annoying. The only files that need to be linted are the files you are commiting. The pre-commit hook below uses git to lint only the files that have been staged for commiting and is much faster.

#!/bin/sh
#
# Lints staged files with jshint before commiting them.

# get the files that we should lint
files="$(  
  # NOTE: the file paths returned are relative to the git root despite the current working directory
  # get all staged files
  git diff --name-only --diff-filter=ACMRTUXB --cached |
    # only lint files that end with .js
    grep '\.js

            
)" # cd up to the git root # example: if we are in {gitRoot}/fu/bar/ then cd ../../ cd "$(git rev-parse --show-cdup)" # get the current working directory (now git root) absolute path cwd="$(pwd)" # escape sed special chars and add trailing slash # we will use this with sed below sedCWD="$(echo $cwd | sed -e 's/\\/\\\\/g' -e 's/\//\\\//g' -e 's/&/\\\&/g')\/" finalRetVal=0 # for each staged file... for file in $files do # lint the STAGED content of the file (the colon tells git to output the staged content) git show :"$file" | # this assumes you have a .jshintrc config file at the git root # the dash at the end tells jshint to lint stdin jshint --config .jshintrc --filename "$file" - | # jshint displays the absolute path for each file which is annoying # lets trim that so they are relative to the git root sed "s/^$sedCWD//" # get the return value of the jshint command (second command in the pipeline) retVal=${PIPESTATUS[1]} # if any of the linting failed we should return a non-zero status if [ $retVal != 0 ] then finalRetVal=$retVal fi done; # use the status of the last failed jshint command or 0 if none failed exit $finalRetVal;

Save this as {gitRoot}/.git/hooks/pre-commit and make it executable with chmod +x pre-commit. Now when you commit all staged files will be linted. Any errors will cause the commit to fail.

You can use a similar script to lint all changed files in your project at any time. I describe how here.