Thursday Jul 30, 2009

Integrating JSLint more tightly into NetBeans

Updated on August 3rd, 2009, to reflect jslint4java release 1.3.1

In my previous blog entry, Netbeans, JavaScript, Ant, Hudson, and JSLint: Oh my!, I described an approach for integrating JSLint into my NetBeans project.  While this approach worked, I still had to navigate to each JavaScript issue manually.  This is a huge pain, something that I haven't done since my earliest days as a developer, before I learned about Emacs' next error functionality.  NetBeans also has a really nice "Next Error" function that automatically navigates the user to the next error detected by the compiler.  Unfortunately, NetBeans didn't recognize the native output of JSLint.   This got me thinking about building a custom wrapper around JSLint so I can transform the output to whatever NetBeans would be happy with.

At roughly the same time, Dominic Mitchell posted a comment in response to my blog entry, pointing me towards jslint4java, which not only wraps JSLint and transforms its output, but it also provides a nice JSLint task interface to Ant.  This looked very promising, so I gave the just recently released jslint4java version 1.3 a whirl, but unfortunately, NetBeans did not recognize the output as errors.

Netbeans not recognizing output as an error

After some experimenting, I learned that NetBeans expects compiler errors to have exactly the same format as javac error messages.  As an example, version 1.3 of jslint4java outputs:

utilities.js:145:22:Use '===' to compare with 'undefined'

Whereas NetBeans expects the following output:

/Users/ashamash/projects/omalley/src/trunk/CQS/web/js/utilities.js:145:22: Use '===' to compare with 'undefined'.

There are two subtle differences:

  • All filenames need to have the full path (e.g. /Users/ashamash/projects/omalley/src/trunk/CQS/web/js/utilities.js vs. just utilities.js)
  • There needs to be a space between the colon and the error description (e.g. 145:22: Use vs. 145:22:Use)

With these two changes, NetBeans is a lot happier:

NetBeans recognizing output as an error

I sent Dominic the code changes needed to implement this, and after a couple of rounds of collaboration, the current development version of jslint4java has these modifications in it, as well as release jslint4java release 1.3.1 (released July 31st, 2009).

To integrate this version into NetBeans builds, follow these steps:

  1. Download jslint4java release 1.3.1 from http://jslint4java.googlecode.com/files/jslint4java-1.3.1-dist.zip, unpack it somewhere, note the path to the jslint4java-1.3.1.jar file.
  2. Define the properties needed in nbproject/project.properties:
    #
    # cqs.build.jslint: true/false setting to determine whether to run the
    # jslint javascript analysis utility on the individual javascript
    # files.  Default is true for production purposes, but developers can
    # set it to false in the file private/private.properties.  It is
    # highly recommended that this be left to true.
    #
    # cqs.build.jslint.failonerror: this true/false setting determines
    # whether the build should fail or not if jslint detects any errors.
    # Currently set to false until we fix all the javascript code so it
    # passes jslint.
    #
    cqs.build.jslint=true
    cqs.build.jslint.failonerror=false
    #
    cqs.js.jslint.files=\\
           js/utilities.js,\\
           js/file2.js,\\
           js/file3.js
    
  3. Now, you are ready to integrate jslint4java into your project's build.xml file, this is the Ant task that we use.  In our project, we keep an explicit list of JavaScript files we want to lint, so we can separate them from 3rd party code.  Another approach, described in the jslint4java docs, is to analyze all the JavaScript files in a given directory.  You can do what works best for you. 
        <target name="doJSLint4Java">
            <echo level="info" message="doJSLintWithJSLint4Java: running on files ${cqs.js.jslint.files}...." />
            
            <property name="jslint4java.dir" value="../tools/jslint4java-1.3.1" />
            <property name="jslint4java.jar.file" value="${jslint4java.dir}/jslint4java-1.3.1.jar"/> 
            <available file="${jslint4java.jar.file}" property="jslint4java.jar.file.available" />
            <fail unless="jslint4java.jar.file.available" message="jslint4java Jar file not found - expected at ${jslint4java.jar.file}" />
    
            <taskdef name="jslint4java"
                     classname="com.googlecode.jslint4java.ant.JSLintTask"
                     classpath="${jslint4java.jar.file}" />
    
            <jslint4java haltOnFailure="${cqs.build.jslint.failonerror}">⁞
              <formatter type="plain" />
              <filelist id="jslint.filelist" dir="${basedir}/web" files="${cqs.js.jslint.files}"/>
            </jslint4java>
    
            <echo level="info" message="doJSLintWithJSLint4Java: finished running...." />
        </target>
    
    
  4. For our project, we use "The Good Parts" JSLint setting, and embed the appropriate settings as a comment at the top of our JavaScript files.  You can use the tool at http://jslint.com/ to help you figure out which settings are best for your project.

