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