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