/* vim: set expandtab ts=4 sw=4: */ /* * You may redistribute this program and/or modify it under the terms of * the GNU General Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ var Fs = require('fs'); var nThen = require('nthen'); var Semaphore = require('../tools/lib/Semaphore'); var Child = require('child_process'); var headerLines = [ '/* vim: set expandtab ts=4 sw=4: */', '/*', ' * You may redistribute this program and/or modify it under the terms of', ' * the GNU General Public License as published by the Free Software Foundation,', ' * either version 3 of the License, or (at your option) any later version.', ' *', ' * This program is distributed in the hope that it will be useful,', ' * but WITHOUT ANY WARRANTY; without even the implied warranty of', ' * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the', ' * GNU General Public License for more details.', ' *', ' * You should have received a copy of the GNU General Public License', ' * along with this program. If not, see .', ' */' ]; var parseFile = function (fileName, fileContent) { var output = ''; var parenthCount = 0; var functionParenthCount = 0; var expectBracket = 0; var name = fileName.replace(/^.*\//, '').replace(/\..*$/,''); var lines = fileContent.split('\n'); var lineInfo = ''; var ignore = false; var error = function(msg) { if (!ignore) { output += lineInfo + ' ' + msg + '\n'; } }; for (var lineNum = 0; lineNum < lines.length; lineNum++) { var line = lines[lineNum]; // switch to 1 indexing for human readability lineInfo = fileName + ":" + (lineNum+1); ignore = false; if (lineNum < headerLines.length) { var expectedLine = headerLines[lineNum]; if (line !== headerLines[lineNum]) { error("missing header\n" + expectedLine + "\n" + line); } } else if (/\.h$/.test(fileName) && lineNum < headerLines.length + 1) { if (line !== '#ifndef ' + name + "_H") { error("expected #ifndef " + name + "_H found " + line); } } else if (/\.h$/.test(fileName) && lineNum < headerLines.length + 2) { if (line !== '#define ' + name + "_H") { error("expected #define " + name + "_H found " + line); } } ignore = /CHECKFILES_IGNORE/.test(line); if (expectBracket === 1) { expectBracket = 0; if (!(/^[\s]*{/.test(line))) { error("expecting a { bracket " + line); } } // implementations.. TUNConfigurator_Linux contains TUNConfigurator_doStuff... var n = name.replace(/_.*/, ''); if ((/^\w+\s.*\(/).test(line)) { if (!(/^int main\(/.test(line) || / CJDNS_/.test(line) || line.indexOf(' '+n) > -1 || /^[ ]?static /.test(line) || /^typedef /.test(line))) { error("all globally visible functions must begin with the name of the file."); } } var matches; if (functionParenthCount === 0) { matches = /^\w+\s.*(\(.*)$/.exec(line); } if (functionParenthCount > 0 || matches) { var txt = (functionParenthCount > 0) ? line : matches[1]; functionParenthCount += (txt.match(/\(/g)||[]).length; functionParenthCount -= (txt.match(/\)/g)||[]).length; if (functionParenthCount === 0) { txt = txt.substring(txt.lastIndexOf(')') + 1); if (/{/.test(txt)) { error("please put the opening bracket on the next line."); } } } if (/[\w]*int[\w]*\s+\*+\w/.test(line) || /[\w]*struct\s+[\w]+\s+\*+\w/.test(line)) { error("int* blah; means int pointer named blah, int *blah; means int names splatblah"); } if (line.length > 100) { error("cjd's editor window is only 100 characters wide"); } if (/\.h$/.test(fileName) && fileName.indexOf('util/platform/libc/') === -1) { // If the name is CryptoAuth_pvt.h, it's ok to make a structure called CryptoAuth var nameRe = name.replace(/_pvt$/, '').replace(/_impl$/, ''); if (/^struct /.test(line) && line.indexOf('struct ' + nameRe) !== 0 && !(/\(/.test(line))) { error("all structures must begin with the name of the file."); } if (/#define /.test(line) && line.indexOf('#define ' + nameRe) === -1) { error("all defines must begin with the name of the file."); } } if (/\t/.test(line)) { error("tabs are not allowed, use 4 spaces."); } if (/\s$/.test(line)) { error("trailing whitespace."); } if (/[^A-Z](TODO|FIXME|XXX)[^A-Z]/.test(line)) { if (/[^A-Z](TODO|FIXME|XXX)[^\(A-Z]/.test(line)) { error("Please take responsibility for your TODO: eg: // TODO(cjd): make this work"); } else { console.log(lineInfo + ' ' + line.replace(/[ \/]*/, '')); } } if (/(if|for|while)\(/.test(line)) { error("If/for/while statements must be followed by whitespace."); } matches = null; if (parenthCount === 0) { matches = /[^\w#](if|for|while) (\(.*$)/.exec(line); } if (parenthCount > 0 || matches) { var txt1 = (parenthCount > 0) ? line : matches[2]; parenthCount += (txt1.match(/\(/g)||[]).length; parenthCount -= (txt1.match(/\)/g)||[]).length; if (parenthCount === 0) { txt1 = txt1.substring(txt1.lastIndexOf(')') + 1); // for (x; y; z) ; <-- ok // for (x; y; z) { <-- ok // for (x; y; z) { \ <-- ok (in preprocessor macro) // for (x; y; z) <-- ok but you better put a bracket on the next line // for (x; y; z) { j++; } <-- ok // for (x; y; z) j++; <-- BZZZZZZZZZZT if (!(/^[\s]*[;{].*$/.test(txt1)) && !(/^[\s]+{[\s]*\\$/).test(txt1)) { if (/[\s]*$/.test(txt1)) { expectBracket = 1; } else { error(parenthCount + ' ' + line); } } } } } return output; }; var checkFile = module.exports.checkFile = function (file, callback) { Fs.readFile(file, function (err, ret) { if (err) { throw err; } callback(parseFile(file, ret.toString())); }); }; var lint = module.exports.lint = function (fileName, fileContent, callback) { var out = parseFile(fileName, fileContent); callback(out, !!out); }; var checkFiles = module.exports.checkFiles = function (files, callback) { var sema = Semaphore.create(64); var errors = ''; nThen(function (waitFor) { files.forEach(function (file) { sema.take(waitFor(function (returnAfter) { checkFile(file, waitFor(returnAfter(function (err) { if (err) { errors += file + '\n' + err + '\n'; } }))); })); }); }).nThen(function (waitFor) { callback(errors); }); }; var checkDir = module.exports.checkDir = function (dir, runInFork, callback) { var gitIgnoreLines; if (runInFork) { var err = ''; var out = ''; var proc = Child.spawn(process.execPath, [__filename]); proc.stdout.on('data', function (data) { err += data.toString('utf8'); }); proc.stderr.on('data', function (data) { err += data.toString('utf8'); }); proc.on('close', function (ret) { out += err; var error; if (ret !== 0) { error = new Error(out); } callback(error, out); }); return; } var output = ''; nThen(function (waitFor) { Fs.readFile('.gitignore', waitFor(function (err, ret) { if (err) { throw err; } gitIgnoreLines = ret.toString('utf8').split('\n'); })); }).nThen(function (waitFor) { var addDir = function (dir) { Fs.readdir(dir, waitFor(function (err, files) { if (err) { throw err; } files.forEach(function (file) { Fs.stat(dir + '/' + file, waitFor(function (err, stat) { if (err) { throw err; } if (file === '.git') { } else if (file === 'contrib') { } else if (file === 'dependencies') { } else if (gitIgnoreLines.indexOf(file) !== -1) { } else { if (stat.isDirectory()) { addDir(dir + '/' + file); } else if (/.*\.[ch]$/.test(file)) { checkFile(dir + '/' + file, waitFor(function (ret) { output += ret; })); } } })); }); })); }; addDir(dir); }).nThen(function (waitFor) { callback(output); }); }; if (module.parent === null) { checkDir('.', false, function(output) { if (output !== '') { console.log(output); process.exit(1); } }); }