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.

Mike Moore

Read more posts by this author.

Austin, TX yo1.dog
comments powered by Disqus