.

Tags:

A bad API can lead to ambiguities and reduced readability. One very common thing I still encounter in various JavaScript frameworks is the so-called Boolean trap, i.e. the unwise use of Boolean argument(s) in a function call. Reviewing or reading code which gets trapped there is not fun. The earlier we can catch such as a bad behavior, the better our chance is to prevent it to become widespread.

Fortunately, with the help of a syntax parser, catching possible Boolean traps is not a monumental job. This blog post describes the strategy to do it. Here we are using Esprima, the blazing-fast standard-compliant ECMAScript parser which I started some time ago. In fact, this tool is now part of Esprima examples, check out examples/findbooleantrap.js (less than 200 lines). Invoke the script as follows:

node findbooleantrap.js /some/path

Every file in that specified directory will be located and parsed. If parsing does not fail, the syntax tree will be analyzed to find 5 (five) possible Boolean problems:

  • A non-setter function having the Boolean literal as its argument. It’s assumed that a setter function will start with set, e.g. setEnabled. An example which will be caught:
  • this.refresh(true);
  • Double-negative function name, e.g. setHidden. In this case setVisible will be less confusing.
  • item.setHidden(false);
  • "Can you make up your mind?" moment, such as jQuery stop:
  • element.stop(true, false);
  • A crazy stream of Boolean literals, as demonstrated by the DOM initKeyEvent:
  • event.initKeyEvent('keypress', true, true, null, null, false, false, false, false);
  • Useless last parameter which can’t explain anything (unless you read the documentation):
  • return getSomething(obj, false);

Since this is just a simple script, obviously you can tweak it to suit your need. For example, many projects do not use an explicit setter function which leads to code like enable(true) and thus the first check can produce a lot of noise. The double-negative warning is done by a cross-reference to a short blacklist, hence you may want to populate the blacklist with more entries.

The main gut of this script is a syntax tree traversal function with a visitor. The traversal can be carried out in many different ways, the script uses this simple approach:

function traverse(object, visitor) {
    var key, child;
 
    if (visitor.call(null, object) === false) {
        return;
    }
    for (key in object) {
        if (object.hasOwnProperty(key)) {
            child = object[key];
            if (typeof child === 'object' && child !== null) {
                traverse(child, visitor);
            }
        }
    }
}

which will be used like this:

traverse(syntax, function (node) {
    if (node.type === 'CallExpression') {
        // check node
    }
});

As an illustration, the code fragment to detect the fifth condition mentioned in the list before is as follows:

function checkLastArgument(node) {
    var args = node['arguments'];
    if (typeof args[args.length - 1].value === 'boolean')
        report(node, 'Ambiguous Boolean literal as the last argument');
}

Rather straightforward, isn’t it? Similar logic exists to handle other four suspicious usages of Boolean literals. Because Esprima can give location information for every syntax node, it’s rather trivial to display the line number where the problem is found. Feel free to explore the code and possibly even improve it.

So what are you waiting for? Install Node.js if you don’t have it yet, grab Esprima from its Git repo, and off you go. Beside fixing the existing API, you may also want to consider running the checks as part of your project pre-commit hook. Enjoy and haveFun(true)!

  • Peter StJ

    When does the visitor parameter actually return false (in the traverse function)? I don’t see how this could happen ever if it never returns any value and yet you have the if statement…can you please explain?

    • http://twitter.com/ariyahidayat Ariya Hidayat

      You’re right, it’s not used in this case. The reason the if statement is there is because the traversal function is copied from some other examples.

  • http://twitter.com/pahen Patrik Henningsson

    Smooth! Found a bunch of weird stuff in a project of mine. Thanks! :)