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