And that's it.  You should read about best practices with JSLint, etc.  Thanks to Dominic Mitchell for making this tool available!

If you want to build it from scratch, you can follow these steps:

  1. Download the current snapshot of jslint4java, use git:
    $ git clone git://github.com/happygiraffe/jslint4java.git
    Initialized empty Git repository in /Users/ashamash/projects/jslint4java/src/jslint4java/.git/
    remote: Counting objects: 2548, done.
    remote: Compressing objects: 100% (862/862), done.
    remote: Total 2548 (delta 1028), reused 2346 (delta 940)
    Receiving objects: 100% (2548/2548), 1021.89 KiB | 232 KiB/s, done.
    Resolving deltas: 100% (1028/1028), done.
    $ 
    
  2. To build it, use Maven:
    $ mvn -Pdist clean package
    [INFO] Scanning for projects...
    [INFO] Reactor build order: 
    [INFO]   jslint4java parent
    [INFO]   jslint4java
    [INFO]   jslint4java ant task
    [INFO]   jslint4java docs
    [INFO]   jslint4java distribution
    [INFO] ------------------------------------------------------------------------
    [INFO] Building jslint4java parent
    [INFO]    task-segment: [clean, package]
    [INFO] ------------------------------------------------------------------------
    [INFO] [clean:clean]
    [INFO] Deleting file set: /Users/ashamash/projects/jslint4java/src/jslint4java/target (included: [\*\*], excluded: [])
    [INFO] [site:attach-descriptor]
    [INFO] [javadoc:jar {execution: attach-javadocs}]
    [INFO] Not executing Javadoc as the project is not a Java classpath-capable package
    [INFO] Preparing javadoc:aggregate
    [INFO] ------------------------------------------------------------------------
    [INFO] Building jslint4java parent
    [INFO] ------------------------------------------------------------------------
    [WARNING] Removing: aggregate from forked lifecycle, to prevent recursive invocation.
    [INFO] No goals needed for project - skipping
    [INFO] ------------------------------------------------------------------------
    [INFO] Building jslint4java
    [INFO] ------------------------------------------------------------------------
    [INFO] No goals needed for project - skipping
    [INFO] ------------------------------------------------------------------------
    [INFO] Building jslint4java ant task
    [INFO] ------------------------------------------------------------------------
    [INFO] No goals needed for project - skipping
    [INFO] ------------------------------------------------------------------------
    [INFO] Building jslint4java docs
    [INFO] ------------------------------------------------------------------------
    [INFO] No goals needed for project - skipping
    [INFO] ------------------------------------------------------------------------
    [INFO] Building jslint4java distribution
    [INFO] ------------------------------------------------------------------------
    [INFO] No goals needed for project - skipping
    [WARNING] The dependency: com.googlecode.jslint4java:jslint4java-ant:jar:1.4-SNAPSHOT can't be resolved but has been found in the reactor.
    This dependency has been excluded from the plugin execution. You should rerun this mojo after executing mvn install.
    [INFO] [javadoc:aggregate {execution: aggregate-javadoc}]
    [WARNING] The dependency: [com.googlecode.jslint4java:jslint4java-ant:jar:1.4-SNAPSHOT] can't be resolved but has been found in the reactor (probably snapshots).
    This dependency has been excluded from the Javadoc classpath. You should rerun javadoc after executing mvn install.
    [WARNING] IGNORED to add some artifacts in the classpath. See above.
    [INFO] ------------------------------------------------------------------------
    [INFO] Building jslint4java
    [INFO]    task-segment: [clean, package]
    [INFO] ------------------------------------------------------------------------
    [INFO] [clean:clean]
    [INFO] Deleting file set: /Users/ashamash/projects/jslint4java/src/jslint4java/jslint4java/target (included: [\*\*], excluded: [])
    [INFO] [resources:resources]
    [INFO] Using 'UTF-8' encoding to copy filtered resources.
    [INFO] Copying 1 resource
    [INFO] [compiler:compile]
    [INFO] Compiling 6 source files to /Users/ashamash/projects/jslint4java/src/jslint4java/jslint4java/target/classes
    [INFO] [resources:testResources]
    [INFO] Using 'UTF-8' encoding to copy filtered resources.
    [INFO] skip non existing resourceDirectory /Users/ashamash/projects/jslint4java/src/jslint4java/jslint4java/src/test/resources
    [INFO] [compiler:testCompile]
    [INFO] Compiling 5 source files to /Users/ashamash/projects/jslint4java/src/jslint4java/jslint4java/target/test-classes
    [INFO] [surefire:test]
    [INFO] Surefire report directory: /Users/ashamash/projects/jslint4java/src/jslint4java/jslint4java/target/surefire-reports
    
    -------------------------------------------------------
     T E S T S
    -------------------------------------------------------
    Running com.googlecode.jslint4java.OptionTest
    Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.049 sec
    Running com.googlecode.jslint4java.IssueTest
    Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.122 sec
    Running com.googlecode.jslint4java.JSLintTest
    Tests run: 15, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 5.119 sec
    Running com.googlecode.jslint4java.OptionParserTest
    Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.005 sec
    Running com.googlecode.jslint4java.UtilTest
    Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.019 sec
    
    Results :
    
    Tests run: 33, Failures: 0, Errors: 0, Skipped: 0
    
    [INFO] [jar:jar]
    [INFO] Building jar: /Users/ashamash/projects/jslint4java/src/jslint4java/jslint4java/target/jslint4java-1.4-SNAPSHOT.jar
    [INFO] ------------------------------------------------------------------------
    [INFO] Building jslint4java ant task
    [INFO]    task-segment: [clean, package]
    [INFO] ------------------------------------------------------------------------
    [INFO] [clean:clean]
    [INFO] Deleting file set: /Users/ashamash/projects/jslint4java/src/jslint4java/jslint4java-ant/target (included: [\*\*], excluded: [])
    [INFO] [resources:resources]
    [INFO] Using 'UTF-8' encoding to copy filtered resources.
    [INFO] Copying 1 resource
    [INFO] [compiler:compile]
    [INFO] Compiling 5 source files to /Users/ashamash/projects/jslint4java/src/jslint4java/jslint4java-ant/target/classes
    [INFO] [resources:testResources]
    [INFO] Using 'UTF-8' encoding to copy filtered resources.
    [INFO] Copying 6 resources
    [INFO] [compiler:testCompile]
    [INFO] Compiling 1 source file to /Users/ashamash/projects/jslint4java/src/jslint4java/jslint4java-ant/target/test-classes
    [INFO] [surefire:test]
    [INFO] Surefire report directory: /Users/ashamash/projects/jslint4java/src/jslint4java/jslint4java-ant/target/surefire-reports
    
    -------------------------------------------------------
     T E S T S
    -------------------------------------------------------
    Running com.googlecode.jslint4java.ant.PlainResultFormatterTest
    Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.09 sec
    
    Results :
    
    Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
    
    [INFO] [antrun:run {execution: default}]
    [INFO] Executing tasks
    
    allTests:
    [au:antunit] Build File: /Users/ashamash/projects/jslint4java/src/jslint4java/jslint4java-ant/target/test-classes/antunit/tests.xml
    [au:antunit] Tests run: 7, Failures: 0, Errors: 0, Time elapsed: 3.573 sec
    [au:antunit] Target: testArbitraryResource took 0.836 sec
    [au:antunit] Target: testFailsOnError took 0.862 sec
    [au:antunit] Target: testNamesInOutputHavePath took 0.433 sec
    [au:antunit] Target: testNoResources took 0.331 sec
    [au:antunit] Target: testOptionParams took 0.378 sec
    [au:antunit] Target: testSuccess took 0.277 sec
    [au:antunit] Target: testIssue6 took 0.393 sec
    [INFO] Executed tasks
    [INFO] [jar:jar]
    [INFO] Building jar: /Users/ashamash/projects/jslint4java/src/jslint4java/jslint4java-ant/target/jslint4java-ant-1.4-SNAPSHOT.jar
    [INFO] [shade:shade {execution: make-shaded-jar}]
    [INFO] Including com.googlecode.jslint4java:jslint4java:jar:1.4-SNAPSHOT in the shaded jar.
    [INFO] Including rhino:js:jar:1.7R1 in the shaded jar.
    [INFO] Attaching shaded artifact.
    [INFO] ------------------------------------------------------------------------
    [INFO] Building jslint4java docs
    [INFO]    task-segment: [clean, package]
    [INFO] ------------------------------------------------------------------------
    [INFO] [clean:clean]
    [INFO] Deleting file set: /Users/ashamash/projects/jslint4java/src/jslint4java/jslint4java-docs/target (included: [\*\*], excluded: [])
    [INFO] [resources:resources {execution: filter-docs}]
    [INFO] Using 'UTF-8' encoding to copy filtered resources.
    [INFO] Copying 6 resources
    [INFO] [site:attach-descriptor]
    [INFO] [assembly:single {execution: zip}]
    [INFO] Reading assembly descriptor: /Users/ashamash/projects/jslint4java/src/jslint4java/jslint4java-docs/src/main/assembly/zip.xml
    [INFO] Building zip: /Users/ashamash/projects/jslint4java/src/jslint4java/jslint4java-docs/target/jslint4java-docs-1.4-SNAPSHOT.zip
    [INFO] ------------------------------------------------------------------------
    [INFO] Building jslint4java distribution
    [INFO]    task-segment: [clean, package]
    [INFO] ------------------------------------------------------------------------
    [INFO] [clean:clean]
    [INFO] Deleting file set: /Users/ashamash/projects/jslint4java/src/jslint4java/jslint4java-dist/target (included: [\*\*], excluded: [])
    [INFO] [site:attach-descriptor]
    [INFO] [assembly:single {execution: dist}]
    [INFO] Reading assembly descriptor: /Users/ashamash/projects/jslint4java/src/jslint4java/jslint4java-dist/src/main/assembly/dist.xml
    [INFO] Building zip: /Users/ashamash/projects/jslint4java/src/jslint4java/jslint4java-dist/target/jslint4java-1.4-SNAPSHOT-dist.zip
    [INFO] 
    [INFO] 
    [INFO] ------------------------------------------------------------------------
    [INFO] Reactor Summary:
    [INFO] ------------------------------------------------------------------------
    [INFO] jslint4java parent .................................... SUCCESS [10.958s]
    [INFO] jslint4java ........................................... SUCCESS [9.327s]
    [INFO] jslint4java ant task .................................. SUCCESS [6.435s]
    [INFO] jslint4java docs ...................................... SUCCESS [3.104s]
    [INFO] jslint4java distribution .............................. SUCCESS [1.791s]
    [INFO] ------------------------------------------------------------------------
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESSFUL
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 32 seconds
    [INFO] Finished at: Mon Aug 03 12:10:02 EDT 2009
    [INFO] Final Memory: 48M/81M
    [INFO] ------------------------------------------------------------------------
    
  3. Note the path to the jslint4java-ant/target/jslint4java-1.4-SNAPSHOT-shaded.jar file, this is the jar file you'll need to reference in the build.xml file.
    $ find `pwd` -name '\*.jar' -print
    /Users/ashamash/projects/jslint4java/src/jslint4java/jslint4java/target/jslint4java-1.4-SNAPSHOT.jar
    /Users/ashamash/projects/jslint4java/src/jslint4java/jslint4java-ant/target/jslint4java-ant-1.4-SNAPSHOT-shaded.jar
    /Users/ashamash/projects/jslint4java/src/jslint4java/jslint4java-ant/target/jslint4java-ant-1.4-SNAPSHOT.jar
    

