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