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