During this exercise, I was exposed to two new (for me) technologies, git and Maven (I'm personally an svn and ant type of person).  I especially liked the Maven integration in NetBeans.  Both experiences were quite good, but that's a story for another day.

Friday Jun 19, 2009

Netbeans, JavaScript, Ant, Hudson, and JSLint: Oh my!

I agree with Douglas Crockford on two points he makes in his book:

  • ... JavaScript became the language of the Web by default, making its popularity almost completely independent of its qualities as a programming language ...
  • Most programming languages contain goo⁞d and bad parts, but JavaScript has more than its share of the bad, having been developed and released in a hurry before it could be refined.

The first point is becoming more debatable, as other RIA technologies (including Sun's JavaFX) become more prominent, but let's leave that aside for now.

As for the second point, there is a lot developers can do to make the experience with JavaScript a whole lot better.  Don't get me wrong, JavaScript can be amazing, but it can also be a nightmare.  Part of the nightmare is that the development environments and tools for JavaScript are not yet on par with other environments.  My current project involves writing a whole lot of JavaScript.  We've gone through the usual JavaScript cycles - we've been burned by both programming errors as well as deployment errors.

This blog entry describes how we've integrated JSLint into our Continuous Build/Integration environment, so we can better control the code that we write.  There are other techniques that we've implemented to improve our experience with JavaScript that I'll describe in future blogs:

  • Build time automated concatenation of all the JavaScript files into a single file that has a unique file name, so that it can be cached.  This of course implies automated munging of the .html/.jsp pages that source the JavaScript files...
  • Selenium - for automated testing of our Web UI - eases the fear of writing and refactoring our JavaScript code.

First, download the components that you'll need, and install them in a tools directory somewhere:

  1. Rhino - JavaScript for Java - Rhino is an open-source implementation of JavaScript written entirely in Java. It is typically embedded into Java applications to provide scripting to end users.
  2. JSLint itself - get this version which is prebundled to work with Rhino.

This blog assumes you already have a Netbeans project created.  Add the following properties to your nbproject/project.properties file.  The CQS prefix that I use is the name of our project, you should change it to match yours.  As for whether you want your build to fail based on whether JSLint passes or not, that's your call, but I highly recommend it, otherwise developers (myself included) will tend to ignore the output of JSLint.

#
# cqs.build.jslint: true/false setting to determine whether to run the
# jslint javascript analysis utility on the individual javascript
# files.  Default is true for production purposes, but developers can
# set it to false in the file private/private.properties.  It is
# highly recommended that this be left to true.
#
# cqs.build.jslint.failonerror: this true/false setting determines
# whether the build should fail or not if jslint detects any errors.
#
cqs.build.jslint=true
cqs.build.jslint.failonerror=true 

Next, define your list of JavaScript files you want to analyze. 

cqs.js.jslint.files=\\
       js/utilities.js,\\
       js/3rdparty.js,\\
       js/tooltipsprop.js,\\
       js/publish2.js,\\
       js/manageusers.js,\\
       js/login.js,\\
       js/detailhtmlformatter.js,\\
       js/staticsearch.js,\\
       js/searchonready.js,\\
       js/mylibrary.js

Another technique others use is to analyze all the .js files in a given directory, this approach is described in various blogs such as this one.  However, we maintain a list of JavaScript files that are required, since this is used elsewhere in our build scripts (e.g. the automated concatenation aspects of our build script use it).

Next, you'll be making a series of changes to your build.xml file.  First, some stuff that we need to define, since we use it later.  The targets use the propertyregex task defined in ant-contrib, so you'll need to define this task in order to use these targets, this needs to come before the target definitions.  I don't know how people used Ant before ant-contrib was made available:

<property name="tools.dir" value="${basedir}/../tools" />
<property name="ant.contrib.jar.file" value="${tools.dir}/ant/lib/ant-contrib-1.0b3.jar" />
<available file="${ant.contrib.jar.file}" property="ant.contrib.jar.file.available" />
<fail unless="ant.contrib.jar.file.available" message="Ant contrib library not installed" />
<taskdef name="propertyregex" classname="net.sf.antcontrib.property.RegexTask">
    <classpath>
        <fileset dir="${tools.dir}/ant/lib" />
    </classpath>
</taskdef> 

Modify the appropriate Netbeans build target to include a "jslint" target:

<target name="-pre-dist" depends="jslint" >
</target>

These are the actual Ant targets for implementing jslint:

<target name="jslint">
    <if>
        <istrue value="${cqs.build.jslint}"/>
        <then>
            <antcall target="doJSLint" inheritAll="true" inheritRefs="true" />
        </then>
        <else>
            <echo message="Not running JSLint - JSLint is disabled." />
        </else>
    </if>
</target> <!-- jslint -->

<target name="doJSLint">
    <echo level="info" message="JSLint: running...." />
    <property name="rhino.jar.file" value="${tools.dir}/rhino1_7R2/js.jar"/>
    <property name="jslint.js.file" value="${tools.dir}/jslint/jslint.js"/>

    <available file="${rhino.jar.file}" property="rhino.jar.file.available" />
    <fail unless="rhino.jar.file.available" message="Rhino Jar file not found - expected at ${rhino.jar.file}" />

    <available file="${jslint.js.file}" property="jslint.js.file.available" />
    <fail unless="jslint.js.file.available" message="jslint.js file not found - expected at - ${jslint.js.file}" />

    <propertyregex property="cqs.js.jslint.files.spacedelim" input="${cqs.js.jslint.files}" regexp="," replace=" "/>

    <echo level="info" message="JSLint: running on ${cqs.js.jslint.files.spacedelim}...." />
    <exec dir="${basedir}/web" executable="java" failonerror="${cqs.build.jslint.failonerror}">
        <arg line="-jar ${rhino.jar.file} ${jslint.js.file} ${cqs.js.jslint.files.spacedelim}"/>
    </exec>

    <echo level="info" message="JSLint: finished running...." />
</target> <!-- doJSLint -->
And that's it!  JSLint will now be run as part of the regular build.  Within Netbeans, you can just "deploy" the project, that will run it.  On the command line, "ant dist" will do the trick.

We use Hudson for continous build/continuous integration, so every time a source file is checked in, a full build, including a JSLint pass, is done.

About

Ari Shamash is the software engineering manager of the Sun Software Library engineering team at Sun Microsystems.

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today