Last active
August 29, 2015 14:13
-
-
Save weikinhuang/e8fc437e4acf6503f8d1 to your computer and use it in GitHub Desktop.
Javascript linting precommit hook with jscs and jshint
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Update, fix skipped file test