##// END OF EJS Templates
check-code: export needs a space to avoid false positives
timeless -
r27791:0029c2be default
parent child Browse files
Show More
@@ -1,587 +1,587 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 (r'export.*=', "don't export and assign at once"),
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*[uU].*$\n(^ \$ |^$)', "prefix diff -u/-U with cmp"),
126 126 (r'^\s+(if)? diff *-\w*[uU]', "prefix diff -u/-U with cmp"),
127 127 (r'seq ', "don't use 'seq', use $TESTDIR/seq.py"),
128 128 (r'\butil\.Abort\b', "directly use error.Abort"),
129 129 (r'\|&', "don't use |&, use 2>&1"),
130 130 (r'\w = +\w', "only one space after = allowed"),
131 131 ],
132 132 # warnings
133 133 [
134 134 (r'^function', "don't use 'function', use old style"),
135 135 (r'^diff.*-\w*N', "don't use 'diff -N'"),
136 136 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
137 137 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
138 138 (r'kill (`|\$\()', "don't use kill, use killdaemons.py")
139 139 ]
140 140 ]
141 141
142 142 testfilters = [
143 143 (r"( *)(#([^\n]*\S)?)", repcomment),
144 144 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
145 145 ]
146 146
147 147 winglobmsg = "use (glob) to match Windows paths too"
148 148 uprefix = r"^ \$ "
149 149 utestpats = [
150 150 [
151 151 (r'^(\S.*|| [$>] \S.*)[ \t]\n', "trailing whitespace on non-output"),
152 152 (uprefix + r'.*\|\s*sed[^|>\n]*\n',
153 153 "use regex test output patterns instead of sed"),
154 154 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
155 155 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
156 156 (uprefix + r'.*\|\| echo.*(fail|error)',
157 157 "explicit exit code checks unnecessary"),
158 158 (uprefix + r'set -e', "don't use set -e"),
159 159 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
160 160 (uprefix + r'.*:\.\S*/', "x:.y in a path does not work on msys, rewrite "
161 161 "as x://.y, or see `hg log -k msys` for alternatives", r'-\S+:\.|' #-Rxxx
162 162 '# no-msys'), # in test-pull.t which is skipped on windows
163 163 (r'^ saved backup bundle to \$TESTTMP.*\.hg$', winglobmsg),
164 164 (r'^ changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
165 165 winglobmsg),
166 166 (r'^ pulling from \$TESTTMP/.*[^)]$', winglobmsg,
167 167 '\$TESTTMP/unix-repo$'), # in test-issue1802.t which skipped on windows
168 168 (r'^ reverting (?!subrepo ).*/.*[^)]$', winglobmsg),
169 169 (r'^ cloning subrepo \S+/.*[^)]$', winglobmsg),
170 170 (r'^ pushing to \$TESTTMP/.*[^)]$', winglobmsg),
171 171 (r'^ pushing subrepo \S+/\S+ to.*[^)]$', winglobmsg),
172 172 (r'^ moving \S+/.*[^)]$', winglobmsg),
173 173 (r'^ no changes made to subrepo since.*/.*[^)]$', winglobmsg),
174 174 (r'^ .*: largefile \S+ not available from file:.*/.*[^)]$', winglobmsg),
175 175 (r'^ .*file://\$TESTTMP',
176 176 'write "file:/*/$TESTTMP" + (glob) to match on windows too'),
177 177 (r'^ (cat|find): .*: No such file or directory',
178 178 'use test -f to test for file existence'),
179 179 ],
180 180 # warnings
181 181 [
182 182 (r'^ [^*?/\n]* \(glob\)$',
183 183 "glob match with no glob character (?*/)"),
184 184 ]
185 185 ]
186 186
187 187 for i in [0, 1]:
188 188 for tp in testpats[i]:
189 189 p = tp[0]
190 190 m = tp[1]
191 191 if p.startswith(r'^'):
192 192 p = r"^ [$>] (%s)" % p[1:]
193 193 else:
194 194 p = r"^ [$>] .*(%s)" % p
195 195 utestpats[i].append((p, m) + tp[2:])
196 196
197 197 utestfilters = [
198 198 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
199 199 (r"( *)(#([^\n]*\S)?)", repcomment),
200 200 ]
201 201
202 202 pypats = [
203 203 [
204 204 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
205 205 "tuple parameter unpacking not available in Python 3+"),
206 206 (r'lambda\s*\(.*,.*\)',
207 207 "tuple parameter unpacking not available in Python 3+"),
208 208 (r'import (.+,[^.]+\.[^.]+|[^.]+\.[^.]+,)',
209 209 '2to3 can\'t always rewrite "import qux, foo.bar", '
210 210 'use "import foo.bar" on its own line instead.'),
211 211 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
212 212 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
213 213 (r'dict\(.*=', 'dict() is different in Py2 and 3 and is slower than {}',
214 214 'dict-from-generator'),
215 215 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
216 216 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
217 217 (r'^\s*\t', "don't use tabs"),
218 218 (r'\S;\s*\n', "semicolon"),
219 219 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
220 220 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
221 221 (r'(\w|\)),\w', "missing whitespace after ,"),
222 222 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
223 223 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
224 224 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
225 225 (r'.{81}', "line too long"),
226 226 (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
227 227 (r'[^\n]\Z', "no trailing newline"),
228 228 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
229 229 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
230 230 # "don't use underbars in identifiers"),
231 231 (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
232 232 "don't use camelcase in identifiers"),
233 233 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
234 234 "linebreak after :"),
235 235 (r'class\s[^( \n]+:', "old-style class, use class foo(object)"),
236 236 (r'class\s[^( \n]+\(\):',
237 237 "class foo() creates old style object, use class foo(object)"),
238 238 (r'\b(%s)\(' % '|'.join(k for k in keyword.kwlist
239 239 if k not in ('print', 'exec')),
240 240 "Python keyword is not a function"),
241 241 (r',]', "unneeded trailing ',' in list"),
242 242 # (r'class\s[A-Z][^\(]*\((?!Exception)',
243 243 # "don't capitalize non-exception classes"),
244 244 # (r'in range\(', "use xrange"),
245 245 # (r'^\s*print\s+', "avoid using print in core and extensions"),
246 246 (r'[\x80-\xff]', "non-ASCII character literal"),
247 247 (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"),
248 248 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
249 249 "gratuitous whitespace after Python keyword"),
250 250 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
251 251 # (r'\s\s=', "gratuitous whitespace before ="),
252 252 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
253 253 "missing whitespace around operator"),
254 254 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
255 255 "missing whitespace around operator"),
256 256 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
257 257 "missing whitespace around operator"),
258 258 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
259 259 "wrong whitespace around ="),
260 260 (r'\([^()]*( =[^=]|[^<>!=]= )',
261 261 "no whitespace around = for named parameters"),
262 262 (r'raise Exception', "don't raise generic exceptions"),
263 263 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
264 264 "don't use old-style two-argument raise, use Exception(message)"),
265 265 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
266 266 (r' [=!]=\s+(True|False|None)',
267 267 "comparison with singleton, use 'is' or 'is not' instead"),
268 268 (r'^\s*(while|if) [01]:',
269 269 "use True/False for constant Boolean expression"),
270 270 (r'(?:(?<!def)\s+|\()hasattr',
271 271 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
272 272 (r'opener\([^)]*\).read\(',
273 273 "use opener.read() instead"),
274 274 (r'opener\([^)]*\).write\(',
275 275 "use opener.write() instead"),
276 276 (r'[\s\(](open|file)\([^)]*\)\.read\(',
277 277 "use util.readfile() instead"),
278 278 (r'[\s\(](open|file)\([^)]*\)\.write\(',
279 279 "use util.writefile() instead"),
280 280 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
281 281 "always assign an opened file to a variable, and close it afterwards"),
282 282 (r'[\s\(](open|file)\([^)]*\)\.',
283 283 "always assign an opened file to a variable, and close it afterwards"),
284 284 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
285 285 (r'\.debug\(\_', "don't mark debug messages for translation"),
286 286 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
287 287 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
288 288 (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
289 289 'legacy exception syntax; use "as" instead of ","'),
290 290 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
291 291 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
292 292 "missing _() in ui message (use () to hide false-positives)"),
293 293 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
294 294 (r'\b__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
295 295 (r'os\.path\.join\(.*, *(""|\'\')\)',
296 296 "use pathutil.normasprefix(path) instead of os.path.join(path, '')"),
297 297 (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
298 298 # XXX only catch mutable arguments on the first line of the definition
299 299 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
300 300 (r'\butil\.Abort\b', "directly use error.Abort"),
301 301 ],
302 302 # warnings
303 303 [
304 304 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
305 305 ]
306 306 ]
307 307
308 308 pyfilters = [
309 309 (r"""(?msx)(?P<comment>\#.*?$)|
310 310 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
311 311 (?P<text>(([^\\]|\\.)*?))
312 312 (?P=quote))""", reppython),
313 313 ]
314 314
315 315 txtfilters = []
316 316
317 317 txtpats = [
318 318 [
319 319 ('\s$', 'trailing whitespace'),
320 320 ('.. note::[ \n][^\n]', 'add two newlines after note::')
321 321 ],
322 322 []
323 323 ]
324 324
325 325 cpats = [
326 326 [
327 327 (r'//', "don't use //-style comments"),
328 328 (r'^ ', "don't use spaces to indent"),
329 329 (r'\S\t', "don't use tabs except for indent"),
330 330 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
331 331 (r'.{81}', "line too long"),
332 332 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
333 333 (r'return\(', "return is not a function"),
334 334 (r' ;', "no space before ;"),
335 335 (r'[^;] \)', "no space before )"),
336 336 (r'[)][{]', "space between ) and {"),
337 337 (r'\w+\* \w+', "use int *foo, not int* foo"),
338 338 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
339 339 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
340 340 (r'\w,\w', "missing whitespace after ,"),
341 341 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
342 342 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
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 as 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 # If you're looking at this line, it's because a file has:
485 485 # no- check- code
486 486 # but the reason to output skipping is to make life for
487 487 # tests easier. So, instead of writing it with a normal
488 488 # spelling, we write it with the expected spelling from
489 489 # tests/test-check-code.t
490 490 print "Skipping %s it has no-che?k-code (glob)" % f
491 491 return "Skip" # skip checking this file
492 492 for p, r in filters:
493 493 post = re.sub(p, r, post)
494 494 nerrs = len(pats[0]) # nerr elements are errors
495 495 if warnings:
496 496 pats = pats[0] + pats[1]
497 497 else:
498 498 pats = pats[0]
499 499 # print post # uncomment to show filtered version
500 500
501 501 if debug:
502 502 print "Checking %s for %s" % (name, f)
503 503
504 504 prelines = None
505 505 errors = []
506 506 for i, pat in enumerate(pats):
507 507 if len(pat) == 3:
508 508 p, msg, ignore = pat
509 509 else:
510 510 p, msg = pat
511 511 ignore = None
512 512 if i >= nerrs:
513 513 msg = "warning: " + msg
514 514
515 515 pos = 0
516 516 n = 0
517 517 for m in p.finditer(post):
518 518 if prelines is None:
519 519 prelines = pre.splitlines()
520 520 postlines = post.splitlines(True)
521 521
522 522 start = m.start()
523 523 while n < len(postlines):
524 524 step = len(postlines[n])
525 525 if pos + step > start:
526 526 break
527 527 pos += step
528 528 n += 1
529 529 l = prelines[n]
530 530
531 531 if ignore and re.search(ignore, l, re.MULTILINE):
532 532 if debug:
533 533 print "Skipping %s for %s:%s (ignore pattern)" % (
534 534 name, f, n)
535 535 continue
536 536 bd = ""
537 537 if blame:
538 538 bd = 'working directory'
539 539 if not blamecache:
540 540 blamecache = getblame(f)
541 541 if n < len(blamecache):
542 542 bl, bu, br = blamecache[n]
543 543 if bl == l:
544 544 bd = '%s@%s' % (bu, br)
545 545
546 546 errors.append((f, lineno and n + 1, l, msg, bd))
547 547 result = False
548 548
549 549 errors.sort()
550 550 for e in errors:
551 551 logfunc(*e)
552 552 fc += 1
553 553 if maxerr and fc >= maxerr:
554 554 print " (too many errors, giving up)"
555 555 break
556 556
557 557 return result
558 558
559 559 if __name__ == "__main__":
560 560 parser = optparse.OptionParser("%prog [options] [files]")
561 561 parser.add_option("-w", "--warnings", action="store_true",
562 562 help="include warning-level checks")
563 563 parser.add_option("-p", "--per-file", type="int",
564 564 help="max warnings per file")
565 565 parser.add_option("-b", "--blame", action="store_true",
566 566 help="use annotate to generate blame info")
567 567 parser.add_option("", "--debug", action="store_true",
568 568 help="show debug information")
569 569 parser.add_option("", "--nolineno", action="store_false",
570 570 dest='lineno', help="don't show line numbers")
571 571
572 572 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
573 573 lineno=True)
574 574 (options, args) = parser.parse_args()
575 575
576 576 if len(args) == 0:
577 577 check = glob.glob("*")
578 578 else:
579 579 check = args
580 580
581 581 ret = 0
582 582 for f in check:
583 583 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
584 584 blame=options.blame, debug=options.debug,
585 585 lineno=options.lineno):
586 586 ret = 1
587 587 sys.exit(ret)
General Comments 0
You need to be logged in to leave comments. Login now