##// END OF EJS Templates
check-code: forbid mutable value for default argument...
Pierre-Yves David -
r26348:b80b2ee7 default
parent child Browse files
Show More
@@ -1,573 +1,575
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 284 (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
285 285 'legacy exception syntax; use "as" instead of ","'),
286 286 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
287 287 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
288 288 "missing _() in ui message (use () to hide false-positives)"),
289 289 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
290 290 (r'\b__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
291 291 (r'os\.path\.join\(.*, *(""|\'\')\)',
292 292 "use pathutil.normasprefix(path) instead of os.path.join(path, '')"),
293 293 (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
294 # XXX only catch mutable arguments on the first line of the definition
295 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
294 296 ],
295 297 # warnings
296 298 [
297 299 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
298 300 ]
299 301 ]
300 302
301 303 pyfilters = [
302 304 (r"""(?msx)(?P<comment>\#.*?$)|
303 305 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
304 306 (?P<text>(([^\\]|\\.)*?))
305 307 (?P=quote))""", reppython),
306 308 ]
307 309
308 310 txtfilters = []
309 311
310 312 txtpats = [
311 313 [
312 314 ('\s$', 'trailing whitespace'),
313 315 ('.. note::[ \n][^\n]', 'add two newlines after note::')
314 316 ],
315 317 []
316 318 ]
317 319
318 320 cpats = [
319 321 [
320 322 (r'//', "don't use //-style comments"),
321 323 (r'^ ', "don't use spaces to indent"),
322 324 (r'\S\t', "don't use tabs except for indent"),
323 325 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
324 326 (r'.{81}', "line too long"),
325 327 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
326 328 (r'return\(', "return is not a function"),
327 329 (r' ;', "no space before ;"),
328 330 (r'[^;] \)', "no space before )"),
329 331 (r'[)][{]', "space between ) and {"),
330 332 (r'\w+\* \w+', "use int *foo, not int* foo"),
331 333 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
332 334 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
333 335 (r'\w,\w', "missing whitespace after ,"),
334 336 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
335 337 (r'^#\s+\w', "use #foo, not # foo"),
336 338 (r'[^\n]\Z', "no trailing newline"),
337 339 (r'^\s*#import\b', "use only #include in standard C code"),
338 340 ],
339 341 # warnings
340 342 []
341 343 ]
342 344
343 345 cfilters = [
344 346 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
345 347 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
346 348 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
347 349 (r'(\()([^)]+\))', repcallspaces),
348 350 ]
349 351
350 352 inutilpats = [
351 353 [
352 354 (r'\bui\.', "don't use ui in util"),
353 355 ],
354 356 # warnings
355 357 []
356 358 ]
357 359
358 360 inrevlogpats = [
359 361 [
360 362 (r'\brepo\.', "don't use repo in revlog"),
361 363 ],
362 364 # warnings
363 365 []
364 366 ]
365 367
366 368 webtemplatefilters = []
367 369
368 370 webtemplatepats = [
369 371 [],
370 372 [
371 373 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
372 374 'follow desc keyword with either firstline or websub'),
373 375 ]
374 376 ]
375 377
376 378 checks = [
377 379 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
378 380 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
379 381 ('c', r'.*\.[ch]$', '', cfilters, cpats),
380 382 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
381 383 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
382 384 pyfilters, inrevlogpats),
383 385 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
384 386 inutilpats),
385 387 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
386 388 ('web template', r'mercurial/templates/.*\.tmpl', '',
387 389 webtemplatefilters, webtemplatepats),
388 390 ]
389 391
390 392 def _preparepats():
391 393 for c in checks:
392 394 failandwarn = c[-1]
393 395 for pats in failandwarn:
394 396 for i, pseq in enumerate(pats):
395 397 # fix-up regexes for multi-line searches
396 398 p = pseq[0]
397 399 # \s doesn't match \n
398 400 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
399 401 # [^...] doesn't match newline
400 402 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
401 403
402 404 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
403 405 filters = c[3]
404 406 for i, flt in enumerate(filters):
405 407 filters[i] = re.compile(flt[0]), flt[1]
406 408 _preparepats()
407 409
408 410 class norepeatlogger(object):
409 411 def __init__(self):
410 412 self._lastseen = None
411 413
412 414 def log(self, fname, lineno, line, msg, blame):
413 415 """print error related a to given line of a given file.
414 416
415 417 The faulty line will also be printed but only once in the case
416 418 of multiple errors.
417 419
418 420 :fname: filename
419 421 :lineno: line number
420 422 :line: actual content of the line
421 423 :msg: error message
422 424 """
423 425 msgid = fname, lineno, line
424 426 if msgid != self._lastseen:
425 427 if blame:
426 428 print "%s:%d (%s):" % (fname, lineno, blame)
427 429 else:
428 430 print "%s:%d:" % (fname, lineno)
429 431 print " > %s" % line
430 432 self._lastseen = msgid
431 433 print " " + msg
432 434
433 435 _defaultlogger = norepeatlogger()
434 436
435 437 def getblame(f):
436 438 lines = []
437 439 for l in os.popen('hg annotate -un %s' % f):
438 440 start, line = l.split(':', 1)
439 441 user, rev = start.split()
440 442 lines.append((line[1:-1], user, rev))
441 443 return lines
442 444
443 445 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
444 446 blame=False, debug=False, lineno=True):
445 447 """checks style and portability of a given file
446 448
447 449 :f: filepath
448 450 :logfunc: function used to report error
449 451 logfunc(filename, linenumber, linecontent, errormessage)
450 452 :maxerr: number of error to display before aborting.
451 453 Set to false (default) to report all errors
452 454
453 455 return True if no error is found, False otherwise.
454 456 """
455 457 blamecache = None
456 458 result = True
457 459
458 460 try:
459 461 fp = open(f)
460 462 except IOError as e:
461 463 print "Skipping %s, %s" % (f, str(e).split(':', 1)[0])
462 464 return result
463 465 pre = post = fp.read()
464 466 fp.close()
465 467
466 468 for name, match, magic, filters, pats in checks:
467 469 if debug:
468 470 print name, f
469 471 fc = 0
470 472 if not (re.match(match, f) or (magic and re.search(magic, f))):
471 473 if debug:
472 474 print "Skipping %s for %s it doesn't match %s" % (
473 475 name, match, f)
474 476 continue
475 477 if "no-" "check-code" in pre:
476 478 print "Skipping %s it has no-" "check-code" % f
477 479 return "Skip" # skip checking this file
478 480 for p, r in filters:
479 481 post = re.sub(p, r, post)
480 482 nerrs = len(pats[0]) # nerr elements are errors
481 483 if warnings:
482 484 pats = pats[0] + pats[1]
483 485 else:
484 486 pats = pats[0]
485 487 # print post # uncomment to show filtered version
486 488
487 489 if debug:
488 490 print "Checking %s for %s" % (name, f)
489 491
490 492 prelines = None
491 493 errors = []
492 494 for i, pat in enumerate(pats):
493 495 if len(pat) == 3:
494 496 p, msg, ignore = pat
495 497 else:
496 498 p, msg = pat
497 499 ignore = None
498 500 if i >= nerrs:
499 501 msg = "warning: " + msg
500 502
501 503 pos = 0
502 504 n = 0
503 505 for m in p.finditer(post):
504 506 if prelines is None:
505 507 prelines = pre.splitlines()
506 508 postlines = post.splitlines(True)
507 509
508 510 start = m.start()
509 511 while n < len(postlines):
510 512 step = len(postlines[n])
511 513 if pos + step > start:
512 514 break
513 515 pos += step
514 516 n += 1
515 517 l = prelines[n]
516 518
517 519 if ignore and re.search(ignore, l, re.MULTILINE):
518 520 if debug:
519 521 print "Skipping %s for %s:%s (ignore pattern)" % (
520 522 name, f, n)
521 523 continue
522 524 bd = ""
523 525 if blame:
524 526 bd = 'working directory'
525 527 if not blamecache:
526 528 blamecache = getblame(f)
527 529 if n < len(blamecache):
528 530 bl, bu, br = blamecache[n]
529 531 if bl == l:
530 532 bd = '%s@%s' % (bu, br)
531 533
532 534 errors.append((f, lineno and n + 1, l, msg, bd))
533 535 result = False
534 536
535 537 errors.sort()
536 538 for e in errors:
537 539 logfunc(*e)
538 540 fc += 1
539 541 if maxerr and fc >= maxerr:
540 542 print " (too many errors, giving up)"
541 543 break
542 544
543 545 return result
544 546
545 547 if __name__ == "__main__":
546 548 parser = optparse.OptionParser("%prog [options] [files]")
547 549 parser.add_option("-w", "--warnings", action="store_true",
548 550 help="include warning-level checks")
549 551 parser.add_option("-p", "--per-file", type="int",
550 552 help="max warnings per file")
551 553 parser.add_option("-b", "--blame", action="store_true",
552 554 help="use annotate to generate blame info")
553 555 parser.add_option("", "--debug", action="store_true",
554 556 help="show debug information")
555 557 parser.add_option("", "--nolineno", action="store_false",
556 558 dest='lineno', help="don't show line numbers")
557 559
558 560 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
559 561 lineno=True)
560 562 (options, args) = parser.parse_args()
561 563
562 564 if len(args) == 0:
563 565 check = glob.glob("*")
564 566 else:
565 567 check = args
566 568
567 569 ret = 0
568 570 for f in check:
569 571 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
570 572 blame=options.blame, debug=options.debug,
571 573 lineno=options.lineno):
572 574 ret = 1
573 575 sys.exit(ret)
General Comments 0
You need to be logged in to leave comments. Login now