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