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