Running JSLint as a Subversion Commit Hook with PHP and Rhino
By Gavin Davies
We recently blogged about using JSLint to automatically find trailing commas, which has helped us find IE7 bugs before they ever reach the browser. We thought we’d take this approach a step further and put a hook on our Subversion repository that not only checks for trailing commas, but reports any JSLint errors that have been committed. We’ll talk through the why and how so you can set this up in your own company.
Why run JSLint on a commit hook?
Douglas Crockford
JavaScript is a wonderful, powerful, expressive language, but it gives you plenty of scope to shoot yourself in the foot. JSLint is a static code analysis tool that scans the source for common coding errors. Although Box UK’s coding standards state that “all JavaScript files should pass JSLint”, we have not, up to now, done anything to enforce this. Following a spate of internal tickets related to bugs in IE7 that could have been avoided by following JSLint recommended best practises, we decided to implement a commit hook.

JSLint errors are not necessarily showstoppers so it didn’t make sense to start failing commits, so we decided to use a temporary post-commit hook. Post commit hooks do not fail the commit – they happen after the commit has already taken place. We are planning to move to a pre-commit hook after a couple of months, once our developers have had chance to get used to making sure their code passes JSLint.
The How – Setting Up JSLint as a Commit Hook
We set this up on Debian Linux, but it should work equally on Windows, Mac or Unix. What you’ll need:
- Your Subversion server and repository must be set up and working.
- Java installed (Sun JVM recommended).
- Rhino JavaScript engine for Java installed.
- PHP installed.
- A copy of the JSLint javascript with the Rhino extension. Download and concatenate http://www.jslint.com/fulljslint.js and http://www.jslint.com/rhino/rhino.js into a single file jslint.js
Step one: Make sure Rhino works and can run JSLint
On your SVN server, install Rhino. You should test Rhino on a JavaScript file. Here is a little script to check it:
# echo var foo = {}; > tmp.js
# rhino /usr/sbin/jslint.js tmp.js
jslint: No problems found in tmp.js
Step two: Create a jslint.php PHP file to check the repo
Create a file on your SVN server, jslint.php.
Our script, jslint.php, looks something like the following:
<?php
/**
* This script will reject any files that fail our JSLint test
*/
// variables
$lintCmd = '/usr/bin/rhino /opt/BoxUK/commitHookScripts/jslint.js';
// collect arguments passed in
list( $ALL, $svnlook, $reposPath, $revId ) = $argv;
exec( "$svnlook changed "$reposPath" -r "$revId" ", $lines );
// array collection used to determine pass/fail
$jsLintErrors = array();
foreach ( $lines as $line ) {
// Make sure we only check javascript files
$ext = substr($line, -3);
if ($ext !== '.js') {
continue;
}
// collection of relevant files
$aMatches = array();
if (!preg_match('/^[AU]s+(.+.)(.+)$/i',$line,$aMatches) ) {
continue;
}
// extract the actual filename
list( $ALL, $fileName, $extension ) = $aMatches;
$filePath = $fileName . $extension;
// see what has been committed
$data = '';
$cat = "svnlook cat "$reposPath/" "$filePath" 2>&1 ";
exec( $cat, $data );
// write javascript to temporary file for ease of linting
$tmpFile = '/tmp/js' . time() . '.js';
$fp = fopen( $tmpFile, 'w' );
fwrite( $fp, join("n",$data) );
fclose( $fp );
// jslint the file
$returnValue = null;
$errorCount = 0;
$aErrors = array();
exec( "$lintCmd "$tmpFile"", $aErrors, $errorCount );
// remove temporary file
unlink( $tmpFile );
// if there were any problems, then report them
if($errorCount > 0) { // error count
$jsLintErrors[] = array(
'path' => $filePath,
'errors' => $aErrors
);
}
}
function gecho($s) {
static $cnt = 0;
if (++$cnt < 50) {
echo $s;
} else if ($cnt === 50) {
echo "n ** Too many errors to display in SVN response! **";
}
}
// If we have errors, then pass them back and throw a fail code 1
if (count($jsLintErrors) > 0) {
gecho ("** JAVASCRIPT JSLINT WARNINGS ** ");
gecho (" Your files are committed and your work is safe, but you should
do an SVN UP on the files you just committed, fix these problems,
and commit again.");
foreach ($jsLintErrors as $err ) {
gecho ("n WARNING: Bad JavaScript detected in {$err['path']}:n");
foreach($err['errors'] as $error) {
gecho ("n $error");
}
}
exit(42);
}
Notice that we use the function “gecho”, which keeps a count of the number of errors it has output. This is because if we had too many errors, SVN would return MERGE 200 ERROR (see Troubleshooting section below).
To test it, run:
php jslint.php {pathToSvnLook} {pathToRepo} {revisionNumber}
e.g.
php jslint.php /bin/svnlook /svn/amaxus4 8130
This should give you a summary of JavaScript error messages in that commit.
Step 3: Install commit hook
Find your SVN repository’s hooks directory. There will be a file in there called post-commit.tmpl – that’s the template for a post commit hook. Create a file called post-commit. Make sure whatever use SVN is has execute permissions for that file.
#!/bin/sh # POST-COMMIT HOOK # Make sure that the file passes JSLint if it is a JS file PHP=/usr/bin/php SCRIPTS=/opt/BoxUK/commitHookScripts SVNLOOK=/bin/svnlook REPOS="$1" REV="$2" $PHP $SCRIPTS/jslint.php "$SVNLOOK" "$REPOS" "$REV" 1>&2 || exit 15
All this does is call the PHP file we made in step 2, passing in the parameters the script needs. Make sure you alter the PHP, SCRIPTS, and SVNLOOK variables to match your machine’s settings.
For Windows, this will be a batch file, so it will look a bit different. If you research commit hooks on Windows, there are plenty of resources out there for getting this working.
Step 4: Test the hook
We recommend the following tests:
- Commit some good JavaScript to your repo. Observe that all is well and no errors are shown.
- Commit non-JavaScript files to your repo. Observe that all is well and no errors are shown.
- Commit some bad JavaScript to your repo. Observe that it is committed, but errors show up.
Hopefully all is well, but if not, here are some troubleshooting tips:
Troubleshooting
It doesn’t work!
Check your permissions. The repo hook should be executable, and the system should be able to run Rhino and Java without assuming any environment variables.
Rhino very slow?
We found that with the GJJ implementation of the JVM, Rhino was slow to the point of being unusable. We switched to Sun Java 1.6 and it was 20 times quicker.
200 MERGE OK error
One teething issue we had was we got a “200 MERGE OK” error when there was a lot of output from the commit hook. It turned out that we could only send back 60 lines or so; any more, and we got the 200 MERGE OK error and no more information back from Subversion.
Alternatives and further materials
All we’ve done here is integrate JSLint with an SVN post-commit hook. Greg Sherwood shows how you can integrate jslint with PHPCodeSniffer for a more complete approach; you could add this to a commit hook if you wished.
JavaScript Lint (jsl) is similar to jslint, and can also be integrated with PHPCodeSniffer.
Wrap up
Static validation cannot catch all problems, but it is another line of defence in the battle against bugs. Have a read of Douglas Crockford’s book or his website. For the Amaxus team, this is another step towards having the highest quality Web CMS on the market.