| // 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) { |
| } |
| |
| }); |
| }); |