Javascript Refactoring without an IDE

There are a lot of questions that pop up when you're changing an entry in your style guide. If you're editing a file which hasn't been updated, is this pull request the one to update it in? Is that a separate PR? Should I just keep with the old style for now, but use it for new files? Without a clear cross-over point, you may end up with multiple competing standards, all of which are a snapshot of what the styleguide looked like when the code was written.

Using tools from the node community, we are able to make that cross-over point exceedingly clear. Using tools such as falafel (or the higher level rewrite-js or tern), we can write refactorings for our code programmatically. These tools seem particularly well suited for those programmers who aren't interested in booting up an IDE, but would like some of the refactoring primitives they provide.

The Code

#!/usr/bin/env node
/*
 * We have trailing underscores on variables to
 * indicate private. That's silly. Preceeding
 * underscores are the only obvious choice.
 * 
 *  var foo = function () {
 *    this.bar_ = '9';
 *    _.bindAll(this, 'foo_');
 *  }
 *
 * becomes
 *
 * var foo = function () {
 *   this._bar = '9'
 *   _.bindAll(this, '_foo');
 * }
 */
var falafel = require('falafel');
var fs = require('fs')
var filename = process.argv[2]
var source = fs.readFileSync(filename)

var regexp = /(.*[a-zA-Z0-9])_$/
var trailingUnderscore = function (str) {
  return regexp.exec(str) !== null;
}

var invertUnderscore = function (str) {
  var id = regexp.exec(str)[1];
  return '_' + id;
}

var output = falafel(""+source, function (node) {
  if (node.type === 'Identifier' && 
      trailingUnderscore(node.name)) {
    node.update(invertUnderscore(node.name));
  }
  if (node.type === 'String' && 
      trailingUnderscore(node)) {
    node.update(invertUnderscore(node));
  }
});

console.log("" + output);

As the documentation at the top of the file says, this code will transform variables that end in a single underscore with one that is preceeded with an underscore. For those curious, the original convention existed to indicate "private" and was a convention of Google's closure.

The code starts by requiring falafel, the library responsible for transforming the code. We read in the file, pulling the filename from the argument to this script. Looping over every node in the AST (that is, for every "word" in the source file) check if it's an identifier or a string. Identifier in this case is more or less a variable name or object key.

Once we find each of these things, update it's source to be the underscore-changed version. "Updating the source" in this case, is a little weird because the source of an identifier is just the identifier's word. This would probably be a bit different if we were working on functions.

You can have this operate across your code base with the addition of a tiny shell script which can be invoked as ./my_script path/to/js/dir.

#!/bin/bash
for i in `find $@ -name '*.js'`; do
    # Output result to temporary file
    ./underscore_fix $i > $i.new
    # Make a backup of the old one
    mv $i $i.bak
    # Move the new one into place
    mv $i.new $i
done

By automating the changes to your style guide, you get a clear cut-over point for using the new style. Not only that, you also get a reproducable method for transforming code (such as the stuff in pull requests already) into compliant code.