##// END OF EJS Templates
check-code: prepend warning prefix only once, but for each warning...
Simon Heimberg -
r20005:22154ec6 stable
parent child Browse files
Show More
@@ -1,546 +1,547 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 try:
14 14 import re2
15 15 except ImportError:
16 16 re2 = None
17 17
18 18 def compilere(pat, multiline=False):
19 19 if multiline:
20 20 pat = '(?m)' + pat
21 21 if re2:
22 22 try:
23 23 return re2.compile(pat)
24 24 except re2.error:
25 25 pass
26 26 return re.compile(pat)
27 27
28 28 def repquote(m):
29 29 fromc = '.:'
30 30 tochr = 'pq'
31 31 def encodechr(i):
32 32 if i > 255:
33 33 return 'u'
34 34 c = chr(i)
35 35 if c in ' \n':
36 36 return c
37 37 if c.isalpha():
38 38 return 'x'
39 39 if c.isdigit():
40 40 return 'n'
41 41 try:
42 42 return tochr[fromc.find(c)]
43 43 except (ValueError, IndexError):
44 44 return 'o'
45 45 t = m.group('text')
46 46 tt = ''.join(encodechr(i) for i in xrange(256))
47 47 t = t.translate(tt)
48 48 return m.group('quote') + t + m.group('quote')
49 49
50 50 def reppython(m):
51 51 comment = m.group('comment')
52 52 if comment:
53 53 l = len(comment.rstrip())
54 54 return "#" * l + comment[l:]
55 55 return repquote(m)
56 56
57 57 def repcomment(m):
58 58 return m.group(1) + "#" * len(m.group(2))
59 59
60 60 def repccomment(m):
61 61 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
62 62 return m.group(1) + t + "*/"
63 63
64 64 def repcallspaces(m):
65 65 t = re.sub(r"\n\s+", "\n", m.group(2))
66 66 return m.group(1) + t
67 67
68 68 def repinclude(m):
69 69 return m.group(1) + "<foo>"
70 70
71 71 def rephere(m):
72 72 t = re.sub(r"\S", "x", m.group(2))
73 73 return m.group(1) + t
74 74
75 75
76 76 testpats = [
77 77 [
78 78 (r'pushd|popd', "don't use 'pushd' or 'popd', use 'cd'"),
79 79 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
80 80 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
81 81 (r'(?<!hg )grep.*-a', "don't use 'grep -a', use in-line python"),
82 82 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
83 83 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
84 84 (r'echo -n', "don't use 'echo -n', use printf"),
85 85 (r'(^| )wc[^|]*$\n(?!.*\(re\))', "filter wc output"),
86 86 (r'head -c', "don't use 'head -c', use 'dd'"),
87 87 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
88 88 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
89 89 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
90 90 (r'printf.*[^\\]\\([1-9]|0\d)', "don't use 'printf \NNN', use Python"),
91 91 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
92 92 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
93 93 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
94 94 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
95 95 "use egrep for extended grep syntax"),
96 96 (r'/bin/', "don't use explicit paths for tools"),
97 97 (r'[^\n]\Z', "no trailing newline"),
98 98 (r'export.*=', "don't export and assign at once"),
99 99 (r'^source\b', "don't use 'source', use '.'"),
100 100 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
101 101 (r'ls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
102 102 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
103 103 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
104 104 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
105 105 (r'^alias\b.*=', "don't use alias, use a function"),
106 106 (r'if\s*!', "don't use '!' to negate exit status"),
107 107 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
108 108 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
109 109 (r'^( *)\t', "don't use tabs to indent"),
110 110 (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
111 111 "put a backslash-escaped newline after sed 'i' command"),
112 112 ],
113 113 # warnings
114 114 [
115 115 (r'^function', "don't use 'function', use old style"),
116 116 (r'^diff.*-\w*N', "don't use 'diff -N'"),
117 117 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
118 118 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
119 119 (r'kill (`|\$\()', "don't use kill, use killdaemons.py")
120 120 ]
121 121 ]
122 122
123 123 testfilters = [
124 124 (r"( *)(#([^\n]*\S)?)", repcomment),
125 125 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
126 126 ]
127 127
128 128 winglobmsg = "use (glob) to match Windows paths too"
129 129 uprefix = r"^ \$ "
130 130 utestpats = [
131 131 [
132 132 (r'^(\S.*|| [$>] .*)[ \t]\n', "trailing whitespace on non-output"),
133 133 (uprefix + r'.*\|\s*sed[^|>\n]*\n',
134 134 "use regex test output patterns instead of sed"),
135 135 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
136 136 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
137 137 (uprefix + r'.*\|\| echo.*(fail|error)',
138 138 "explicit exit code checks unnecessary"),
139 139 (uprefix + r'set -e', "don't use set -e"),
140 140 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
141 141 (r'^ saved backup bundle to \$TESTTMP.*\.hg$', winglobmsg),
142 142 (r'^ changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
143 143 winglobmsg),
144 144 (r'^ pulling from \$TESTTMP/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
145 145 (r'^ reverting .*/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
146 146 (r'^ cloning subrepo \S+/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
147 147 (r'^ pushing to \$TESTTMP/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
148 148 (r'^ pushing subrepo \S+/\S+ to.*[^)]$', winglobmsg,
149 149 '\$TESTTMP/unix-repo$'),
150 150 (r'^ moving \S+/.*[^)]$', winglobmsg),
151 151 (r'^ no changes made to subrepo since.*/.*[^)]$',
152 152 winglobmsg, '\$TESTTMP/unix-repo$'),
153 153 (r'^ .*: largefile \S+ not available from file:.*/.*[^)]$',
154 154 winglobmsg, '\$TESTTMP/unix-repo$'),
155 155 ],
156 156 # warnings
157 157 [
158 158 (r'^ [^*?/\n]* \(glob\)$',
159 159 "glob match with no glob character (?*/)"),
160 160 ]
161 161 ]
162 162
163 163 for i in [0, 1]:
164 164 for p, m in testpats[i]:
165 165 if p.startswith(r'^'):
166 166 p = r"^ [$>] (%s)" % p[1:]
167 167 else:
168 168 p = r"^ [$>] .*(%s)" % p
169 169 utestpats[i].append((p, m))
170 170
171 171 utestfilters = [
172 172 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
173 173 (r"( *)(#([^\n]*\S)?)", repcomment),
174 174 ]
175 175
176 176 pypats = [
177 177 [
178 178 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
179 179 "tuple parameter unpacking not available in Python 3+"),
180 180 (r'lambda\s*\(.*,.*\)',
181 181 "tuple parameter unpacking not available in Python 3+"),
182 182 (r'import (.+,[^.]+\.[^.]+|[^.]+\.[^.]+,)',
183 183 '2to3 can\'t always rewrite "import qux, foo.bar", '
184 184 'use "import foo.bar" on its own line instead.'),
185 185 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
186 186 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
187 187 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
188 188 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
189 189 (r'^\s*\t', "don't use tabs"),
190 190 (r'\S;\s*\n', "semicolon"),
191 191 (r'[^_]_\("[^"]+"\s*%', "don't use % inside _()"),
192 192 (r"[^_]_\('[^']+'\s*%", "don't use % inside _()"),
193 193 (r'(\w|\)),\w', "missing whitespace after ,"),
194 194 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
195 195 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
196 196 (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)\1except.*?:\n'
197 197 r'((?:\n|\1\s.*\n)+?)\1finally:', 'no try/except/finally in Python 2.4'),
198 198 (r'(?<!def)(\s+|^|\()next\(.+\)',
199 199 'no next(foo) in Python 2.4 and 2.5, use foo.next() instead'),
200 200 (r'(\s+)try:\n((?:\n|\1\s.*\n)*?)\1\s*yield\b.*?'
201 201 r'((?:\n|\1\s.*\n)+?)\1finally:',
202 202 'no yield inside try/finally in Python 2.4'),
203 203 (r'.{81}', "line too long"),
204 204 (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
205 205 (r'[^\n]\Z', "no trailing newline"),
206 206 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
207 207 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
208 208 # "don't use underbars in identifiers"),
209 209 (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
210 210 "don't use camelcase in identifiers"),
211 211 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
212 212 "linebreak after :"),
213 213 (r'class\s[^( \n]+:', "old-style class, use class foo(object)"),
214 214 (r'class\s[^( \n]+\(\):',
215 215 "class foo() not available in Python 2.4, use class foo(object)"),
216 216 (r'\b(%s)\(' % '|'.join(keyword.kwlist),
217 217 "Python keyword is not a function"),
218 218 (r',]', "unneeded trailing ',' in list"),
219 219 # (r'class\s[A-Z][^\(]*\((?!Exception)',
220 220 # "don't capitalize non-exception classes"),
221 221 # (r'in range\(', "use xrange"),
222 222 # (r'^\s*print\s+', "avoid using print in core and extensions"),
223 223 (r'[\x80-\xff]', "non-ASCII character literal"),
224 224 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
225 225 (r'^\s*with\s+', "with not available in Python 2.4"),
226 226 (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"),
227 227 (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
228 228 (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
229 229 (r'(?<!def)\s+(any|all|format)\(',
230 230 "any/all/format not available in Python 2.4"),
231 231 (r'(?<!def)\s+(callable)\(',
232 232 "callable not available in Python 3, use getattr(f, '__call__', None)"),
233 233 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
234 234 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
235 235 "gratuitous whitespace after Python keyword"),
236 236 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
237 237 # (r'\s\s=', "gratuitous whitespace before ="),
238 238 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
239 239 "missing whitespace around operator"),
240 240 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
241 241 "missing whitespace around operator"),
242 242 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
243 243 "missing whitespace around operator"),
244 244 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
245 245 "wrong whitespace around ="),
246 246 (r'\([^()]*( =[^=]|[^<>!=]= )',
247 247 "no whitespace around = for named parameters"),
248 248 (r'raise Exception', "don't raise generic exceptions"),
249 249 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
250 250 "don't use old-style two-argument raise, use Exception(message)"),
251 251 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
252 252 (r' [=!]=\s+(True|False|None)',
253 253 "comparison with singleton, use 'is' or 'is not' instead"),
254 254 (r'^\s*(while|if) [01]:',
255 255 "use True/False for constant Boolean expression"),
256 256 (r'(?:(?<!def)\s+|\()hasattr',
257 257 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
258 258 (r'opener\([^)]*\).read\(',
259 259 "use opener.read() instead"),
260 260 (r'BaseException', 'not in Python 2.4, use Exception'),
261 261 (r'os\.path\.relpath', 'os.path.relpath is not in Python 2.5'),
262 262 (r'opener\([^)]*\).write\(',
263 263 "use opener.write() instead"),
264 264 (r'[\s\(](open|file)\([^)]*\)\.read\(',
265 265 "use util.readfile() instead"),
266 266 (r'[\s\(](open|file)\([^)]*\)\.write\(',
267 267 "use util.readfile() instead"),
268 268 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
269 269 "always assign an opened file to a variable, and close it afterwards"),
270 270 (r'[\s\(](open|file)\([^)]*\)\.',
271 271 "always assign an opened file to a variable, and close it afterwards"),
272 272 (r'(?i)descendent', "the proper spelling is descendAnt"),
273 273 (r'\.debug\(\_', "don't mark debug messages for translation"),
274 274 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
275 275 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
276 276 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
277 277 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
278 278 "missing _() in ui message (use () to hide false-positives)"),
279 279 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
280 280 ],
281 281 # warnings
282 282 [
283 283 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
284 284 ]
285 285 ]
286 286
287 287 pyfilters = [
288 288 (r"""(?msx)(?P<comment>\#.*?$)|
289 289 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
290 290 (?P<text>(([^\\]|\\.)*?))
291 291 (?P=quote))""", reppython),
292 292 ]
293 293
294 294 txtfilters = []
295 295
296 296 txtpats = [
297 297 [
298 298 ('\s$', 'trailing whitespace'),
299 299 ],
300 300 []
301 301 ]
302 302
303 303 cpats = [
304 304 [
305 305 (r'//', "don't use //-style comments"),
306 306 (r'^ ', "don't use spaces to indent"),
307 307 (r'\S\t', "don't use tabs except for indent"),
308 308 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
309 309 (r'.{81}', "line too long"),
310 310 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
311 311 (r'return\(', "return is not a function"),
312 312 (r' ;', "no space before ;"),
313 313 (r'[)][{]', "space between ) and {"),
314 314 (r'\w+\* \w+', "use int *foo, not int* foo"),
315 315 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
316 316 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
317 317 (r'\w,\w', "missing whitespace after ,"),
318 318 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
319 319 (r'^#\s+\w', "use #foo, not # foo"),
320 320 (r'[^\n]\Z', "no trailing newline"),
321 321 (r'^\s*#import\b', "use only #include in standard C code"),
322 322 ],
323 323 # warnings
324 324 []
325 325 ]
326 326
327 327 cfilters = [
328 328 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
329 329 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
330 330 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
331 331 (r'(\()([^)]+\))', repcallspaces),
332 332 ]
333 333
334 334 inutilpats = [
335 335 [
336 336 (r'\bui\.', "don't use ui in util"),
337 337 ],
338 338 # warnings
339 339 []
340 340 ]
341 341
342 342 inrevlogpats = [
343 343 [
344 344 (r'\brepo\.', "don't use repo in revlog"),
345 345 ],
346 346 # warnings
347 347 []
348 348 ]
349 349
350 350 checks = [
351 351 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
352 352 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
353 353 ('c', r'.*\.[ch]$', cfilters, cpats),
354 354 ('unified test', r'.*\.t$', utestfilters, utestpats),
355 355 ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters,
356 356 inrevlogpats),
357 357 ('layering violation ui in util', r'mercurial/util\.py', pyfilters,
358 358 inutilpats),
359 359 ('txt', r'.*\.txt$', txtfilters, txtpats),
360 360 ]
361 361
362 362 def _preparepats():
363 363 for c in checks:
364 364 failandwarn = c[-1]
365 365 for pats in failandwarn:
366 366 for i, pseq in enumerate(pats):
367 367 # fix-up regexes for multi-line searches
368 368 p = pseq[0]
369 369 # \s doesn't match \n
370 370 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
371 371 # [^...] doesn't match newline
372 372 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
373 373
374 374 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
375 375 filters = c[2]
376 376 for i, flt in enumerate(filters):
377 377 filters[i] = re.compile(flt[0]), flt[1]
378 378 _preparepats()
379 379
380 380 class norepeatlogger(object):
381 381 def __init__(self):
382 382 self._lastseen = None
383 383
384 384 def log(self, fname, lineno, line, msg, blame):
385 385 """print error related a to given line of a given file.
386 386
387 387 The faulty line will also be printed but only once in the case
388 388 of multiple errors.
389 389
390 390 :fname: filename
391 391 :lineno: line number
392 392 :line: actual content of the line
393 393 :msg: error message
394 394 """
395 395 msgid = fname, lineno, line
396 396 if msgid != self._lastseen:
397 397 if blame:
398 398 print "%s:%d (%s):" % (fname, lineno, blame)
399 399 else:
400 400 print "%s:%d:" % (fname, lineno)
401 401 print " > %s" % line
402 402 self._lastseen = msgid
403 403 print " " + msg
404 404
405 405 _defaultlogger = norepeatlogger()
406 406
407 407 def getblame(f):
408 408 lines = []
409 409 for l in os.popen('hg annotate -un %s' % f):
410 410 start, line = l.split(':', 1)
411 411 user, rev = start.split()
412 412 lines.append((line[1:-1], user, rev))
413 413 return lines
414 414
415 415 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
416 416 blame=False, debug=False, lineno=True):
417 417 """checks style and portability of a given file
418 418
419 419 :f: filepath
420 420 :logfunc: function used to report error
421 421 logfunc(filename, linenumber, linecontent, errormessage)
422 422 :maxerr: number of error to display before aborting.
423 423 Set to false (default) to report all errors
424 424
425 425 return True if no error is found, False otherwise.
426 426 """
427 427 blamecache = None
428 428 result = True
429 429 for name, match, filters, pats in checks:
430 430 if debug:
431 431 print name, f
432 432 fc = 0
433 433 if not re.match(match, f):
434 434 if debug:
435 435 print "Skipping %s for %s it doesn't match %s" % (
436 436 name, match, f)
437 437 continue
438 438 try:
439 439 fp = open(f)
440 440 except IOError, e:
441 441 print "Skipping %s, %s" % (f, str(e).split(':', 1)[0])
442 442 continue
443 443 pre = post = fp.read()
444 444 fp.close()
445 445 if "no-" "check-code" in pre:
446 446 if debug:
447 447 print "Skipping %s for %s it has no-" "check-code" % (
448 448 name, f)
449 449 break
450 450 for p, r in filters:
451 451 post = re.sub(p, r, post)
452 452 nerrs = len(pats[0]) # nerr elements are errors
453 453 if warnings:
454 454 pats = pats[0] + pats[1]
455 455 else:
456 456 pats = pats[0]
457 457 # print post # uncomment to show filtered version
458 458
459 459 if debug:
460 460 print "Checking %s for %s" % (name, f)
461 461
462 462 prelines = None
463 463 errors = []
464 464 for i, pat in enumerate(pats):
465 465 if len(pat) == 3:
466 466 p, msg, ignore = pat
467 467 else:
468 468 p, msg = pat
469 469 ignore = None
470 if i >= nerrs:
471 msg = "warning: " + msg
470 472
471 473 pos = 0
472 474 n = 0
473 475 for m in p.finditer(post):
474 476 if prelines is None:
475 477 prelines = pre.splitlines()
476 478 postlines = post.splitlines(True)
477 479
478 480 start = m.start()
479 481 while n < len(postlines):
480 482 step = len(postlines[n])
481 483 if pos + step > start:
482 484 break
483 485 pos += step
484 486 n += 1
485 487 l = prelines[n]
486 488
487 489 if "check-code" "-ignore" in l:
488 490 if debug:
489 491 print "Skipping %s for %s:%s (check-code" "-ignore)" % (
490 492 name, f, n)
491 493 continue
492 494 elif ignore and re.search(ignore, l, re.MULTILINE):
493 495 continue
494 496 bd = ""
495 497 if blame:
496 498 bd = 'working directory'
497 499 if not blamecache:
498 500 blamecache = getblame(f)
499 501 if n < len(blamecache):
500 502 bl, bu, br = blamecache[n]
501 503 if bl == l:
502 504 bd = '%s@%s' % (bu, br)
503 if i >= nerrs:
504 msg = "warning: " + msg
505
505 506 errors.append((f, lineno and n + 1, l, msg, bd))
506 507 result = False
507 508
508 509 errors.sort()
509 510 for e in errors:
510 511 logfunc(*e)
511 512 fc += 1
512 513 if maxerr and fc >= maxerr:
513 514 print " (too many errors, giving up)"
514 515 break
515 516
516 517 return result
517 518
518 519 if __name__ == "__main__":
519 520 parser = optparse.OptionParser("%prog [options] [files]")
520 521 parser.add_option("-w", "--warnings", action="store_true",
521 522 help="include warning-level checks")
522 523 parser.add_option("-p", "--per-file", type="int",
523 524 help="max warnings per file")
524 525 parser.add_option("-b", "--blame", action="store_true",
525 526 help="use annotate to generate blame info")
526 527 parser.add_option("", "--debug", action="store_true",
527 528 help="show debug information")
528 529 parser.add_option("", "--nolineno", action="store_false",
529 530 dest='lineno', help="don't show line numbers")
530 531
531 532 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
532 533 lineno=True)
533 534 (options, args) = parser.parse_args()
534 535
535 536 if len(args) == 0:
536 537 check = glob.glob("*")
537 538 else:
538 539 check = args
539 540
540 541 ret = 0
541 542 for f in check:
542 543 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
543 544 blame=options.blame, debug=options.debug,
544 545 lineno=options.lineno):
545 546 ret = 1
546 547 sys.exit(ret)
@@ -1,245 +1,253 b''
1 1 $ cat > correct.py <<EOF
2 2 > def toto(arg1, arg2):
3 3 > del arg2
4 4 > return (5 + 6, 9)
5 5 > EOF
6 6 $ cat > wrong.py <<EOF
7 7 > def toto( arg1, arg2):
8 8 > del(arg2)
9 9 > return ( 5+6, 9)
10 10 > EOF
11 11 $ cat > quote.py <<EOF
12 12 > # let's use quote in comments
13 13 > (''' ( 4x5 )
14 14 > but """\\''' and finally''',
15 15 > """let's fool checkpatch""", '1+2',
16 16 > '"""', 42+1, """and
17 17 > ( 4-1 ) """, "( 1+1 )\" and ")
18 18 > a, '\\\\\\\\', "\\\\\\" x-2", "c-1"
19 19 > EOF
20 20 $ cat > non-py24.py <<EOF
21 21 > # Using builtins that does not exist in Python 2.4
22 22 > if any():
23 23 > x = all()
24 24 > y = format(x)
25 25 > # next(generator) is new in 2.6
26 26 > z = next(x)
27 27 > # but generator.next() is okay
28 28 > x.next()
29 29 > # and we can make our own next
30 30 > def next(stuff):
31 31 > pass
32 32 >
33 33 > # Do not complain about our own definition
34 34 > def any(x):
35 35 > pass
36 36 >
37 37 > # try/except/finally block does not exist in Python 2.4
38 38 > try:
39 39 > pass
40 40 > except StandardError, inst:
41 41 > pass
42 42 > finally:
43 43 > pass
44 44 >
45 45 > # nested try/finally+try/except is allowed
46 46 > try:
47 47 > try:
48 48 > pass
49 49 > except StandardError, inst:
50 50 > pass
51 51 > finally:
52 52 > pass
53 53 >
54 54 > # yield inside a try/finally block is not allowed in Python 2.4
55 55 > try:
56 56 > pass
57 57 > yield 1
58 58 > finally:
59 59 > pass
60 60 > try:
61 61 > yield
62 62 > pass
63 63 > finally:
64 64 > pass
65 65 >
66 66 > EOF
67 67 $ cat > classstyle.py <<EOF
68 68 > class newstyle_class(object):
69 69 > pass
70 70 >
71 71 > class oldstyle_class:
72 72 > pass
73 73 >
74 74 > class empty():
75 75 > pass
76 76 >
77 77 > no_class = 1:
78 78 > pass
79 79 > EOF
80 80 $ check_code="$TESTDIR"/../contrib/check-code.py
81 81 $ "$check_code" ./wrong.py ./correct.py ./quote.py ./non-py24.py ./classstyle.py
82 82 ./wrong.py:1:
83 83 > def toto( arg1, arg2):
84 84 gratuitous whitespace in () or []
85 85 ./wrong.py:2:
86 86 > del(arg2)
87 87 Python keyword is not a function
88 88 ./wrong.py:3:
89 89 > return ( 5+6, 9)
90 90 gratuitous whitespace in () or []
91 91 missing whitespace in expression
92 92 ./quote.py:5:
93 93 > '"""', 42+1, """and
94 94 missing whitespace in expression
95 95 ./non-py24.py:2:
96 96 > if any():
97 97 any/all/format not available in Python 2.4
98 98 ./non-py24.py:3:
99 99 > x = all()
100 100 any/all/format not available in Python 2.4
101 101 ./non-py24.py:4:
102 102 > y = format(x)
103 103 any/all/format not available in Python 2.4
104 104 ./non-py24.py:6:
105 105 > z = next(x)
106 106 no next(foo) in Python 2.4 and 2.5, use foo.next() instead
107 107 ./non-py24.py:18:
108 108 > try:
109 109 no try/except/finally in Python 2.4
110 110 ./non-py24.py:35:
111 111 > try:
112 112 no yield inside try/finally in Python 2.4
113 113 ./non-py24.py:40:
114 114 > try:
115 115 no yield inside try/finally in Python 2.4
116 116 ./classstyle.py:4:
117 117 > class oldstyle_class:
118 118 old-style class, use class foo(object)
119 119 ./classstyle.py:7:
120 120 > class empty():
121 121 class foo() not available in Python 2.4, use class foo(object)
122 122 [1]
123 123 $ cat > python3-compat.py << EOF
124 124 > foo <> bar
125 125 > reduce(lambda a, b: a + b, [1, 2, 3, 4])
126 126 > EOF
127 127 $ "$check_code" python3-compat.py
128 128 python3-compat.py:1:
129 129 > foo <> bar
130 130 <> operator is not available in Python 3+, use !=
131 131 python3-compat.py:2:
132 132 > reduce(lambda a, b: a + b, [1, 2, 3, 4])
133 133 reduce is not available in Python 3+
134 134 [1]
135 135
136 136 $ cat > is-op.py <<EOF
137 137 > # is-operator comparing number or string literal
138 138 > x = None
139 139 > y = x is 'foo'
140 140 > y = x is "foo"
141 141 > y = x is 5346
142 142 > y = x is -6
143 143 > y = x is not 'foo'
144 144 > y = x is not "foo"
145 145 > y = x is not 5346
146 146 > y = x is not -6
147 147 > EOF
148 148
149 149 $ "$check_code" ./is-op.py
150 150 ./is-op.py:3:
151 151 > y = x is 'foo'
152 152 object comparison with literal
153 153 ./is-op.py:4:
154 154 > y = x is "foo"
155 155 object comparison with literal
156 156 ./is-op.py:5:
157 157 > y = x is 5346
158 158 object comparison with literal
159 159 ./is-op.py:6:
160 160 > y = x is -6
161 161 object comparison with literal
162 162 ./is-op.py:7:
163 163 > y = x is not 'foo'
164 164 object comparison with literal
165 165 ./is-op.py:8:
166 166 > y = x is not "foo"
167 167 object comparison with literal
168 168 ./is-op.py:9:
169 169 > y = x is not 5346
170 170 object comparison with literal
171 171 ./is-op.py:10:
172 172 > y = x is not -6
173 173 object comparison with literal
174 174 [1]
175 175
176 176 $ cat > for-nolineno.py <<EOF
177 177 > except:
178 178 > EOF
179 179 $ "$check_code" for-nolineno.py --nolineno
180 180 for-nolineno.py:0:
181 181 > except:
182 182 naked except clause
183 183 [1]
184 184
185 185 $ cat > warning.t <<EOF
186 186 > $ function warnonly {
187 187 > > }
188 > $ diff -N aaa
189 > $ function onwarn {}
188 190 > EOF
189 191 $ "$check_code" warning.t
190 192 $ "$check_code" --warn warning.t
191 193 warning.t:1:
192 194 > $ function warnonly {
193 195 warning: don't use 'function', use old style
196 warning.t:3:
197 > $ diff -N aaa
198 warning: don't use 'diff -N'
199 warning.t:4:
200 > $ function onwarn {}
201 warning: don't use 'function', use old style
194 202 [1]
195 203 $ cat > raise-format.py <<EOF
196 204 > raise SomeException, message
197 205 > # this next line is okay
198 206 > raise SomeException(arg1, arg2)
199 207 > EOF
200 208 $ "$check_code" not-existing.py raise-format.py
201 209 Skipping*not-existing.py* (glob)
202 210 raise-format.py:1:
203 211 > raise SomeException, message
204 212 don't use old-style two-argument raise, use Exception(message)
205 213 [1]
206 214
207 215 $ cat > rst.py <<EOF
208 216 > """problematic rst text
209 217 >
210 218 > .. note::
211 219 > wrong
212 220 > """
213 221 >
214 222 > '''
215 223 >
216 224 > .. note::
217 225 >
218 226 > valid
219 227 >
220 228 > new text
221 229 >
222 230 > .. note::
223 231 >
224 232 > also valid
225 233 > '''
226 234 >
227 235 > """mixed
228 236 >
229 237 > .. note::
230 238 >
231 239 > good
232 240 >
233 241 > .. note::
234 242 > plus bad
235 243 > """
236 244 > EOF
237 245 $ $check_code -w rst.py
238 246 rst.py:3:
239 247 > .. note::
240 248 warning: add two newlines after '.. note::'
241 249 rst.py:26:
242 250 > .. note::
243 251 warning: add two newlines after '.. note::'
244 252 [1]
245 253
General Comments 0
You need to be logged in to leave comments. Login now