Continuous Code Validation using JSLint


Callbacks are often found in JavaScript code. Some say ‘They are inconvenient and make code harder to understand’. Actually, it is a matter of habit and JS code may be as readable and easy to understand as any other. Additionally, this approach of coding allow us to ‘release’ program flow. No function is waiting for another’s result. Everything goes like stream.

Few month ago there was buzz around Node.js: Google’s V8 JavaScript engine acting as server. Before that nobody thought about event-based programming on server side. Every I/O operation in Node.js is asynchronous (database queries, reading from file or fetching content of some webpage). These are all the time used in web applications, especially real-time ones. Besides, lightweight and high-performance Nginx server is using event model.

There is enough articles about Node.js functioning. Today, I’ll show how to create real application that is checking in continuous way if our code project in JSLint by Douglas Crockford. All with growl notifications.

Node.js installation

Firsty, download and unzip the newest version of Node.js from GitHub.

Sadly, Node.js doesn’t support Windows: you must use Virtual Machine.

If you’re on Mac OS x, Linux or FreeBSD type this into console:

> git clone http://github.com/ry/node.git
> cd node
> ./configure
> make
> sudo make install

It will install Node.js on your machine. Check if everything works:

> echo "require('sys').puts('Hello world');" > test.js
> node test.js

If you see Hello World message, well done.

Creating project

Growl integration is boring – we’ll use one of many modules for Node.js. We’ll also need JSLint and two icons to make notification beautiful.

mkdir grolint
cd grolint
wget http://www.jslint.com/jslint.js
wget http://github.com/visionmedia/node-growl/raw/master/lib/growl.js
wget -O no.png http://imgur.com/Jq7TC.png
wget -O ok.png http://i.imgur.com/uihIH.png

In Node.js external files are included using require() function that returns object of global functions and variables in included file. In fact that object is equal to the exports object in included file. So if we want use JSLINT function from jslint.js file, we have to add one line at the end:

echo "exports.check = JSLINT;" >> jslint.js

We’ll also use built-in sysfs, path and child_process modules. Sys is responsible for I/O with console, fs is acronym of File System and child_process allows us to run other console applications (Growl in our case).

Let’s create grolint.js file and write some first lines:

var sys = require('sys'),
    fs = require('fs'),
    path = require('path'),
    growl = require('growl'),
    jslint = require('./jslint');

Note that if we include non-internal module, we add ./ at the beginning of the relative path and we don’t add “.js” at the end. You may check if everything’s OK: place this at end of file and run node grolint.js command:

sys.puts(sys.inspect(jslint));

You should see something like:

{ check: 
   { [Function]
     data: [Function]
   , report: [Function]
   , jslint: [Circular]
   , edition: '2010-04-06'
   }
}

Application body

Our application will do two things:

  1. At the beginning validate all .js files in current directory against JSLint
  2. Watch for changes in every mentioned file and validate continuously

The first thing is validating single file.

function validate(filename, callback) {
    sys.print('Checking ' + filename.replace(__dirname + '/', '') + '... ');
    fs.readFile(filename, 'utf8', function(err, file) {
        callback(jslint.check(file.replace(/^\#\!.*/, '')));
    });
}

We created function validate in which we call fs.readFile that asynchronously reads the entire contents of a file. Second argument is encoding – binary, ascii or utf8. Third function is callback – and that’s beauty of Node.js. In PHP we would wait for contents of the file. Here interpreter continues working and when the file is loaded it will be passed as argument to callback function.

JSlint.check checks given JavaScript source and returns true/false depending on whether it validates. If no, errors are stored in jslint.checks.errors array.

Now, we need callback function that tells developer whether and what’s wrong:

function status(ok) {
    if(ok) {
        sys.puts("all right");
    } else {
        var errors = jslint.check.errors;
        sys.puts(errors.length + ' errors');
        for (var i = 0; i < errors.length; i += 1) {
            if (errors[i]) {
                sys.puts(' ' + errors[i].line + ': ' + errors[i].reason);
            }
        }
    }
}

We want also similar function but with growl notifications. It will alert developer if something’s wrong in saved file:

function status_and_growl(ok) {
    if(!ok) {
        growl.notify(jslint.check.errors.length + ' errors (check console)');
    }
    status(ok);
}

Next. Notice we want to process every .js file in current directory and subdirectories. To accomplish that, we need function that walks a directory recursively and call specific function on each JavaScript file. Here’s source:

function walk(filename, callback){
    fs.stat(filename, function(err, stats) {
        if(stats.isFile() && filename.match(/\.js$/)) {
            // Filename - do callback
            callback(filename);
        } else if(stats.isDirectory()) {
            // Directory - walk recursive
            fs.readdir(filename, function(err, files) {
                for(var i = 0; i < files.length; i++) {
                    walk(filename + '/' + files[i], callback);
                }
            });
        }
    });
}

fs.stat checks stats (e.g. type or size) and passes it to callback function. If given filename is actually directory, we iterate through all files and recursively call function itself. If filename is JavaScript file, we pass it to callback function (in out case it will be validate).

Now we have all needed functions. What remains is call walk function on current directory (stored in predefined __directory variable) and use fs.watchFile function which watch for changes in particular file:

walk(__dirname, function(filename) {
    // Check each file at the beginning
    validate(filename, status);

    // Watch every JS file for changes
    fs.watchFile(filename, function(curr, prev) {
        if(curr !== prev) {
            validate(filename, status_and_growl);
        }
    });
});

There are two arguments in watchFile’s callback function: current and previous modification time. Why curr !== prev? This is a documentation bug, watchFile also triggers when a file is accessed, not just when it’s changed. We need to test modification time.

That’s it. Run code with “node grolint.js” and enjoy. You may optionally add grolint directory to PATH for further use.

You may find whole code here.


Add to Del.cio.us

RSS Feed

Add to Technorati Favorites

Stumble It!


Digg It!

        www.sajithmr.com

  • You can replace __dirname with process.cwd() to get current working directory, and run it like:

    Host:directory/you/want/to/test $ node path/to/grolint.js

    Or make an alias alias grolint="node path/to/grolint.js" and run it like:

    Host:directory/you/want/to/test $ grolint
  • Robak Naziemny
    __dirname seems to point to grolint.js directory, so it's impossible to run tests on other directories.
  • Yes It rocks! The article is clear and immediate (just a very little typo on html: require('growl') instead of require('./growl')).
    Thank you very much, you have resolved me a big problem.
  • Datalion
    Hello,

    Yeah, verynice tools. I want to minify my js script, but it's prefered to use optizer such as jslint befor minifiing it. So I use this type on my project.

    But, the validation process is not complete.
    I’m means that I didn’t find any way to add some libraries to check fr optimization.
    I’m using prototypejs on all my scripts so execute jslint on all script, so I have plenty of errors :
    some are due to my fault
    some are due to ‘inimplemented function’ (for example, $ is a prototypejs function) so not implemented)
  • Thanks for this excellent post. I was able to get this up and running and part of my validation process in a deploy script very quickly. One note: consider re-writing this function slightly to make the output more meaningful for single run validations:

    function validate(filename, callback) {
    fs.readFile(filename, 'utf8', function(err, file) {
    sys.print('Checking ' + filename.replace(__dirname + '/', '') + '... ');
    callback(jslint.check(file.replace(/^\#\!.*/, '')));
    });
    }

    Also, be aware that jslint doesn't like validating itself :P it catches two errors in the current edition, dated 2010-12-23.
  • I've used your recursive walk function in my project.
    Thanks!
blog comments powered by Disqus