If you've ever written a JS-based webapp in a modern browser and then went back to test it on older IE versions, then you've undoubtedly experienced the pain of hitting one IE quirk after another. One of those many quirks you might have encountered is the weirdness in how IE 7 and below - a.k.a. quirks mode - handles trailing commas in array literals and object literals.

Trailing commas are popular with some programmers in other languages, because if you had a long array or object literal across multiple lines, for example

var names = [ 'bobby' , 'catherine' , 'danny' , 'eric' , ];

Adding a new entry doesn't require touching the last existing entry. However, in Javascript, trailing commas are mostly avoided because of the quirks.

Trailing Comma in Array Literals

Given an array literal with a trailing comma

var arr = [ 1 , 2 , 3 , ];

In standards compliant browsers, this is an array with 3 elements. But in quirks mode, this is an array with 4 elements.

Trailing Comma in Object Literals

Given an object literal with a trailing comma

var obj = { foo : 'foo' , bar : 'bar' , };

Standards compliant browsers are perfectly happy with this. While in quirks mode, IE blows up with a cryptic syntax error like SCRIPT1028: Expected identifier, string or number . What makes matters worse is that usually older IEs don't give an accurate line number for where the problem was, and so you would usually have to resort to the divid-and-conquer method, a.k.a the binary search method, a.k.a. comment out a half of your code repeatedly until you narrow it down to the place of the error.

Solving It

If you have been following along, you might have guessed that we are going solve this using static analysis. While another Javascript parser might have thrown away that trailing comma because it contributes nothing to the semantic meaning of the program, Esprima, with the raw and range options, will allow you the ability to recreate the verbatim source of the original program. Falafel makes this easy by providing a source() method which returns the verbatim source for any given AST node.

Let's get falafeling!

Fixing Arrays

First, we'll just detect array literal expressions

var falafel = require ( 'falafel' ); var fs = require ( 'fs' ); var code = fs . readFileSync ( 'sample.js' ) + '' ; code = falafel ( code , function ( node ){ if ( node . type === 'ArrayExpression' ){ processArrayExpression ( node ); } });

What should processArrayExpression() do? As the first step, let's just print the expression and see that we can get the original source back.

function processArrayExpression ( node ){ console . log ( 'Array expression:' , node . source ()) }

Make a sample.js containing

var arr = [1, 2, 3,]; var arr2 = ['a', 'b', 'c'];

Then run the falafel program and you'd get

Array expression: [1, 2, 3,] Array expression: ['a', 'b', 'c']

So, as you can see, we can get the original source back. Now, we can detect the existence of the trailing comma via some regex hacking - ohhhh, look away now!

function processArrayExpression ( node ){ var src = node . source (); if ( src . match ( /,\s*]$/ )){ console . log ( 'Array has trailing comma:' , src ); } }

Running the program now you should see that we've successfully filtered out the array expression without a trailing comma

Array has trailing comma: [1, 2, 3,]

The next step is to fix it. Of course we'll use falafel's handy update() method. Regex to the rescue again! Don't look!

node . update ( src . replace ( /,(\s*)]$/ , '$1]' ));

Finally, we'll print out the rewritten code to the console, and the full source is

var falafel = require ( 'falafel' ); var fs = require ( 'fs' ); var code = fs . readFileSync ( 'sample.js' ) + '' ; code = falafel ( code , function ( node ){ if ( node . type === 'ArrayExpression' ){ processArrayExpression ( node ); } }); console . log ( code ); function processArrayExpression ( node ){ var src = node . source (); if ( src . match ( /,\s*]$/ )){ node . update ( src . replace ( /,(\s*)]$/ , '$1]' )); } }

Run this and we see the trailing comma fixed!

var arr = [1, 2, 3]; var arr2 = ['a', 'b', 'c'];

Wonderful.

Homework - Fixing Objects

Your homework this time is to fix trailing commas for object literal as well. Have fun, programmer!