##// END OF EJS Templates
check-code: there must also be whitespace between ')' and operator...
Mads Kiilerich -
r18054:b35e3364 default
parent child Browse files
Show More
@@ -1,454 +1,454 b''
1 1 #!/usr/bin/env python
2 2 #
3 3 # check-code - a style and portability checker for Mercurial
4 4 #
5 5 # Copyright 2010 Matt Mackall <mpm@selenic.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 import re, glob, os, sys
11 11 import keyword
12 12 import optparse
13 13
14 14 def repquote(m):
15 15 t = re.sub(r"\w", "x", m.group('text'))
16 16 t = re.sub(r"[^\s\nx]", "o", t)
17 17 return m.group('quote') + t + m.group('quote')
18 18
19 19 def reppython(m):
20 20 comment = m.group('comment')
21 21 if comment:
22 22 return "#" * len(comment)
23 23 return repquote(m)
24 24
25 25 def repcomment(m):
26 26 return m.group(1) + "#" * len(m.group(2))
27 27
28 28 def repccomment(m):
29 29 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
30 30 return m.group(1) + t + "*/"
31 31
32 32 def repcallspaces(m):
33 33 t = re.sub(r"\n\s+", "\n", m.group(2))
34 34 return m.group(1) + t
35 35
36 36 def repinclude(m):
37 37 return m.group(1) + "<foo>"
38 38
39 39 def rephere(m):
40 40 t = re.sub(r"\S", "x", m.group(2))
41 41 return m.group(1) + t
42 42
43 43
44 44 testpats = [
45 45 [
46 46 (r'pushd|popd', "don't use 'pushd' or 'popd', use 'cd'"),
47 47 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
48 48 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
49 49 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
50 50 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
51 51 (r'echo -n', "don't use 'echo -n', use printf"),
52 52 (r'(^| )wc[^|]*$\n(?!.*\(re\))', "filter wc output"),
53 53 (r'head -c', "don't use 'head -c', use 'dd'"),
54 54 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
55 55 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
56 56 (r'printf.*\\([1-9]|0\d)', "don't use 'printf \NNN', use Python"),
57 57 (r'printf.*\\x', "don't use printf \\x, use Python"),
58 58 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
59 59 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
60 60 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
61 61 "use egrep for extended grep syntax"),
62 62 (r'/bin/', "don't use explicit paths for tools"),
63 63 (r'[^\n]\Z', "no trailing newline"),
64 64 (r'export.*=', "don't export and assign at once"),
65 65 (r'^source\b', "don't use 'source', use '.'"),
66 66 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
67 67 (r'ls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
68 68 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
69 69 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
70 70 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
71 71 (r'^alias\b.*=', "don't use alias, use a function"),
72 72 (r'if\s*!', "don't use '!' to negate exit status"),
73 73 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
74 74 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
75 75 (r'^( *)\t', "don't use tabs to indent"),
76 76 ],
77 77 # warnings
78 78 [
79 79 (r'^function', "don't use 'function', use old style"),
80 80 (r'^diff.*-\w*N', "don't use 'diff -N'"),
81 81 (r'\$PWD', "don't use $PWD, use `pwd`"),
82 82 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
83 83 ]
84 84 ]
85 85
86 86 testfilters = [
87 87 (r"( *)(#([^\n]*\S)?)", repcomment),
88 88 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
89 89 ]
90 90
91 91 uprefix = r"^ \$ "
92 92 utestpats = [
93 93 [
94 94 (r'^(\S.*|| [$>] .*)[ \t]\n', "trailing whitespace on non-output"),
95 95 (uprefix + r'.*\|\s*sed[^|>\n]*\n',
96 96 "use regex test output patterns instead of sed"),
97 97 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
98 98 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
99 99 (uprefix + r'.*\|\| echo.*(fail|error)',
100 100 "explicit exit code checks unnecessary"),
101 101 (uprefix + r'set -e', "don't use set -e"),
102 102 (uprefix + r'\s', "don't indent commands, use > for continued lines"),
103 103 (r'^ saved backup bundle to \$TESTTMP.*\.hg$',
104 104 "use (glob) to match Windows paths too"),
105 105 ],
106 106 # warnings
107 107 []
108 108 ]
109 109
110 110 for i in [0, 1]:
111 111 for p, m in testpats[i]:
112 112 if p.startswith(r'^'):
113 113 p = r"^ [$>] (%s)" % p[1:]
114 114 else:
115 115 p = r"^ [$>] .*(%s)" % p
116 116 utestpats[i].append((p, m))
117 117
118 118 utestfilters = [
119 119 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
120 120 (r"( *)(#([^\n]*\S)?)", repcomment),
121 121 ]
122 122
123 123 pypats = [
124 124 [
125 125 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
126 126 "tuple parameter unpacking not available in Python 3+"),
127 127 (r'lambda\s*\(.*,.*\)',
128 128 "tuple parameter unpacking not available in Python 3+"),
129 129 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
130 130 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
131 131 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
132 132 (r'^\s*\t', "don't use tabs"),
133 133 (r'\S;\s*\n', "semicolon"),
134 134 (r'[^_]_\("[^"]+"\s*%', "don't use % inside _()"),
135 135 (r"[^_]_\('[^']+'\s*%", "don't use % inside _()"),
136 (r'\w,\w', "missing whitespace after ,"),
137 (r'\w[+/*\-<>]\w', "missing whitespace in expression"),
136 (r'(\w|\)),\w', "missing whitespace after ,"),
137 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
138 138 (r'^\s+\w+=\w+[^,)\n]$', "missing whitespace in assignment"),
139 139 (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)\1except.*?:\n'
140 140 r'((?:\n|\1\s.*\n)+?)\1finally:', 'no try/except/finally in Python 2.4'),
141 141 (r'(\s+)try:\n((?:\n|\1\s.*\n)*?)\1\s*yield\b.*?'
142 142 r'((?:\n|\1\s.*\n)+?)\1finally:',
143 143 'no yield inside try/finally in Python 2.4'),
144 144 (r'.{81}', "line too long"),
145 145 (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
146 146 (r'[^\n]\Z', "no trailing newline"),
147 147 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
148 148 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
149 149 # "don't use underbars in identifiers"),
150 150 (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
151 151 "don't use camelcase in identifiers"),
152 152 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
153 153 "linebreak after :"),
154 154 (r'class\s[^( \n]+:', "old-style class, use class foo(object)"),
155 155 (r'class\s[^( \n]+\(\):',
156 156 "class foo() not available in Python 2.4, use class foo(object)"),
157 157 (r'\b(%s)\(' % '|'.join(keyword.kwlist),
158 158 "Python keyword is not a function"),
159 159 (r',]', "unneeded trailing ',' in list"),
160 160 # (r'class\s[A-Z][^\(]*\((?!Exception)',
161 161 # "don't capitalize non-exception classes"),
162 162 # (r'in range\(', "use xrange"),
163 163 # (r'^\s*print\s+', "avoid using print in core and extensions"),
164 164 (r'[\x80-\xff]', "non-ASCII character literal"),
165 165 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
166 166 (r'^\s*with\s+', "with not available in Python 2.4"),
167 167 (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"),
168 168 (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
169 169 (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
170 170 (r'(?<!def)\s+(any|all|format)\(',
171 171 "any/all/format not available in Python 2.4"),
172 172 (r'(?<!def)\s+(callable)\(',
173 173 "callable not available in Python 3, use getattr(f, '__call__', None)"),
174 174 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
175 175 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
176 176 "gratuitous whitespace after Python keyword"),
177 177 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
178 178 # (r'\s\s=', "gratuitous whitespace before ="),
179 179 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
180 180 "missing whitespace around operator"),
181 181 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
182 182 "missing whitespace around operator"),
183 183 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
184 184 "missing whitespace around operator"),
185 185 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
186 186 "wrong whitespace around ="),
187 187 (r'raise Exception', "don't raise generic exceptions"),
188 188 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
189 189 (r' [=!]=\s+(True|False|None)',
190 190 "comparison with singleton, use 'is' or 'is not' instead"),
191 191 (r'^\s*(while|if) [01]:',
192 192 "use True/False for constant Boolean expression"),
193 193 (r'(?:(?<!def)\s+|\()hasattr',
194 194 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
195 195 (r'opener\([^)]*\).read\(',
196 196 "use opener.read() instead"),
197 197 (r'BaseException', 'not in Python 2.4, use Exception'),
198 198 (r'os\.path\.relpath', 'os.path.relpath is not in Python 2.5'),
199 199 (r'opener\([^)]*\).write\(',
200 200 "use opener.write() instead"),
201 201 (r'[\s\(](open|file)\([^)]*\)\.read\(',
202 202 "use util.readfile() instead"),
203 203 (r'[\s\(](open|file)\([^)]*\)\.write\(',
204 204 "use util.readfile() instead"),
205 205 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
206 206 "always assign an opened file to a variable, and close it afterwards"),
207 207 (r'[\s\(](open|file)\([^)]*\)\.',
208 208 "always assign an opened file to a variable, and close it afterwards"),
209 209 (r'(?i)descendent', "the proper spelling is descendAnt"),
210 210 (r'\.debug\(\_', "don't mark debug messages for translation"),
211 211 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
212 212 (r'^\s*except\s*:', "warning: naked except clause", r'#.*re-raises'),
213 213 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
214 214 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
215 215 "missing _() in ui message (use () to hide false-positives)"),
216 216 ],
217 217 # warnings
218 218 [
219 219 ]
220 220 ]
221 221
222 222 pyfilters = [
223 223 (r"""(?msx)(?P<comment>\#.*?$)|
224 224 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
225 225 (?P<text>(([^\\]|\\.)*?))
226 226 (?P=quote))""", reppython),
227 227 ]
228 228
229 229 cpats = [
230 230 [
231 231 (r'//', "don't use //-style comments"),
232 232 (r'^ ', "don't use spaces to indent"),
233 233 (r'\S\t', "don't use tabs except for indent"),
234 234 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
235 235 (r'.{81}', "line too long"),
236 236 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
237 237 (r'return\(', "return is not a function"),
238 238 (r' ;', "no space before ;"),
239 239 (r'\w+\* \w+', "use int *foo, not int* foo"),
240 240 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
241 241 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
242 242 (r'\w,\w', "missing whitespace after ,"),
243 243 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
244 244 (r'^#\s+\w', "use #foo, not # foo"),
245 245 (r'[^\n]\Z', "no trailing newline"),
246 246 (r'^\s*#import\b', "use only #include in standard C code"),
247 247 ],
248 248 # warnings
249 249 []
250 250 ]
251 251
252 252 cfilters = [
253 253 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
254 254 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
255 255 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
256 256 (r'(\()([^)]+\))', repcallspaces),
257 257 ]
258 258
259 259 inutilpats = [
260 260 [
261 261 (r'\bui\.', "don't use ui in util"),
262 262 ],
263 263 # warnings
264 264 []
265 265 ]
266 266
267 267 inrevlogpats = [
268 268 [
269 269 (r'\brepo\.', "don't use repo in revlog"),
270 270 ],
271 271 # warnings
272 272 []
273 273 ]
274 274
275 275 checks = [
276 276 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
277 277 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
278 278 ('c', r'.*\.c$', cfilters, cpats),
279 279 ('unified test', r'.*\.t$', utestfilters, utestpats),
280 280 ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters,
281 281 inrevlogpats),
282 282 ('layering violation ui in util', r'mercurial/util\.py', pyfilters,
283 283 inutilpats),
284 284 ]
285 285
286 286 class norepeatlogger(object):
287 287 def __init__(self):
288 288 self._lastseen = None
289 289
290 290 def log(self, fname, lineno, line, msg, blame):
291 291 """print error related a to given line of a given file.
292 292
293 293 The faulty line will also be printed but only once in the case
294 294 of multiple errors.
295 295
296 296 :fname: filename
297 297 :lineno: line number
298 298 :line: actual content of the line
299 299 :msg: error message
300 300 """
301 301 msgid = fname, lineno, line
302 302 if msgid != self._lastseen:
303 303 if blame:
304 304 print "%s:%d (%s):" % (fname, lineno, blame)
305 305 else:
306 306 print "%s:%d:" % (fname, lineno)
307 307 print " > %s" % line
308 308 self._lastseen = msgid
309 309 print " " + msg
310 310
311 311 _defaultlogger = norepeatlogger()
312 312
313 313 def getblame(f):
314 314 lines = []
315 315 for l in os.popen('hg annotate -un %s' % f):
316 316 start, line = l.split(':', 1)
317 317 user, rev = start.split()
318 318 lines.append((line[1:-1], user, rev))
319 319 return lines
320 320
321 321 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
322 322 blame=False, debug=False, lineno=True):
323 323 """checks style and portability of a given file
324 324
325 325 :f: filepath
326 326 :logfunc: function used to report error
327 327 logfunc(filename, linenumber, linecontent, errormessage)
328 328 :maxerr: number of error to display before aborting.
329 329 Set to false (default) to report all errors
330 330
331 331 return True if no error is found, False otherwise.
332 332 """
333 333 blamecache = None
334 334 result = True
335 335 for name, match, filters, pats in checks:
336 336 if debug:
337 337 print name, f
338 338 fc = 0
339 339 if not re.match(match, f):
340 340 if debug:
341 341 print "Skipping %s for %s it doesn't match %s" % (
342 342 name, match, f)
343 343 continue
344 344 fp = open(f)
345 345 pre = post = fp.read()
346 346 fp.close()
347 347 if "no-" + "check-code" in pre:
348 348 if debug:
349 349 print "Skipping %s for %s it has no- and check-code" % (
350 350 name, f)
351 351 break
352 352 for p, r in filters:
353 353 post = re.sub(p, r, post)
354 354 if warnings:
355 355 pats = pats[0] + pats[1]
356 356 else:
357 357 pats = pats[0]
358 358 # print post # uncomment to show filtered version
359 359
360 360 if debug:
361 361 print "Checking %s for %s" % (name, f)
362 362
363 363 prelines = None
364 364 errors = []
365 365 for pat in pats:
366 366 if len(pat) == 3:
367 367 p, msg, ignore = pat
368 368 else:
369 369 p, msg = pat
370 370 ignore = None
371 371
372 372 # fix-up regexes for multi-line searches
373 373 po = p
374 374 # \s doesn't match \n
375 375 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
376 376 # [^...] doesn't match newline
377 377 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
378 378
379 379 #print po, '=>', p
380 380
381 381 pos = 0
382 382 n = 0
383 383 for m in re.finditer(p, post, re.MULTILINE):
384 384 if prelines is None:
385 385 prelines = pre.splitlines()
386 386 postlines = post.splitlines(True)
387 387
388 388 start = m.start()
389 389 while n < len(postlines):
390 390 step = len(postlines[n])
391 391 if pos + step > start:
392 392 break
393 393 pos += step
394 394 n += 1
395 395 l = prelines[n]
396 396
397 397 if "check-code" + "-ignore" in l:
398 398 if debug:
399 399 print "Skipping %s for %s:%s (check-code -ignore)" % (
400 400 name, f, n)
401 401 continue
402 402 elif ignore and re.search(ignore, l, re.MULTILINE):
403 403 continue
404 404 bd = ""
405 405 if blame:
406 406 bd = 'working directory'
407 407 if not blamecache:
408 408 blamecache = getblame(f)
409 409 if n < len(blamecache):
410 410 bl, bu, br = blamecache[n]
411 411 if bl == l:
412 412 bd = '%s@%s' % (bu, br)
413 413 errors.append((f, lineno and n + 1, l, msg, bd))
414 414 result = False
415 415
416 416 errors.sort()
417 417 for e in errors:
418 418 logfunc(*e)
419 419 fc += 1
420 420 if maxerr and fc >= maxerr:
421 421 print " (too many errors, giving up)"
422 422 break
423 423
424 424 return result
425 425
426 426 if __name__ == "__main__":
427 427 parser = optparse.OptionParser("%prog [options] [files]")
428 428 parser.add_option("-w", "--warnings", action="store_true",
429 429 help="include warning-level checks")
430 430 parser.add_option("-p", "--per-file", type="int",
431 431 help="max warnings per file")
432 432 parser.add_option("-b", "--blame", action="store_true",
433 433 help="use annotate to generate blame info")
434 434 parser.add_option("", "--debug", action="store_true",
435 435 help="show debug information")
436 436 parser.add_option("", "--nolineno", action="store_false",
437 437 dest='lineno', help="don't show line numbers")
438 438
439 439 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
440 440 lineno=True)
441 441 (options, args) = parser.parse_args()
442 442
443 443 if len(args) == 0:
444 444 check = glob.glob("*")
445 445 else:
446 446 check = args
447 447
448 448 ret = 0
449 449 for f in check:
450 450 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
451 451 blame=options.blame, debug=options.debug,
452 452 lineno=options.lineno):
453 453 ret = 1
454 454 sys.exit(ret)
General Comments 0
You need to be logged in to leave comments. Login now