Because JavaScript isn’t compiled before being deployed, web developers don’t have the extra compilation step to identify errors. JavaScript code validators partly fill this void by performing static analysis on your JavaScript code. You were introduced to JSLint and JSHint earlier in this book. In this chapter, you’ll learn how to incorporate JSHint into your build system to automatically analyze and verify your JavaScript code.
JSHint is used in this chapter, because it comes with a prebuilt command-line file suitable for running with Rhino. JSLint, as of the time of this writing, doesn’t have a prebuilt command-line file, though some third parties have written utilities including command-line controls for JSLint.
The first step in validating files is to locate the files. There are two
different tasks for this purpose: <fileset> and <filelist>. The <fileset> task is used when you want to include a large number of files based
on a pattern. Specify the dir attribute
as the directory to look in and then includes with a filename pattern, such
as:
<fileset dir="./src" includes="**/*.js" />
This fileset includes all JavaScript files contained in the src directory. You can optionally specify some file patterns to exclude as well:
<fileset dir="./src" includes="**/*.js" excludes="**/*-test.js />
This fileset includes all JavaScript files except the ones that end with -test.js. This is a common practice for excluding unit test files that are contained in the same directories as the source files.
The <filelist> task works
similarly except that you must explicitly list the files to include. This
task is best used when you want to retrieve references to a specific set
of files. The <fileset> element
also expects a dir for the directory.
The files attribute contains a
comma-separated list of files. For example:
<filelist dir="./src" files="core/core.js" />
In practice, you’ll end up using <fileset> more frequently than <filelist>, as it’s far more likely that
you’ll be dealing with large groups of files rather than specific, named
files.
The <apply> task is used to execute command-line utilities on a collection of
files from within an Ant target. Because JSHint is written in JavaScript,
you’ll need to use the Rhino command-line JavaScript engine to execute it. Download
the latest Rhino release from http://www.mozilla.org/rhino
and place the js.jar file in your dependencies folder
(lib.dir).
To run JSHint on the command line, type the following:
java -jar js.jar jshint.js [options] [list of files]For example:
java -jar js.jar jshint.js curly=true,noempty-true core/core.js
The <apply> task allows you
to recreate command-line entires using the <arg> element. There are two ways to use
<arg>: by specifying the path attribute for file or directory references,
or by specifying the line attribute for
plain text. You can break down the command-line format into the following
pieces:
javaThe program to execute, specified by the executable attribute of <apply>
-jarAn option for java,
represented by the line
attribute of <arg>
jshint.jsThe main JSHint file, represented by the path attribute of <arg>
curly=true,noempty-trueThe options, represented by the line attribute of <arg>
core/core.jsThe file to validate, represented by the path attribute of <arg>
Given that, you can quickly create an Ant skeleton for this utility:
<target name="validate">
<apply executable="java">
<arg line="-jar"/>
<arg path="js.jar"/>
<arg path="jshint.js" />
<arg
line="curly=true,forin=true,latedef=true,noempty=true,undef=true,rhino=false"
/>
<arg path="core/core.js"/>
</apply>
</target>Although this approach works, you should really run JSHint on a
collection of JavaScript files rather than on a single one. The <apply> task makes this step easy by
allowing you to specify a <fileset> and then include it in a particular spot on the command line
by using the <srcfile> element. For example, the following target validates all
JavaScript files in the source directory:
<target name="validate">
<apply executable="java">
<fileset dir="${src.dir}" includes="**/*.js" />
<arg line="-jar"/>
<arg path="js.jar"/>
<arg path="jshint.js" />
<arg
line="curly=true,forin=true,latedef=true,noempty=true,undef=true,rhino=false"
/>
<srcfile/>
</apply>
</target>This Ant target now executes JSHint on every file specified by the
<fileset> element. You can run
the target via:
ant validate
Although the validate target
works well, there are some improvements that can be made.
First, the current version runs JSHint once on each file. So if there are
three JavaScript files, it’s the equivalent of running this:
java -jar js.jar jshint.js curly=true,noempty-true first.js java -jar js.jar jshint.js curly=true,noempty-true second.js java -jar js.jar jshint.js curly=true,noempty-true third.js
There is some overhead when running java, specifically the creation and destruction
of the Java Virtual Machine (JVM). This task adds a significant amount of
time to the target.
The JSHint command-line script actually accepts multiple files, so
it’s perfectly capable of using one JVM to check every file. Fortunately,
the <apply> task makes it easy to pass in all of the filenames. You just need
to set the parallel attribute to
"true":
<target name="validate">
<apply executable="java" parallel="true">
<fileset dir="${src.dir}" includes="**/*.js" />
<arg line="-jar"/>
<arg path="js.jar"/>
<arg path="jshint.js" />
<arg
line="curly=true,forin=true,latedef=true,noempty=true,undef=true,rhino=false"
/>
<srcfile/>
</apply>
</target>By adding one attribute, the validate target now passes all of the files onto
the command line at once (separated by spaces). Doing so allows JSHint to
read and validate all files with just one JVM and dramatically improves
the target speed.
The last addition to the validate
target is to have the build fail if validation fails. It’s usually a good
idea to add this step, because it ensures that developers are aware of the
problem. The current version of the validate target will output validation failures
to the command line, but any other task that follows will continue to
execute, potentially causing the failure messages to scroll off
screen.
You can force <apply> to
fail the build by setting the failonerror
property to "true":
<target name="validate">
<apply executable="java" failonerror="true" parallel="true">
<fileset dir="${src.dir}" includes="**/*.js" />
<arg line="-jar"/>
<arg path="js.jar"/>
<arg path="jshint.js" />
<arg
line="curly=true,forin=true,latedef=true,noempty=true,undef=true,rhino=false"
/>
<srcfile/>
</apply>
</target>This version of the validate
target will fail the build when an error occurs during execution of
<apply>. An error is any nonzero
exit code returned from the executable. Because JSHint returns 1 when a
validation error occurs, it will cause the build to fail.
The last step in improving the validate task is to externalize three pieces of
data:
The location of js.jar
The location of jshint.js
The command-line options
As these may change in the future, it’s best to represent them as properties and include them in your properties file or at the top of your build.xml file. Here’s an example properties file:
src.dir = ./src
lib.dir = ./lib
rhino = ${lib.dir}/js.jar
jshint = ${lib.dir}/jshint.js
jshint.options = curly=true,forin=true,latedef=true,noempty=true,undef=true\
,rhino=falseYou can then reference the properties from the validate target:
<target name="validate">
<apply executable="java" failonerror="true" parallel="true">
<fileset dir="${src.dir}" includes="**/*.js" />
<arg line="-jar"/>
<arg path="${rhino}"/>
<arg path="${jshint}" />
<arg line="${jshint.options}" />
<srcfile/>
</apply>
</target>With this change, you’re easily able to update the location of files and JSHint options without needing to go back into the target.
Buildr has a <jshint> task
that abstracts away a lot of the configuration necessary to
run JSHint. After importing the buildr.xml file as
mentioned in the previous chapter, use the <jshint> task by passing in any number
of <fileset>
elements:
<target name="validate">
<jshint>
<fileset dir="${src.dir}" includes="**/*.js" />
</jshint>
</target>You can also change the default options by using the options
attribute:
<target name="validate">
<jshint options="${jshint.options}">
<fileset dir="${src.dir}" includes="**/*.js" />
</jshint>
</target>The end result is exactly the same as using the target from the previous section.