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