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