Enforcing Strict JavaScript

I have recently discovered JSLINT (www.jslint.com).  This is lint processing for JavaScript.  For the under-40 crowd, lint is a unix utility we used back in the days when C compilers were not strict.  Used to be that you could do things in C that were blatantly wrong — and detectable — but the C compilers and linkers happily gave you enough rope to hang yourself.  lint analyzed your C source code, reported errors, and encouraged you to make your code more strict. 

Now we have JavaScript.  A wonderful utilitarian language that also has a ready supply of hanging rope.  A sizeable share of our JavaScript programming errors happen because we inadvertently make use of the laxness that is part of JavaScript.

This is where JSLINT comes in.  JSLINT is a utility (written in JavaScript) that will analyze your script and report on errors it finds.  When you visit www.jslint.com you can paste your code into the HTML page, and you will get a report of errors and other information about constructs in your script.  JSLINT will continue to complain about your code until you have reduced it to a strict subset that can be used reliably.

I ran some of the code I’ve written recently through JSLINT and I was… embarrassed.  I was determined to make all my code strict.  (Hard to find a better motivator than embarrassment.)  The problem is that copy/pasting code from Designer to www.jslint.com got pretty tedious.  Happily, the author of JSLINT (Douglas Crockford) has made the source code shareable.  I took the source for JSLINT and incorporated it into LintCheck.pdf.  Clicking the button on the form will prompt you for a PDF or XDP file.  It will extract all the script, run JSLINT and report on what it finds.  Here is a sample form with some coding errors and the resulting report.  (You need Acrobat to run LintCheck.pdf.)

One of the things I quickly discovered is that JSLINT reports many issues that are not necessarily related to language/grammar strictness, but rather are more a matter of enforcing good programming style.  Good programming style in turn reduces the likelihood of bugs.  eg. At first I was annoyed when JSLINT insisted that all my control blocks be enclosed in braces, but I do understand how that coding practise makes source code more predictable — especially when you consider the potential ambiguity of an expression such as: "if (..) if (..) else (..)"

Some more details on using LintCheck.pdf:

Global References

In the script output, global references are highlighted in blue.  If you see something highlighted in blue that is not a reference to a form object, then it is likely an undeclared variable. If you want to turn off this highlighting, insert a special comment in your code.  e.g. /*global Price Quantity */ will turn off highlighting on script references to the Price and Quantity fields.  commonly referenced globals such as "xfa", "console", "app" etc. have already been turned off.

Options

If you look at www.jslint.com you will see a number of options that make JSLINT more or less strict.  Some of those options are relevant only to JavaScript embedded in HTML pages.  The more generic options are included in LintCheck.pdf.  When I check my code, I turn on the options for "Strict white space", "Disallow undefined variables", "Disallow == and !=" and "Disallow bitwise operators".

Embed JSLINT directives in your code

If you want specific options to be enforced selectively in your code, you can control them with comments.  For example, to turn on white space checking, insert this comment: 

/*jslint white: true, indent: 4 */

To help you build up this string, there is a field on the form that shows what the comment would look like for the chosen options.

Conflicts with XFA Properties and Methods

Script writers who use variable names that conflict with XFA properties or methods will run afoul of the "naked field processing" (as explained here).  Since JSLINT provides a list of variables and global references, LintCheck.pdf will report cases where users have a conflict in their script.

Tabs in Source Code

JSLINT is unhappy with combinations of spaces and tabs.  If your line of code starts with 2 spaces followed by a tab, most code editors will bring you to column 4.  JSLINT assumes this to be column 6.  It assumes that a tab character always adds 4 spaces.  If you mix tabs and spaces you will get complaints from JSLINT when strict white space is on.  You will also find that the character position of errors is reported incorrectly.

FormCalc

Lint processing does not apply to FormCalc.  But since LintCheck.pdf produces a script report, it seemed best to allow FormCalc scripts to be included in the output.  There is an option to control whether FormCalc is included or not.

No-op expressions

Suppose you have a calculation that looks like:

form1.#subform[0].check[6].result::calculate – (JavaScript, client)
Price.rawValue * Quantity.rawValue;

JSLINT will generate an error: "Expected an assignment or function call and instead saw an expression.".

However, this is a valid construct in our forms.  Calculations and validations are the result of the last expression evaluated in a script.  The version of JSLINT embedded in LintCheck.pdf adds a new option to control this behaviour.  To turn this check on (e.g. for script objects) add this directive in your script: /*jslint nonoop: true */

One reason to turn this check on is because for forms to work in Form Guides, calculations must be expressed as an assignment. e.g.

form1.#subform[0].check[6].result::calculate – (JavaScript, client)
this.rawValue = Price.rawValue * Quantity.rawValue;

Track Members

One common JavaScript coding error is to misspell a member name.  Look at this sample script:

var fruits = ["apple",
              "orange",
              "peach",
              "plum"];
// truncate the array
fruits.lenght = 2;

The intention was to truncate the array to 2 — but it doesn’t because I misspelled the length property.  The script runs without a runtime error — because JavaScript assumed we wanted to populate a new property called "lenght".  The solution here is to add a comment at the top of your script listing all the members you expect to see:

/*members length rawValue */

Now JSLINT will complain about the offending line because the "lenght" property is not in the l
ist of sanctioned members.

Performance

Forms with lots of script will generate very large reports.  Very large reports take a long time to generate.  Running LintCheck.pdf on itself takes around 15 seconds to complete on my desktop system (and generates 133 pages).

E4X Handling

Unfortunately, the parser in JSLINT is not able to handle E4X expressions.  Since LintCheck.pdf makes use of E4X to handle XDP files, I needed a workaround to avoid having JSLINT stop when it encountered my E4X expressions.  Turns out there are JSLINT friendly variations.  The script expression to find the root subform and script elements of an XDP originally looked like:

var sRootSubform = xXDP.*::template.*::subform[0];
var vScripts = vRootSubform..*::script;

The alternate expressions looked like:

var vRootSubform = xXDP.elements(
                                  QName(null,"template")
                                 )[0].elements(
                                  QName(null,"subform")
                                 )[0];
var vScripts = vRootSubform.descendants(QName(null,"script"));