Codestyle.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. /* vim: set expandtab ts=4 sw=4: */
  2. /*
  3. * You may redistribute this program and/or modify it under the terms of
  4. * the GNU General Public License as published by the Free Software Foundation,
  5. * either version 3 of the License, or (at your option) any later version.
  6. *
  7. * This program is distributed in the hope that it will be useful,
  8. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. * GNU General Public License for more details.
  11. *
  12. * You should have received a copy of the GNU General Public License
  13. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  14. */
  15. 'use strict';
  16. var Fs = require('fs');
  17. var nThen = require('nthen');
  18. var Semaphore = require('saferphore');
  19. var Child = require('child_process');
  20. var headerLines = [
  21. '/* vim: set expandtab ts=4 sw=4: */',
  22. '/*',
  23. ' * You may redistribute this program and/or modify it under the terms of',
  24. ' * the GNU General Public License as published by the Free Software Foundation,',
  25. ' * either version 3 of the License, or (at your option) any later version.',
  26. ' *',
  27. ' * This program is distributed in the hope that it will be useful,',
  28. ' * but WITHOUT ANY WARRANTY; without even the implied warranty of',
  29. ' * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the',
  30. ' * GNU General Public License for more details.',
  31. ' *',
  32. ' * You should have received a copy of the GNU General Public License',
  33. ' * along with this program. If not, see <https://www.gnu.org/licenses/>.',
  34. ' */'
  35. ];
  36. var parseFile = function (fileName, fileContent) {
  37. var output = '';
  38. var parenthCount = 0;
  39. var functionParenthCount = 0;
  40. var expectBracket = 0;
  41. var name = fileName.replace(/^.*\//, '').replace(/\..*$/,'');
  42. var lines = fileContent.split('\n');
  43. var lineInfo = '';
  44. var ignore = false;
  45. var error = function(msg) {
  46. if (!ignore) {
  47. output += lineInfo + ' ' + msg + '\n';
  48. }
  49. };
  50. for (var lineNum = 0; lineNum < lines.length; lineNum++) {
  51. var line = lines[lineNum];
  52. // switch to 1 indexing for human readability
  53. lineInfo = fileName + ":" + (lineNum+1);
  54. ignore = false;
  55. if (lineNum < headerLines.length) {
  56. var expectedLine = headerLines[lineNum];
  57. if (line !== headerLines[lineNum]) {
  58. error("missing header\n" + expectedLine + "\n" + line);
  59. }
  60. } else if (/\.h$/.test(fileName) && lineNum < headerLines.length + 1) {
  61. if (line !== '#ifndef ' + name + "_H") {
  62. error("expected #ifndef " + name + "_H found " + line);
  63. }
  64. } else if (/\.h$/.test(fileName) && lineNum < headerLines.length + 2) {
  65. if (line !== '#define ' + name + "_H") {
  66. error("expected #define " + name + "_H found " + line);
  67. }
  68. }
  69. ignore = /CHECKFILES_IGNORE/.test(line);
  70. if (expectBracket === 1) {
  71. expectBracket = 0;
  72. if (!(/^[\s]*{/.test(line))) {
  73. error("expecting a { bracket " + line);
  74. }
  75. }
  76. // implementations.. TUNConfigurator_Linux contains TUNConfigurator_doStuff...
  77. var n = name.replace(/_.*/, '');
  78. if ((/^\w+\s.*\(/).test(line)) {
  79. if (!(/^int main\(/.test(line)
  80. || / CJDNS_/.test(line)
  81. || line.indexOf(' '+n) > -1
  82. || /^[ ]?static /.test(line)
  83. || /^typedef /.test(line)))
  84. {
  85. error("all globally visible functions must begin with the name of the file.");
  86. }
  87. }
  88. var matches;
  89. if (functionParenthCount === 0) {
  90. matches = /^\w+\s.*(\(.*)$/.exec(line);
  91. }
  92. if (functionParenthCount > 0 || matches) {
  93. var txt = (functionParenthCount > 0) ? line : matches[1];
  94. functionParenthCount += (txt.match(/\(/g)||[]).length;
  95. functionParenthCount -= (txt.match(/\)/g)||[]).length;
  96. if (functionParenthCount === 0) {
  97. txt = txt.substring(txt.lastIndexOf(')') + 1);
  98. if (/{/.test(txt)) {
  99. error("please put the opening bracket on the next line.");
  100. }
  101. }
  102. }
  103. if (/[\w]*int[\w]*\s+\*+\w/.test(line) || /[\w]*struct\s+[\w]+\s+\*+\w/.test(line)) {
  104. error("int* blah; means int pointer named blah, int *blah; means int names splatblah");
  105. }
  106. if (line.length > 100) {
  107. error("cjd's editor window is only 100 characters wide");
  108. }
  109. if (/\.h$/.test(fileName) && fileName.indexOf('util/platform/libc/') === -1) {
  110. // If the name is CryptoAuth_pvt.h, it's ok to make a structure called CryptoAuth
  111. var nameRe = name.replace(/_pvt$/, '').replace(/_impl$/, '');
  112. if (/^struct /.test(line) && line.indexOf('struct ' + nameRe) !== 0 && !(/\(/.test(line))) {
  113. error("all structures must begin with the name of the file.");
  114. }
  115. if (/#define /.test(line) && line.indexOf('#define ' + nameRe) === -1) {
  116. error("all defines must begin with the name of the file.");
  117. }
  118. }
  119. if (/\t/.test(line)) {
  120. error("tabs are not allowed, use 4 spaces.");
  121. }
  122. if (/\s$/.test(line)) {
  123. error("trailing whitespace.");
  124. }
  125. if (/[^A-Z](TODO|FIXME|XXX)[^A-Z]/.test(line)) {
  126. if (/[^A-Z](TODO|FIXME|XXX)[^\(A-Z]/.test(line)) {
  127. error("Please take responsibility for your TODO: eg: // TODO(cjd): make this work");
  128. } else {
  129. console.log(lineInfo + ' ' + line.replace(/[ \/]*/, ''));
  130. }
  131. }
  132. if (/(if|for|while)\(/.test(line)) {
  133. error("If/for/while statements must be followed by whitespace.");
  134. }
  135. matches = null;
  136. if (parenthCount === 0) {
  137. matches = /[^\w#](if|for|while) (\(.*$)/.exec(line);
  138. }
  139. if (parenthCount > 0 || matches) {
  140. var txt1 = (parenthCount > 0) ? line : matches[2];
  141. parenthCount += (txt1.match(/\(/g)||[]).length;
  142. parenthCount -= (txt1.match(/\)/g)||[]).length;
  143. if (parenthCount === 0) {
  144. txt1 = txt1.substring(txt1.lastIndexOf(')') + 1);
  145. // for (x; y; z) ; <-- ok
  146. // for (x; y; z) { <-- ok
  147. // for (x; y; z) { \ <-- ok (in preprocessor macro)
  148. // for (x; y; z) <-- ok but you better put a bracket on the next line
  149. // for (x; y; z) { j++; } <-- ok
  150. // for (x; y; z) j++; <-- BZZZZZZZZZZT
  151. if (!(/^[\s]*[;{].*$/.test(txt1)) && !(/^[\s]+{[\s]*\\$/).test(txt1)) {
  152. if (/[\s]*$/.test(txt1)) {
  153. expectBracket = 1;
  154. } else {
  155. error(parenthCount + ' ' + line);
  156. }
  157. }
  158. }
  159. }
  160. }
  161. return output;
  162. };
  163. var checkFile = module.exports.checkFile = function (file, callback) {
  164. Fs.readFile(file, function (err, ret) {
  165. if (err) { throw err; }
  166. callback(parseFile(file, ret.toString()));
  167. });
  168. };
  169. var lint = module.exports.lint = function (fileName, fileContent, callback) {
  170. var out = parseFile(fileName, fileContent);
  171. callback(out, !!out);
  172. };
  173. var checkFiles = module.exports.checkFiles = function (files, callback) {
  174. var sema = Semaphore.create(64);
  175. var errors = '';
  176. nThen(function (waitFor) {
  177. files.forEach(function (file) {
  178. sema.take(waitFor(function (returnAfter) {
  179. checkFile(file, waitFor(returnAfter(function (err) {
  180. if (err) {
  181. errors += file + '\n' + err + '\n';
  182. }
  183. })));
  184. }));
  185. });
  186. }).nThen(function (waitFor) {
  187. callback(errors);
  188. });
  189. };
  190. var checkDir = module.exports.checkDir = function (dir, runInFork, callback) {
  191. var gitIgnoreLines;
  192. if (runInFork) {
  193. var err = '';
  194. var out = '';
  195. var proc = Child.spawn(process.execPath, [__filename]);
  196. proc.stdout.on('data', function (data) { err += data.toString('utf8'); });
  197. proc.stderr.on('data', function (data) { err += data.toString('utf8'); });
  198. proc.on('close', function (ret) {
  199. out += err;
  200. var error;
  201. if (ret !== 0) { error = new Error(out); }
  202. callback(error, out);
  203. });
  204. return;
  205. }
  206. var output = '';
  207. nThen(function (waitFor) {
  208. Fs.readFile('.gitignore', waitFor(function (err, ret) {
  209. if (err) { throw err; }
  210. gitIgnoreLines = ret.toString('utf8').split('\n');
  211. }));
  212. }).nThen(function (waitFor) {
  213. var addDir = function (dir) {
  214. Fs.readdir(dir, waitFor(function (err, files) {
  215. if (err) { throw err; }
  216. files.forEach(function (file) {
  217. Fs.stat(dir + '/' + file, waitFor(function (err, stat) {
  218. if (err) { throw err; }
  219. if (file === '.git') {
  220. } else if (file === 'contrib') {
  221. } else if (file === 'dependencies') {
  222. } else if (gitIgnoreLines.indexOf(file) !== -1) {
  223. } else {
  224. if (stat.isDirectory()) {
  225. addDir(dir + '/' + file);
  226. } else if (/.*\.[ch]$/.test(file)) {
  227. checkFile(dir + '/' + file, waitFor(function (ret) {
  228. output += ret;
  229. }));
  230. }
  231. }
  232. }));
  233. });
  234. }));
  235. };
  236. addDir(dir);
  237. }).nThen(function (waitFor) {
  238. callback(output);
  239. });
  240. };
  241. if (module.parent === null) {
  242. checkDir('.', false, function(output) {
  243. if (output !== '') {
  244. console.log(output);
  245. process.exit(1);
  246. }
  247. });
  248. }