Friday, July 3, 2009

JSLint

One of the most frustrating things about writing in JavaScript has to be the general lack of help catching syntax errors. This is closely followed by the lack of ability to check for semantic errors. Due to JavaScript's late binding nature, and its history of being a component of the browser, these kinds of tools are only now (and slowly) appearing.

Some time ago I discovered jslint. Like many such tools, the only way to use it was to paste your code into the textarea of a web page and then click a button to get the report. While this works and does find your problems, it is not ideal for continuous use and is tedious to use for resolving more than a handful of reported issues. What I wanted was a JavaScript "compiler": something that could be integrated into the normal development cycle to find errors as I write code. Some time back I incorporated jsmin into the ZJS build process as an Ant task. Since the code for jsmin was written in JavaScript, I used a <scriptdef> to wrap the code in the necessary boilerplate.

As I wrote last time, I have recently extended <jsmin> to produce <jsbuild>. Where <jsmin> is purely fileset based, <jsbuild> actually reads the files to determine dependencies. Since I was already in the code, I figured I might as well dig in and include jslint as well. The code for jslint (5000+ lines) is a tad larger than jsmin (300+ lines)! And it had a particular philosophy on code style that I couldn't tweak via its provided options. You see, jslint likes this:


if (x == 4) {
doSomething();
}


But hates this:


if (x == 4)
doSomething();


This is clearly documented (see "Required Blocks") and when I asked Mr. Crockford if he would consider adding an option to not generate warnings for this style, he replied with a rather blunt "Nope.". Given the plethora of options (30 or more) in jslint, I was taken aback by his refusal to accommodate such a simple aesthetic difference of opinion. He's entitled, of course, and I cannot exactly complain since he is giving away the code for jslint. So I added the "reqblocks" option to his code as I incorporated it into ZJS.

It turns out that it was an utterly trivial insertion of a single "if" statement - the hard part, as you might imagine, was figuring out where to place it!

So now with the guts of <jslint> ready to go, I added a bit more logic to <jsbuild> to be able to invoke a nested sequential task whenever it decided to generate its output. The build script now looks like this:


<jsbuild out="${build.dir}/@{tag}/src/zjs/zjs-all.js"
level="@{jsmin}" verbosity="quiet"
packages="zjs=${src.dir}/zjs" modules="zjs.**">
<comment>${jscomment}</comment>
<onbuild>
<jslint src="${build.dir}/@{tag}/src/zjs/zjs-all.js"
report="${build.dir}/@{tag}/zjs-all-lint.html"
options="reqblocks=false,strict=false,onevar=false,undef=true,browser=true,forin=true"
console="true" globals="false" if="'debug' == '@{tag}'"
/>
</onbuild>
</jsbuild>


I experimented with <jslint> all by itself against each individual file:


<jslint options="reqblocks=false,strict=false,onevar=false,undef=true,browser=true,forin=true"
console="true" globals="false">
<fileset dir="${src.dir}/zjs">
<include name="*.js"/>
</fileset>
</jslint>


The <jslint> task supports the nested <fileset> or the "src" attribute, but what I found was that jslint gets cranky when dealing with files that depend on each other. It complains about any globals defined in file 1 and used in file 2. This is because I had to invoke jslint anew for each file for its reports to have the proper line numbers.

Things worked much better when jslint was invoked against the output file from <jsbuild> since it had included (almost) all dependencies. While there may still be gaps (due to excluded modules or packages), the result was much closer to what I wanted. The last detail I had to sort out was how to streamline dealing the numerous reports spewing from jslint. By default, the jslint report looked like this:

Problem at line 1234 character 17: Missing semicolon.
return x

The "problem" with this format was that the NetBeans output window just viewed it as dumb text. I wanted to be able to click on the output and jump to the offending code. I also did not want to write a NetBeans plug-in to do the necessary hyperlink magic!

And it turns out I didn't have to. It dawned on that tools like javac and Ant were unaware of NetBeans and its output window, yet their output was getting linked to the source. I figured there must be some recognizer in NetBeans that translates certain output patterns into hyperlinks. To test this out, I remembered that the output of javac for the same diagnostic would have looked like this:

/foo/inputfile.js:1234: Missing semicolon.
return x
^

With a little HTML stripping (the jslint report is laden with HTML) and some regex juju, I translated the jslint output into the javac format - and bingo! Links in my output window! Granted they go to my generate zjs-all.js file, but that's good enough for now! Well, that and the "if" attribute on jslint so I can run it only on the debug build - jslint also doesn't like the output of jsmin as its input. Go figure.

Future: As the dust settled, I realized what is really called for in <jslint>: a hybridization with <jsbuild>. If I could express the same package paths and scan for dependencies, I could squeeze the last of the noise out. I could produce a temporary buffer that contained all the dependent files for each input file (even those I would normally filter out in my <jsbuild>) and pass that to jslint. I could then scrub all the messages from jslint that did not apply to the lines coming from the target input file and rebase the line numbers as well. With a little caching of these files and dependencies to speed things up, I could see getting a convenient and super accurate result pointing at the proper source files.

This would easily take 4-8 hours or more and I'm just not feeling like it today. I'll just sit happy with my now jslint clean build of ZJS!

No comments:

Post a Comment