Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Command-line example to find Boolean traps in function calls.

  • Loading branch information...
commit 4c607ec5bb9353825697de2d80505b6cd639ebb1 1 parent f3ee094
@ariya authored
Showing with 198 additions and 1 deletion.
  1. +25 −1 doc/index.html
  2. +173 −0 examples/findbooleantrap.js
View
26 doc/index.html
@@ -98,7 +98,31 @@ <h2 id="usage">Basic Usage</h2>
<h2 id="examples">Examples</h2>
-<p>To be written.</p>
+<h3 id="booleantrap">Find Possible Boolean Traps</h3>
+
+<p>The script <code>findbooleantrap.js</code> in the <code>examples/</code> subdirectory is using Esprima to detect some possible cases of Boolean trap, i.e. the use of Boolean literal which may lead to ambiguities and lack of readability. The script can be invoked from command-line with Node.js:</p>
+
+<pre class="prettyprint lang-bsh">
+node findbooleantrap.js /some/path
+</pre>
+
+It will search for all files (recursively) in the given path, try to parse each file, and then look for signs of Boolean traps:
+
+<ul>
+<li>Literal used with a non-setter function (assumption: setter starts with the &quot;set&quot; prefix):</li>
+<pre class="prettyprint lang-js">this.refresh(true);</pre>
+<li>Literal used with a function whose name may have a double-negative interpretation:</li>
+<pre class="prettyprint lang-js">item.setHidden(false);</pre>
+<li>Two different literals in a single function call:</li>
+<pre class="prettyprint lang-js">element.stop(true, false);</pre>
+<li>Multiple literals in a single function invocation:</li>
+<pre class="prettyprint lang-js">event.initKeyEvent("keypress", true, true, null, null,
+ false, false, false, false, 9, 0);</pre>
+<li>Ambiguous Boolean literal as the last argument:</li>
+<pre class="prettyprint lang-js">return getSomething(obj, false);</pre>
+</ul>
+
+For some more info, read also the blog post on <a href="http://ariya.ofilabs.com/2011/08/hall-of-api-shame-boolean-trap.html">Boolean trap</a>.
<h2 id="ast">Syntax Tree Format</h2>
View
173 examples/findbooleantrap.js
@@ -0,0 +1,173 @@
+// Usage: node findbooleantrap.js /path/to/some/directory
+// For more details, please read http://esprima.org/doc/#booleantrap.
+
+/*jslint node:true sloppy:true plusplus:true */
+
+var fs = require('fs'),
+ esprima = require('../esprima'),
+ dirname = process.argv[2],
+ doubleNegativeList = [];
+
+
+// Black-list of terms with double-negative meaning.
+doubleNegativeList = [
+ 'hidden',
+ 'caseinsensitive',
+ 'disabled'
+];
+
+
+// Executes visitor on the object and its children (recursively).
+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);
+ }
+ }
+ }
+}
+
+// http://stackoverflow.com/q/5827612/
+function walk(dir, done) {
+ var results = [];
+ fs.readdir(dir, function (err, list) {
+ if (err) {
+ return done(err);
+ }
+ var i = 0;
+ (function next() {
+ var file = list[i++];
+ if (!file) {
+ return done(null, results);
+ }
+ file = dir + '/' + file;
+ fs.stat(file, function (err, stat) {
+ if (stat && stat.isDirectory()) {
+ walk(file, function (err, res) {
+ results = results.concat(res);
+ next();
+ });
+ } else {
+ results.push(file);
+ next();
+ }
+ });
+ }());
+ });
+}
+
+walk(dirname, function (err, results) {
+ if (err) {
+ console.log('Error', err);
+ return;
+ }
+
+ results.forEach(function (filename) {
+ var shortname, first, content, syntax;
+
+ shortname = filename;
+ first = true;
+
+ if (shortname.substr(0, dirname.length) === dirname) {
+ shortname = shortname.substr(dirname.length + 1, shortname.length);
+ }
+
+ function getFunctionName(node) {
+ if (node.callee.type === 'Identifier') {
+ return node.callee.name;
+ }
+ if (node.callee.type === 'MemberExpression') {
+ return node.callee.property.name;
+ }
+ }
+
+ function report(node, problem) {
+ if (first === true) {
+ console.log(shortname + ': ');
+ first = false;
+ }
+ console.log(' Line', node.loc.start.line, 'in function',
+ getFunctionName(node) + ':', problem);
+ }
+
+ function checkSingleArgument(node) {
+ var args = node['arguments'],
+ functionName = getFunctionName(node);
+
+ if ((args.length !== 1) || (typeof args[0].value !== 'boolean')) {
+ return;
+ }
+
+ // Check if the method is a setter, i.e. starts with 'set',
+ // e.g. 'setEnabled(false)'.
+ if (functionName.substr(0, 3) !== 'set') {
+ report(node, 'Boolean literal with a non-setter function');
+ }
+
+ // Does it contain a term with double-negative meaning?
+ doubleNegativeList.forEach(function (term) {
+ if (functionName.toLowerCase().indexOf(term.toLowerCase()) >= 0) {
+ report(node, 'Boolean literal with confusing double-negative');
+ }
+ });
+ }
+
+ function checkMultipleArguments(node) {
+ var args = node['arguments'],
+ literalCount = 0;
+
+ args.forEach(function (arg) {
+ if (typeof arg.value === 'boolean') {
+ literalCount++;
+ }
+ });
+
+ // At least two arguments must be Boolean literals.
+ if (literalCount >= 2) {
+
+ // Check for two different Boolean literals in one call.
+ if (literalCount === 2 && args.length === 2) {
+ if (args[0].value !== args[1].value) {
+ report(node, 'Confusing true vs false');
+ return;
+ }
+ }
+
+ report(node, 'Multiple Boolean literals');
+ }
+ }
+
+ function checkLastArgument(node) {
+ var args = node['arguments'];
+
+ if (args.length < 2) {
+ return;
+ }
+
+ if (typeof args[args.length - 1].value === 'boolean') {
+ report(node, 'Ambiguous Boolean literal as the last argument');
+ }
+ }
+
+ try {
+ content = fs.readFileSync(filename, 'utf-8');
+ syntax = esprima.parse(content, { tolerant: true, loc: true });
+ traverse(syntax, function (node) {
+ if (node.type === 'CallExpression') {
+ checkSingleArgument(node);
+ checkLastArgument(node);
+ checkMultipleArguments(node);
+ }
+ });
+ } catch (e) {
+ }
+
+ });
+});
Please sign in to comment.
Something went wrong with that request. Please try again.