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