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