##// END OF EJS Templates
check-code: extend try/except/finally check for multiple except clauses
Matt Mackall -
r22175:29658044 default
parent child Browse files
Show More
@@ -1,579 +1,580
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'(^| )\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 ],
126 126 # warnings
127 127 [
128 128 (r'^function', "don't use 'function', use old style"),
129 129 (r'^diff.*-\w*N', "don't use 'diff -N'"),
130 130 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
131 131 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
132 132 (r'kill (`|\$\()', "don't use kill, use killdaemons.py")
133 133 ]
134 134 ]
135 135
136 136 testfilters = [
137 137 (r"( *)(#([^\n]*\S)?)", repcomment),
138 138 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
139 139 ]
140 140
141 141 winglobmsg = "use (glob) to match Windows paths too"
142 142 uprefix = r"^ \$ "
143 143 utestpats = [
144 144 [
145 145 (r'^(\S.*|| [$>] .*)[ \t]\n', "trailing whitespace on non-output"),
146 146 (uprefix + r'.*\|\s*sed[^|>\n]*\n',
147 147 "use regex test output patterns instead of sed"),
148 148 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
149 149 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
150 150 (uprefix + r'.*\|\| echo.*(fail|error)',
151 151 "explicit exit code checks unnecessary"),
152 152 (uprefix + r'set -e', "don't use set -e"),
153 153 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
154 154 (uprefix + r'.*:\.\S*/', "x:.y in a path does not work on msys, rewrite "
155 155 "as x://.y, or see `hg log -k msys` for alternatives", r'-\S+:\.|' #-Rxxx
156 156 'hg pull -q file:../test'), # in test-pull.t which is skipped on windows
157 157 (r'^ saved backup bundle to \$TESTTMP.*\.hg$', winglobmsg),
158 158 (r'^ changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
159 159 winglobmsg),
160 160 (r'^ pulling from \$TESTTMP/.*[^)]$', winglobmsg,
161 161 '\$TESTTMP/unix-repo$'), # in test-issue1802.t which skipped on windows
162 162 (r'^ reverting .*/.*[^)]$', winglobmsg),
163 163 (r'^ cloning subrepo \S+/.*[^)]$', winglobmsg),
164 164 (r'^ pushing to \$TESTTMP/.*[^)]$', winglobmsg),
165 165 (r'^ pushing subrepo \S+/\S+ to.*[^)]$', winglobmsg),
166 166 (r'^ moving \S+/.*[^)]$', winglobmsg),
167 167 (r'^ no changes made to subrepo since.*/.*[^)]$', winglobmsg),
168 168 (r'^ .*: largefile \S+ not available from file:.*/.*[^)]$', winglobmsg),
169 169 (r'^ .*file://\$TESTTMP',
170 170 'write "file:/*/$TESTTMP" + (glob) to match on windows too'),
171 171 (r'^ (cat|find): .*: No such file or directory',
172 172 'use test -f to test for file existence'),
173 173 ],
174 174 # warnings
175 175 [
176 176 (r'^ [^*?/\n]* \(glob\)$',
177 177 "glob match with no glob character (?*/)"),
178 178 ]
179 179 ]
180 180
181 181 for i in [0, 1]:
182 182 for tp in testpats[i]:
183 183 p = tp[0]
184 184 m = tp[1]
185 185 if p.startswith(r'^'):
186 186 p = r"^ [$>] (%s)" % p[1:]
187 187 else:
188 188 p = r"^ [$>] .*(%s)" % p
189 189 utestpats[i].append((p, m) + tp[2:])
190 190
191 191 utestfilters = [
192 192 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
193 193 (r"( *)(#([^\n]*\S)?)", repcomment),
194 194 ]
195 195
196 196 pypats = [
197 197 [
198 198 (r'\([^)]*\*\w[^()]+\w+=', "can't pass varargs with keyword in Py2.5"),
199 199 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
200 200 "tuple parameter unpacking not available in Python 3+"),
201 201 (r'lambda\s*\(.*,.*\)',
202 202 "tuple parameter unpacking not available in Python 3+"),
203 203 (r'import (.+,[^.]+\.[^.]+|[^.]+\.[^.]+,)',
204 204 '2to3 can\'t always rewrite "import qux, foo.bar", '
205 205 'use "import foo.bar" on its own line instead.'),
206 206 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
207 207 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
208 208 (r'dict\(.*=', 'dict() is different in Py2 and 3 and is slower than {}',
209 209 'dict-from-generator'),
210 210 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
211 211 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
212 212 (r'^\s*\t', "don't use tabs"),
213 213 (r'\S;\s*\n', "semicolon"),
214 214 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
215 215 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
216 216 (r'(\w|\)),\w', "missing whitespace after ,"),
217 217 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
218 218 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
219 (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)\1except.*?:\n'
220 r'((?:\n|\1\s.*\n)+?)\1finally:', 'no try/except/finally in Python 2.4'),
219 (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)(\1except.*?:\n'
220 r'((?:\n|\1\s.*\n)+?))+\1finally:',
221 'no try/except/finally in Python 2.4'),
221 222 (r'(?<!def)(\s+|^|\()next\(.+\)',
222 223 'no next(foo) in Python 2.4 and 2.5, use foo.next() instead'),
223 224 (r'(\s+)try:\n((?:\n|\1\s.*\n)*?)\1\s*yield\b.*?'
224 225 r'((?:\n|\1\s.*\n)+?)\1finally:',
225 226 'no yield inside try/finally in Python 2.4'),
226 227 (r'.{81}', "line too long"),
227 228 (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
228 229 (r'[^\n]\Z', "no trailing newline"),
229 230 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
230 231 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
231 232 # "don't use underbars in identifiers"),
232 233 (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
233 234 "don't use camelcase in identifiers"),
234 235 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
235 236 "linebreak after :"),
236 237 (r'class\s[^( \n]+:', "old-style class, use class foo(object)"),
237 238 (r'class\s[^( \n]+\(\):',
238 239 "class foo() not available in Python 2.4, use class foo(object)"),
239 240 (r'\b(%s)\(' % '|'.join(keyword.kwlist),
240 241 "Python keyword is not a function"),
241 242 (r',]', "unneeded trailing ',' in list"),
242 243 # (r'class\s[A-Z][^\(]*\((?!Exception)',
243 244 # "don't capitalize non-exception classes"),
244 245 # (r'in range\(', "use xrange"),
245 246 # (r'^\s*print\s+', "avoid using print in core and extensions"),
246 247 (r'[\x80-\xff]', "non-ASCII character literal"),
247 248 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
248 249 (r'^\s*with\s+', "with not available in Python 2.4"),
249 250 (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"),
250 251 (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
251 252 (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
252 253 (r'(?<!def)\s+(any|all|format)\(',
253 254 "any/all/format not available in Python 2.4", 'no-py24'),
254 255 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
255 256 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
256 257 "gratuitous whitespace after Python keyword"),
257 258 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
258 259 # (r'\s\s=', "gratuitous whitespace before ="),
259 260 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
260 261 "missing whitespace around operator"),
261 262 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
262 263 "missing whitespace around operator"),
263 264 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
264 265 "missing whitespace around operator"),
265 266 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
266 267 "wrong whitespace around ="),
267 268 (r'\([^()]*( =[^=]|[^<>!=]= )',
268 269 "no whitespace around = for named parameters"),
269 270 (r'raise Exception', "don't raise generic exceptions"),
270 271 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
271 272 "don't use old-style two-argument raise, use Exception(message)"),
272 273 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
273 274 (r' [=!]=\s+(True|False|None)',
274 275 "comparison with singleton, use 'is' or 'is not' instead"),
275 276 (r'^\s*(while|if) [01]:',
276 277 "use True/False for constant Boolean expression"),
277 278 (r'(?:(?<!def)\s+|\()hasattr',
278 279 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
279 280 (r'opener\([^)]*\).read\(',
280 281 "use opener.read() instead"),
281 282 (r'BaseException', 'not in Python 2.4, use Exception'),
282 283 (r'os\.path\.relpath', 'os.path.relpath is not in Python 2.5'),
283 284 (r'opener\([^)]*\).write\(',
284 285 "use opener.write() instead"),
285 286 (r'[\s\(](open|file)\([^)]*\)\.read\(',
286 287 "use util.readfile() instead"),
287 288 (r'[\s\(](open|file)\([^)]*\)\.write\(',
288 289 "use util.writefile() instead"),
289 290 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
290 291 "always assign an opened file to a variable, and close it afterwards"),
291 292 (r'[\s\(](open|file)\([^)]*\)\.',
292 293 "always assign an opened file to a variable, and close it afterwards"),
293 294 (r'(?i)descendent', "the proper spelling is descendAnt"),
294 295 (r'\.debug\(\_', "don't mark debug messages for translation"),
295 296 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
296 297 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
297 298 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
298 299 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
299 300 "missing _() in ui message (use () to hide false-positives)"),
300 301 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
301 302 ],
302 303 # warnings
303 304 [
304 305 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
305 306 ]
306 307 ]
307 308
308 309 pyfilters = [
309 310 (r"""(?msx)(?P<comment>\#.*?$)|
310 311 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
311 312 (?P<text>(([^\\]|\\.)*?))
312 313 (?P=quote))""", reppython),
313 314 ]
314 315
315 316 txtfilters = []
316 317
317 318 txtpats = [
318 319 [
319 320 ('\s$', 'trailing whitespace'),
320 321 ('.. note::[ \n][^\n]', 'add two newlines after note::')
321 322 ],
322 323 []
323 324 ]
324 325
325 326 cpats = [
326 327 [
327 328 (r'//', "don't use //-style comments"),
328 329 (r'^ ', "don't use spaces to indent"),
329 330 (r'\S\t', "don't use tabs except for indent"),
330 331 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
331 332 (r'.{81}', "line too long"),
332 333 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
333 334 (r'return\(', "return is not a function"),
334 335 (r' ;', "no space before ;"),
335 336 (r'[)][{]', "space between ) and {"),
336 337 (r'\w+\* \w+', "use int *foo, not int* foo"),
337 338 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
338 339 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
339 340 (r'\w,\w', "missing whitespace after ,"),
340 341 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
341 342 (r'^#\s+\w', "use #foo, not # foo"),
342 343 (r'[^\n]\Z', "no trailing newline"),
343 344 (r'^\s*#import\b', "use only #include in standard C code"),
344 345 ],
345 346 # warnings
346 347 []
347 348 ]
348 349
349 350 cfilters = [
350 351 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
351 352 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
352 353 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
353 354 (r'(\()([^)]+\))', repcallspaces),
354 355 ]
355 356
356 357 inutilpats = [
357 358 [
358 359 (r'\bui\.', "don't use ui in util"),
359 360 ],
360 361 # warnings
361 362 []
362 363 ]
363 364
364 365 inrevlogpats = [
365 366 [
366 367 (r'\brepo\.', "don't use repo in revlog"),
367 368 ],
368 369 # warnings
369 370 []
370 371 ]
371 372
372 373 webtemplatefilters = []
373 374
374 375 webtemplatepats = [
375 376 [],
376 377 [
377 378 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
378 379 'follow desc keyword with either firstline or websub'),
379 380 ]
380 381 ]
381 382
382 383 checks = [
383 384 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
384 385 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
385 386 ('c', r'.*\.[ch]$', '', cfilters, cpats),
386 387 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
387 388 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
388 389 pyfilters, inrevlogpats),
389 390 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
390 391 inutilpats),
391 392 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
392 393 ('web template', r'mercurial/templates/.*\.tmpl', '',
393 394 webtemplatefilters, webtemplatepats),
394 395 ]
395 396
396 397 def _preparepats():
397 398 for c in checks:
398 399 failandwarn = c[-1]
399 400 for pats in failandwarn:
400 401 for i, pseq in enumerate(pats):
401 402 # fix-up regexes for multi-line searches
402 403 p = pseq[0]
403 404 # \s doesn't match \n
404 405 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
405 406 # [^...] doesn't match newline
406 407 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
407 408
408 409 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
409 410 filters = c[3]
410 411 for i, flt in enumerate(filters):
411 412 filters[i] = re.compile(flt[0]), flt[1]
412 413 _preparepats()
413 414
414 415 class norepeatlogger(object):
415 416 def __init__(self):
416 417 self._lastseen = None
417 418
418 419 def log(self, fname, lineno, line, msg, blame):
419 420 """print error related a to given line of a given file.
420 421
421 422 The faulty line will also be printed but only once in the case
422 423 of multiple errors.
423 424
424 425 :fname: filename
425 426 :lineno: line number
426 427 :line: actual content of the line
427 428 :msg: error message
428 429 """
429 430 msgid = fname, lineno, line
430 431 if msgid != self._lastseen:
431 432 if blame:
432 433 print "%s:%d (%s):" % (fname, lineno, blame)
433 434 else:
434 435 print "%s:%d:" % (fname, lineno)
435 436 print " > %s" % line
436 437 self._lastseen = msgid
437 438 print " " + msg
438 439
439 440 _defaultlogger = norepeatlogger()
440 441
441 442 def getblame(f):
442 443 lines = []
443 444 for l in os.popen('hg annotate -un %s' % f):
444 445 start, line = l.split(':', 1)
445 446 user, rev = start.split()
446 447 lines.append((line[1:-1], user, rev))
447 448 return lines
448 449
449 450 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
450 451 blame=False, debug=False, lineno=True):
451 452 """checks style and portability of a given file
452 453
453 454 :f: filepath
454 455 :logfunc: function used to report error
455 456 logfunc(filename, linenumber, linecontent, errormessage)
456 457 :maxerr: number of error to display before aborting.
457 458 Set to false (default) to report all errors
458 459
459 460 return True if no error is found, False otherwise.
460 461 """
461 462 blamecache = None
462 463 result = True
463 464
464 465 try:
465 466 fp = open(f)
466 467 except IOError, e:
467 468 print "Skipping %s, %s" % (f, str(e).split(':', 1)[0])
468 469 return result
469 470 pre = post = fp.read()
470 471 fp.close()
471 472
472 473 for name, match, magic, filters, pats in checks:
473 474 if debug:
474 475 print name, f
475 476 fc = 0
476 477 if not (re.match(match, f) or (magic and re.search(magic, f))):
477 478 if debug:
478 479 print "Skipping %s for %s it doesn't match %s" % (
479 480 name, match, f)
480 481 continue
481 482 if "no-" "check-code" in pre:
482 483 print "Skipping %s it has no-" "check-code" % f
483 484 return "Skip" # skip checking this file
484 485 for p, r in filters:
485 486 post = re.sub(p, r, post)
486 487 nerrs = len(pats[0]) # nerr elements are errors
487 488 if warnings:
488 489 pats = pats[0] + pats[1]
489 490 else:
490 491 pats = pats[0]
491 492 # print post # uncomment to show filtered version
492 493
493 494 if debug:
494 495 print "Checking %s for %s" % (name, f)
495 496
496 497 prelines = None
497 498 errors = []
498 499 for i, pat in enumerate(pats):
499 500 if len(pat) == 3:
500 501 p, msg, ignore = pat
501 502 else:
502 503 p, msg = pat
503 504 ignore = None
504 505 if i >= nerrs:
505 506 msg = "warning: " + msg
506 507
507 508 pos = 0
508 509 n = 0
509 510 for m in p.finditer(post):
510 511 if prelines is None:
511 512 prelines = pre.splitlines()
512 513 postlines = post.splitlines(True)
513 514
514 515 start = m.start()
515 516 while n < len(postlines):
516 517 step = len(postlines[n])
517 518 if pos + step > start:
518 519 break
519 520 pos += step
520 521 n += 1
521 522 l = prelines[n]
522 523
523 524 if ignore and re.search(ignore, l, re.MULTILINE):
524 525 if debug:
525 526 print "Skipping %s for %s:%s (ignore pattern)" % (
526 527 name, f, n)
527 528 continue
528 529 bd = ""
529 530 if blame:
530 531 bd = 'working directory'
531 532 if not blamecache:
532 533 blamecache = getblame(f)
533 534 if n < len(blamecache):
534 535 bl, bu, br = blamecache[n]
535 536 if bl == l:
536 537 bd = '%s@%s' % (bu, br)
537 538
538 539 errors.append((f, lineno and n + 1, l, msg, bd))
539 540 result = False
540 541
541 542 errors.sort()
542 543 for e in errors:
543 544 logfunc(*e)
544 545 fc += 1
545 546 if maxerr and fc >= maxerr:
546 547 print " (too many errors, giving up)"
547 548 break
548 549
549 550 return result
550 551
551 552 if __name__ == "__main__":
552 553 parser = optparse.OptionParser("%prog [options] [files]")
553 554 parser.add_option("-w", "--warnings", action="store_true",
554 555 help="include warning-level checks")
555 556 parser.add_option("-p", "--per-file", type="int",
556 557 help="max warnings per file")
557 558 parser.add_option("-b", "--blame", action="store_true",
558 559 help="use annotate to generate blame info")
559 560 parser.add_option("", "--debug", action="store_true",
560 561 help="show debug information")
561 562 parser.add_option("", "--nolineno", action="store_false",
562 563 dest='lineno', help="don't show line numbers")
563 564
564 565 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
565 566 lineno=True)
566 567 (options, args) = parser.parse_args()
567 568
568 569 if len(args) == 0:
569 570 check = glob.glob("*")
570 571 else:
571 572 check = args
572 573
573 574 ret = 0
574 575 for f in check:
575 576 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
576 577 blame=options.blame, debug=options.debug,
577 578 lineno=options.lineno):
578 579 ret = 1
579 580 sys.exit(ret)
General Comments 0
You need to be logged in to leave comments. Login now