Skip to content

Instantly share code, notes, and snippets.

@weikinhuang
Last active August 29, 2015 14:13
Show Gist options
  • Save weikinhuang/e8fc437e4acf6503f8d1 to your computer and use it in GitHub Desktop.
Save weikinhuang/e8fc437e4acf6503f8d1 to your computer and use it in GitHub Desktop.
Javascript linting precommit hook with jscs and jshint
#!/bin/bash
#
# A hook to disallow js lint errors to be committed
#
# This is a pre-commit hook.
#
# To install this you can either copy or symlink it to
# $GIT_DIR/hooks/pre-commit
#
# Set up with npm install --save-dev jshint jscs
# OR
# Globally with npm install -g jshint jscs
#
# Author: Wei Kin Huang
# necessary check for initial commit
if git rev-parse --verify HEAD >/dev/null 2>&1; then
AGAINST_REV=HEAD
else
# Initial commit: diff against an empty tree object
AGAINST_REV=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi
NODE_BIN="$(which node 2>/dev/null)"
if type cygpath >/dev/null 2>&1; then
NODE_BIN="$(cygpath -u "c:/Program Files/nodejs/node.exe")"
fi
if ! type "$NODE_BIN" >/dev/null 2>&1; then
echo "JS Syntax check failed:"
echo "NodeJs binary does not exist or is not in path: node"
echo "You can bypass this hook with the --no-verify (-n) option"
exit 1
fi
NODE_CMD_SUFFIX=""
if [[ "$(uname)" = MINGW* ]]; then
NODE_CMD_SUFFIX=".cmd"
fi
JSCS_BIN=""
if type jscs >/dev/null 2>&1; then
JSCS_BIN=jscs
elif [[ -e "./node_modules/.bin/jscs${NODE_CMD_SUFFIX}" ]]; then
JSCS_BIN="./node_modules/.bin/jscs${NODE_CMD_SUFFIX}"
fi
JSHINT_BIN=""
if type jshint >/dev/null 2>&1; then
JSHINT_BIN=jshint
elif [[ -e "./node_modules/.bin/jshint${NODE_CMD_SUFFIX}" ]]; then
JSHINT_BIN="./node_modules/.bin/jshint${NODE_CMD_SUFFIX}"
fi
# dash does not support $'\n':
# http://forum.soft32.com/linux2/Bug-409179-DASH-Settings-IFS-work-properly-ftopict70039.html
IFS='
'
# error output
ERRORS=''
HAS_ERROR=0
# variables
INDICATOR_LENGTH=65
COUNTER=0
FILES=$(git diff-index --cached --full-index "$AGAINST_REV")
FILE_COUNT=$(echo "$FILES" | wc -l)
ERROR_INDICATOR="$(echo -e "\e[1m\e[1;31mE\e[0m")"
TMP_FILE=.git/pre-commit-tmpfile
function printcounter() {
local indicator="$1"
# increment counters
COUNTER=$((COUNTER+1))
echo -n $indicator
if [[ $(($COUNTER % $INDICATOR_LENGTH)) == 0 ]] || [[ $COUNTER == $FILE_COUNT ]]; then
if [[ $COUNTER == $FILE_COUNT ]]; then
echo -n $(printf '%'$(($INDICATOR_LENGTH - $(($COUNTER % $INDICATOR_LENGTH))))'s' '')
fi
echo " $(printf %3s $COUNTER) / $(printf %3s $FILE_COUNT)"
fi
}
PRECOMMIT_IGNORE_JS=$(
cat << EOF
try {
var ignorePatterns,
fs = require("fs"),
minimatch = require("minimatch");
ignorePatterns = fs.readFileSync(".precommitignore", "utf8")
.replace(/\r/g, "")
.split(/\n/)
.filter(function(v) { return !!v.trim(); });
process.exit(
ignorePatterns.some(function(pattern) {
return minimatch("FILE_TO_REPLACE", pattern, { nocase : true, matchBase : true });
})
? 0
: 1
);
} catch (e) {
process.exit(0);
}
EOF
)
# get a list of staged files
for line in $FILES; do
# split needed values
#oldmode=$(echo $line | cut -d' ' -f1)
newmode=$(echo $line | cut -d' ' -f2)
#oldsha=$(echo $line | cut -d' ' -f3)
sha=$(echo $line | cut -d' ' -f4)
temp=$(echo $line | cut -d' ' -f5)
status=$(echo $temp | cut -d' ' -f1)
filename=$(echo $temp | cut -d' ' -f2)
ext=$(echo $filename | sed 's/^.*\.//')
indicator='.'
# do not check deleted files
if [[ $status = "D" ]]; then
printcounter D
continue
fi
# do not check symlinks
if [[ $newmode = "120000" ]]; then
printcounter L
continue
fi
if "$NODE_BIN" -e "${PRECOMMIT_IGNORE_JS/FILE_TO_REPLACE/$filename}"; then
printcounter S
continue
fi
# check the staged file content for syntax errors using jshint
git cat-file -p $sha > $TMP_FILE
if [[ "$ext" == "js" ]]; then
# jscs check
if [[ -e ./.jscsrc ]] && [[ -n "$JSCS_BIN" ]]; then
result=$("${JSCS_BIN}" "$TMP_FILE" --config=./.jscsrc 2>&1)
if [[ $? -ne 0 ]]; then
HAS_ERROR=1
indicator=$ERROR_INDICATOR
#echo "$result"
# Swap back in correct filenames
ERRORS=$(echo "$ERRORS"; echo "$result" | perl -pe "s#${TMP_FILE/\//\\\\}|${TMP_FILE/\\//}#${filename}#")
fi
fi
# jshint check
if [[ -e ./.jshintrc ]] && [[ -n "$JSHINT_BIN" ]]; then
result=$("${JSHINT_BIN}" "$TMP_FILE" --config ./.jshintrc 2>&1)
if [[ $? -ne 0 ]]; then
HAS_ERROR=1
indicator=$ERROR_INDICATOR
#echo "$result"
# Swap back in correct filenames
ERRORS=$(echo "$ERRORS"; echo "$result" | perl -pe "s#${TMP_FILE/\//\\\\}|${TMP_FILE/\\//}#${filename}#")
fi
fi
fi
printcounter $indicator
done
unset IFS
rm -f "$TMP_FILE"
if [[ $HAS_ERROR -eq 1 ]]; then
echo -n "$ERRORS"
echo ""
exit 1
fi
@weikinhuang
Copy link
Author

Files can be skipped with a .precommitignore file in the repository root.
Also this uses the .jshintrc and .jscsrc to check if we have the ability to validate the files.

@weikinhuang
Copy link
Author

Update, fix skipped file test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment