Ned Batchelder just wrote about checking javascript syntax in Django, so I figured it was about time to document how I use jslint:
- Use the jslint command line version.
- Add some jslint configuration comments to the top of your javascript files.
- Integrate the whole thing in Hudson for continuous checking.
Jslint can be run and configured in the browser by copy/pasting your code into http://www.jslint.com/ . Now, that's not a thing you want to do after every javascript change, right? So there's also a command line version for Rhino. Rhino is a command line javascript runner.
On debian/ubuntu, just do aptitude install rhino and download the jslint.js file. You don't need to type the lengthly commandline on that jslint page: on ubuntu you can just type rhino path/to/jslint.js path/to/your.js. Beware: as far as I could see, it did not accept multiple javascript files to test, so testing *.js will in fact only test the first one.
This led me to write a small python script that calls rhino with jslint and does some directory walking to find javascript files and calls jslint on all of them:
#!/usr/bin/python # Place this file somewhere as ``jslint`` (preferrably # without the .py) and make it executable. Place a # downloaded jslint.js in the same directory. import os import commands import sys RHINO = 'rhino' # "aptitude install rhino" on ubuntu. JSLINT = os.path.abspath(os.path.join( os.path.dirname(__file__), 'jslint.js')) def main(): if not len(sys.argv) > 1: print "Usage: jslint script1.js [script2.js...]" print " or: jslint directory" sys.exit(1) javascript_files = sys.argv[1:] if len(javascript_files) == 1: possible_dir = javascript_files[0] if os.path.isdir(possible_dir): javascript_files = [] for (dirpath, dirnames, filenames) in os.walk( possible_dir): javascript_files += [ os.path.join(dirpath, filename) for filename in filenames if filename.endswith('.js')] for javascript_file in javascript_files: (status, output) = commands.getstatusoutput( ' '.join([RHINO, JSLINT, javascript_file])) if status == 0: # Success! print "%s is OK" % javascript_file else: print "Error checking %s" % javascript_file print "exit code:", status print output sys.exit(status) sys.exit(0) if __name__ == '__main__': main()
Jslint is pretty picky, so you need to tame it a bit. Most common things you need to do:
- Tell it you run your javascript in a browser so that several (but not all) typical variables are known to exist. If you're missing one: google for "jslint" plus the missing one and you'll probably find a good explanation.
- Tell it about your globally available variables that you know are prepared for you by external libraries. Like $ if you're using jquery :-)
You do that by adding a few comment lines to the top of your javascript file, like this:
// jslint configuration; btw: don't put a space before 'jslint' below. /*jslint browser: true */ /*global $, OpenLayers, window, updateLayer */
Some of the things that jslint enforces:
- No extraneous or missing commas, semicolons, etcetera. You're guaranteed that Internet Explorer does not hickup because you forgot one of those.
- Stating your variables beforehand. You're required to state them all at the beginning of your file or your functions. No more half-way var xyz = 123;. It enforces good habits in this sense.
- It also forces you to change things that are perfectly ok, but that's pretty rare. The most common one is that it dislikes the normal "i++" style of loop. You have to change that into for (i = 0; i < something; i += 1) {.
The last thing is to add jslint checking to Hudson. Continuous integration should also mean continuous checking of your javascript code. I just added a manual "shell" step that calls the above jslint-running python script with the correct path. The script does a sys.exit() with a non-zero value if something goes wrong, so if you return that exit code to hudson, hudson will treat it as a full-blown test failure.
It really helped me to get a good feeling about the health of my javascript files!
