Checking javascript syntax in Hudson: jslint

Tags: django, plone

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!

Pumping station construction near Houten
 
vanrees.org logo

Reinout van Rees

My name is Reinout van Rees and I program in Python, I live in the Netherlands, I cycle recumbent bikes and I have a model railway.

Weblog feeds

Most of my website content is in my weblog. You can keep up to date by subscribing to the automatic feeds (for instance with Google reader):