##// END OF EJS Templates
check-code: reject import urllib
timeless -
r28884:75309bad default
parent child Browse files
Show More
@@ -1,607 +1,609 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)
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 from __future__ import absolute_import, print_function
23 23 import glob
24 24 import keyword
25 25 import optparse
26 26 import os
27 27 import re
28 28 import sys
29 29 try:
30 30 import re2
31 31 except ImportError:
32 32 re2 = None
33 33
34 34 def compilere(pat, multiline=False):
35 35 if multiline:
36 36 pat = '(?m)' + pat
37 37 if re2:
38 38 try:
39 39 return re2.compile(pat)
40 40 except re2.error:
41 41 pass
42 42 return re.compile(pat)
43 43
44 44 def repquote(m):
45 45 fromc = '.:'
46 46 tochr = 'pq'
47 47 def encodechr(i):
48 48 if i > 255:
49 49 return 'u'
50 50 c = chr(i)
51 51 if c in ' \n':
52 52 return c
53 53 if c.isalpha():
54 54 return 'x'
55 55 if c.isdigit():
56 56 return 'n'
57 57 try:
58 58 return tochr[fromc.find(c)]
59 59 except (ValueError, IndexError):
60 60 return 'o'
61 61 t = m.group('text')
62 62 tt = ''.join(encodechr(i) for i in xrange(256))
63 63 t = t.translate(tt)
64 64 return m.group('quote') + t + m.group('quote')
65 65
66 66 def reppython(m):
67 67 comment = m.group('comment')
68 68 if comment:
69 69 l = len(comment.rstrip())
70 70 return "#" * l + comment[l:]
71 71 return repquote(m)
72 72
73 73 def repcomment(m):
74 74 return m.group(1) + "#" * len(m.group(2))
75 75
76 76 def repccomment(m):
77 77 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
78 78 return m.group(1) + t + "*/"
79 79
80 80 def repcallspaces(m):
81 81 t = re.sub(r"\n\s+", "\n", m.group(2))
82 82 return m.group(1) + t
83 83
84 84 def repinclude(m):
85 85 return m.group(1) + "<foo>"
86 86
87 87 def rephere(m):
88 88 t = re.sub(r"\S", "x", m.group(2))
89 89 return m.group(1) + t
90 90
91 91
92 92 testpats = [
93 93 [
94 94 (r'pushd|popd', "don't use 'pushd' or 'popd', use 'cd'"),
95 95 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
96 96 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
97 97 (r'(?<!hg )grep.* -a', "don't use 'grep -a', use in-line python"),
98 98 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
99 99 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
100 100 (r'echo -n', "don't use 'echo -n', use printf"),
101 101 (r'(^|\|\s*)\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"),
102 102 (r'head -c', "don't use 'head -c', use 'dd'"),
103 103 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
104 104 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
105 105 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
106 106 (r'printf.*[^\\]\\([1-9]|0\d)', "don't use 'printf \NNN', use Python"),
107 107 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
108 108 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
109 109 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
110 110 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
111 111 "use egrep for extended grep syntax"),
112 112 (r'/bin/', "don't use explicit paths for tools"),
113 113 (r'[^\n]\Z', "no trailing newline"),
114 114 (r'export .*=', "don't export and assign at once"),
115 115 (r'^source\b', "don't use 'source', use '.'"),
116 116 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
117 117 (r'ls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
118 118 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
119 119 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
120 120 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
121 121 (r'\[\[\s+[^\]]*\]\]', "don't use '[[ ]]', use '[ ]'"),
122 122 (r'^alias\b.*=', "don't use alias, use a function"),
123 123 (r'if\s*!', "don't use '!' to negate exit status"),
124 124 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
125 125 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
126 126 (r'^( *)\t', "don't use tabs to indent"),
127 127 (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
128 128 "put a backslash-escaped newline after sed 'i' command"),
129 129 (r'^diff *-\w*[uU].*$\n(^ \$ |^$)', "prefix diff -u/-U with cmp"),
130 130 (r'^\s+(if)? diff *-\w*[uU]', "prefix diff -u/-U with cmp"),
131 131 (r'seq ', "don't use 'seq', use $TESTDIR/seq.py"),
132 132 (r'\butil\.Abort\b', "directly use error.Abort"),
133 133 (r'\|&', "don't use |&, use 2>&1"),
134 134 (r'\w = +\w', "only one space after = allowed"),
135 135 (r'\bsed\b.*[^\\]\\n', "don't use 'sed ... \\n', use a \\ and a newline"),
136 136 ],
137 137 # warnings
138 138 [
139 139 (r'^function', "don't use 'function', use old style"),
140 140 (r'^diff.*-\w*N', "don't use 'diff -N'"),
141 141 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
142 142 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
143 143 (r'kill (`|\$\()', "don't use kill, use killdaemons.py")
144 144 ]
145 145 ]
146 146
147 147 testfilters = [
148 148 (r"( *)(#([^\n]*\S)?)", repcomment),
149 149 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
150 150 ]
151 151
152 152 winglobmsg = "use (glob) to match Windows paths too"
153 153 uprefix = r"^ \$ "
154 154 utestpats = [
155 155 [
156 156 (r'^(\S.*|| [$>] \S.*)[ \t]\n', "trailing whitespace on non-output"),
157 157 (uprefix + r'.*\|\s*sed[^|>\n]*\n',
158 158 "use regex test output patterns instead of sed"),
159 159 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
160 160 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
161 161 (uprefix + r'.*\|\| echo.*(fail|error)',
162 162 "explicit exit code checks unnecessary"),
163 163 (uprefix + r'set -e', "don't use set -e"),
164 164 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
165 165 (uprefix + r'.*:\.\S*/', "x:.y in a path does not work on msys, rewrite "
166 166 "as x://.y, or see `hg log -k msys` for alternatives", r'-\S+:\.|' #-Rxxx
167 167 '# no-msys'), # in test-pull.t which is skipped on windows
168 168 (r'^ saved backup bundle to \$TESTTMP.*\.hg$', winglobmsg),
169 169 (r'^ changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
170 170 winglobmsg),
171 171 (r'^ pulling from \$TESTTMP/.*[^)]$', winglobmsg,
172 172 '\$TESTTMP/unix-repo$'), # in test-issue1802.t which skipped on windows
173 173 (r'^ reverting (?!subrepo ).*/.*[^)]$', winglobmsg),
174 174 (r'^ cloning subrepo \S+/.*[^)]$', winglobmsg),
175 175 (r'^ pushing to \$TESTTMP/.*[^)]$', winglobmsg),
176 176 (r'^ pushing subrepo \S+/\S+ to.*[^)]$', winglobmsg),
177 177 (r'^ moving \S+/.*[^)]$', winglobmsg),
178 178 (r'^ no changes made to subrepo since.*/.*[^)]$', winglobmsg),
179 179 (r'^ .*: largefile \S+ not available from file:.*/.*[^)]$', winglobmsg),
180 180 (r'^ .*file://\$TESTTMP',
181 181 'write "file:/*/$TESTTMP" + (glob) to match on windows too'),
182 182 (r'^ (cat|find): .*: No such file or directory',
183 183 'use test -f to test for file existence'),
184 184 (r'^ diff -[^ -]*p',
185 185 "don't use (external) diff with -p for portability"),
186 186 (r'^ [-+][-+][-+] .* [-+]0000 \(glob\)',
187 187 "glob timezone field in diff output for portability"),
188 188 (r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@',
189 189 "use '@@ -N* +N,n @@ (glob)' style chunk header for portability"),
190 190 (r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@',
191 191 "use '@@ -N,n +N* @@ (glob)' style chunk header for portability"),
192 192 (r'^ @@ -[0-9]+ [+][0-9]+ @@',
193 193 "use '@@ -N* +N* @@ (glob)' style chunk header for portability"),
194 194 (uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff'
195 195 r'( +(-[^ po-]+|--(?!program|option)[^ ]+|[^-][^ ]*))*$',
196 196 "use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)"),
197 197 ],
198 198 # warnings
199 199 [
200 200 (r'^ [^*?/\n]* \(glob\)$',
201 201 "glob match with no glob character (?*/)"),
202 202 ]
203 203 ]
204 204
205 205 for i in [0, 1]:
206 206 for tp in testpats[i]:
207 207 p = tp[0]
208 208 m = tp[1]
209 209 if p.startswith(r'^'):
210 210 p = r"^ [$>] (%s)" % p[1:]
211 211 else:
212 212 p = r"^ [$>] .*(%s)" % p
213 213 utestpats[i].append((p, m) + tp[2:])
214 214
215 215 utestfilters = [
216 216 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
217 217 (r"( *)(#([^\n]*\S)?)", repcomment),
218 218 ]
219 219
220 220 pypats = [
221 221 [
222 222 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
223 223 "tuple parameter unpacking not available in Python 3+"),
224 224 (r'lambda\s*\(.*,.*\)',
225 225 "tuple parameter unpacking not available in Python 3+"),
226 226 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
227 227 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
228 228 (r'dict\(.*=', 'dict() is different in Py2 and 3 and is slower than {}',
229 229 'dict-from-generator'),
230 230 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
231 231 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
232 232 (r'^\s*\t', "don't use tabs"),
233 233 (r'\S;\s*\n', "semicolon"),
234 234 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
235 235 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
236 236 (r'(\w|\)),\w', "missing whitespace after ,"),
237 237 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
238 238 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
239 239 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
240 240 (r'.{81}', "line too long"),
241 241 (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
242 242 (r'[^\n]\Z', "no trailing newline"),
243 243 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
244 244 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
245 245 # "don't use underbars in identifiers"),
246 246 (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
247 247 "don't use camelcase in identifiers"),
248 248 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
249 249 "linebreak after :"),
250 250 (r'class\s[^( \n]+:', "old-style class, use class foo(object)",
251 251 r'#.*old-style'),
252 252 (r'class\s[^( \n]+\(\):',
253 253 "class foo() creates old style object, use class foo(object)",
254 254 r'#.*old-style'),
255 255 (r'\b(%s)\(' % '|'.join(k for k in keyword.kwlist
256 256 if k not in ('print', 'exec')),
257 257 "Python keyword is not a function"),
258 258 (r',]', "unneeded trailing ',' in list"),
259 259 # (r'class\s[A-Z][^\(]*\((?!Exception)',
260 260 # "don't capitalize non-exception classes"),
261 261 # (r'in range\(', "use xrange"),
262 262 # (r'^\s*print\s+', "avoid using print in core and extensions"),
263 263 (r'[\x80-\xff]', "non-ASCII character literal"),
264 264 (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"),
265 265 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
266 266 "gratuitous whitespace after Python keyword"),
267 267 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
268 268 # (r'\s\s=', "gratuitous whitespace before ="),
269 269 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
270 270 "missing whitespace around operator"),
271 271 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
272 272 "missing whitespace around operator"),
273 273 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
274 274 "missing whitespace around operator"),
275 275 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
276 276 "wrong whitespace around ="),
277 277 (r'\([^()]*( =[^=]|[^<>!=]= )',
278 278 "no whitespace around = for named parameters"),
279 279 (r'raise Exception', "don't raise generic exceptions"),
280 280 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
281 281 "don't use old-style two-argument raise, use Exception(message)"),
282 282 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
283 283 (r' [=!]=\s+(True|False|None)',
284 284 "comparison with singleton, use 'is' or 'is not' instead"),
285 285 (r'^\s*(while|if) [01]:',
286 286 "use True/False for constant Boolean expression"),
287 287 (r'(?:(?<!def)\s+|\()hasattr',
288 288 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
289 289 (r'opener\([^)]*\).read\(',
290 290 "use opener.read() instead"),
291 291 (r'opener\([^)]*\).write\(',
292 292 "use opener.write() instead"),
293 293 (r'[\s\(](open|file)\([^)]*\)\.read\(',
294 294 "use util.readfile() instead"),
295 295 (r'[\s\(](open|file)\([^)]*\)\.write\(',
296 296 "use util.writefile() instead"),
297 297 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
298 298 "always assign an opened file to a variable, and close it afterwards"),
299 299 (r'[\s\(](open|file)\([^)]*\)\.',
300 300 "always assign an opened file to a variable, and close it afterwards"),
301 301 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
302 302 (r'\.debug\(\_', "don't mark debug messages for translation"),
303 303 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
304 304 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
305 305 (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
306 306 'legacy exception syntax; use "as" instead of ","'),
307 307 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
308 308 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
309 309 "missing _() in ui message (use () to hide false-positives)"),
310 310 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
311 311 (r'\b__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
312 312 (r'os\.path\.join\(.*, *(""|\'\')\)',
313 313 "use pathutil.normasprefix(path) instead of os.path.join(path, '')"),
314 314 (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
315 315 # XXX only catch mutable arguments on the first line of the definition
316 316 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
317 317 (r'\butil\.Abort\b', "directly use error.Abort"),
318 318 (r'^import Queue', "don't use Queue, use util.queue + util.empty"),
319 (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"),
320 (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
319 321 ],
320 322 # warnings
321 323 [
322 324 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
323 325 ]
324 326 ]
325 327
326 328 pyfilters = [
327 329 (r"""(?msx)(?P<comment>\#.*?$)|
328 330 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
329 331 (?P<text>(([^\\]|\\.)*?))
330 332 (?P=quote))""", reppython),
331 333 ]
332 334
333 335 txtfilters = []
334 336
335 337 txtpats = [
336 338 [
337 339 ('\s$', 'trailing whitespace'),
338 340 ('.. note::[ \n][^\n]', 'add two newlines after note::')
339 341 ],
340 342 []
341 343 ]
342 344
343 345 cpats = [
344 346 [
345 347 (r'//', "don't use //-style comments"),
346 348 (r'^ ', "don't use spaces to indent"),
347 349 (r'\S\t', "don't use tabs except for indent"),
348 350 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
349 351 (r'.{81}', "line too long"),
350 352 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
351 353 (r'return\(', "return is not a function"),
352 354 (r' ;', "no space before ;"),
353 355 (r'[^;] \)', "no space before )"),
354 356 (r'[)][{]', "space between ) and {"),
355 357 (r'\w+\* \w+', "use int *foo, not int* foo"),
356 358 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
357 359 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
358 360 (r'\w,\w', "missing whitespace after ,"),
359 361 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
360 362 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
361 363 (r'^#\s+\w', "use #foo, not # foo"),
362 364 (r'[^\n]\Z', "no trailing newline"),
363 365 (r'^\s*#import\b', "use only #include in standard C code"),
364 366 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
365 367 (r'strcat\(', "don't use strcat"),
366 368 ],
367 369 # warnings
368 370 []
369 371 ]
370 372
371 373 cfilters = [
372 374 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
373 375 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
374 376 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
375 377 (r'(\()([^)]+\))', repcallspaces),
376 378 ]
377 379
378 380 inutilpats = [
379 381 [
380 382 (r'\bui\.', "don't use ui in util"),
381 383 ],
382 384 # warnings
383 385 []
384 386 ]
385 387
386 388 inrevlogpats = [
387 389 [
388 390 (r'\brepo\.', "don't use repo in revlog"),
389 391 ],
390 392 # warnings
391 393 []
392 394 ]
393 395
394 396 webtemplatefilters = []
395 397
396 398 webtemplatepats = [
397 399 [],
398 400 [
399 401 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
400 402 'follow desc keyword with either firstline or websub'),
401 403 ]
402 404 ]
403 405
404 406 checks = [
405 407 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
406 408 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
407 409 ('c', r'.*\.[ch]$', '', cfilters, cpats),
408 410 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
409 411 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
410 412 pyfilters, inrevlogpats),
411 413 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
412 414 inutilpats),
413 415 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
414 416 ('web template', r'mercurial/templates/.*\.tmpl', '',
415 417 webtemplatefilters, webtemplatepats),
416 418 ]
417 419
418 420 def _preparepats():
419 421 for c in checks:
420 422 failandwarn = c[-1]
421 423 for pats in failandwarn:
422 424 for i, pseq in enumerate(pats):
423 425 # fix-up regexes for multi-line searches
424 426 p = pseq[0]
425 427 # \s doesn't match \n
426 428 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
427 429 # [^...] doesn't match newline
428 430 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
429 431
430 432 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
431 433 filters = c[3]
432 434 for i, flt in enumerate(filters):
433 435 filters[i] = re.compile(flt[0]), flt[1]
434 436 _preparepats()
435 437
436 438 class norepeatlogger(object):
437 439 def __init__(self):
438 440 self._lastseen = None
439 441
440 442 def log(self, fname, lineno, line, msg, blame):
441 443 """print error related a to given line of a given file.
442 444
443 445 The faulty line will also be printed but only once in the case
444 446 of multiple errors.
445 447
446 448 :fname: filename
447 449 :lineno: line number
448 450 :line: actual content of the line
449 451 :msg: error message
450 452 """
451 453 msgid = fname, lineno, line
452 454 if msgid != self._lastseen:
453 455 if blame:
454 456 print("%s:%d (%s):" % (fname, lineno, blame))
455 457 else:
456 458 print("%s:%d:" % (fname, lineno))
457 459 print(" > %s" % line)
458 460 self._lastseen = msgid
459 461 print(" " + msg)
460 462
461 463 _defaultlogger = norepeatlogger()
462 464
463 465 def getblame(f):
464 466 lines = []
465 467 for l in os.popen('hg annotate -un %s' % f):
466 468 start, line = l.split(':', 1)
467 469 user, rev = start.split()
468 470 lines.append((line[1:-1], user, rev))
469 471 return lines
470 472
471 473 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
472 474 blame=False, debug=False, lineno=True):
473 475 """checks style and portability of a given file
474 476
475 477 :f: filepath
476 478 :logfunc: function used to report error
477 479 logfunc(filename, linenumber, linecontent, errormessage)
478 480 :maxerr: number of error to display before aborting.
479 481 Set to false (default) to report all errors
480 482
481 483 return True if no error is found, False otherwise.
482 484 """
483 485 blamecache = None
484 486 result = True
485 487
486 488 try:
487 489 fp = open(f)
488 490 except IOError as e:
489 491 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
490 492 return result
491 493 pre = post = fp.read()
492 494 fp.close()
493 495
494 496 for name, match, magic, filters, pats in checks:
495 497 if debug:
496 498 print(name, f)
497 499 fc = 0
498 500 if not (re.match(match, f) or (magic and re.search(magic, pre))):
499 501 if debug:
500 502 print("Skipping %s for %s it doesn't match %s" % (
501 503 name, match, f))
502 504 continue
503 505 if "no-" "check-code" in pre:
504 506 # If you're looking at this line, it's because a file has:
505 507 # no- check- code
506 508 # but the reason to output skipping is to make life for
507 509 # tests easier. So, instead of writing it with a normal
508 510 # spelling, we write it with the expected spelling from
509 511 # tests/test-check-code.t
510 512 print("Skipping %s it has no-che?k-code (glob)" % f)
511 513 return "Skip" # skip checking this file
512 514 for p, r in filters:
513 515 post = re.sub(p, r, post)
514 516 nerrs = len(pats[0]) # nerr elements are errors
515 517 if warnings:
516 518 pats = pats[0] + pats[1]
517 519 else:
518 520 pats = pats[0]
519 521 # print post # uncomment to show filtered version
520 522
521 523 if debug:
522 524 print("Checking %s for %s" % (name, f))
523 525
524 526 prelines = None
525 527 errors = []
526 528 for i, pat in enumerate(pats):
527 529 if len(pat) == 3:
528 530 p, msg, ignore = pat
529 531 else:
530 532 p, msg = pat
531 533 ignore = None
532 534 if i >= nerrs:
533 535 msg = "warning: " + msg
534 536
535 537 pos = 0
536 538 n = 0
537 539 for m in p.finditer(post):
538 540 if prelines is None:
539 541 prelines = pre.splitlines()
540 542 postlines = post.splitlines(True)
541 543
542 544 start = m.start()
543 545 while n < len(postlines):
544 546 step = len(postlines[n])
545 547 if pos + step > start:
546 548 break
547 549 pos += step
548 550 n += 1
549 551 l = prelines[n]
550 552
551 553 if ignore and re.search(ignore, l, re.MULTILINE):
552 554 if debug:
553 555 print("Skipping %s for %s:%s (ignore pattern)" % (
554 556 name, f, n))
555 557 continue
556 558 bd = ""
557 559 if blame:
558 560 bd = 'working directory'
559 561 if not blamecache:
560 562 blamecache = getblame(f)
561 563 if n < len(blamecache):
562 564 bl, bu, br = blamecache[n]
563 565 if bl == l:
564 566 bd = '%s@%s' % (bu, br)
565 567
566 568 errors.append((f, lineno and n + 1, l, msg, bd))
567 569 result = False
568 570
569 571 errors.sort()
570 572 for e in errors:
571 573 logfunc(*e)
572 574 fc += 1
573 575 if maxerr and fc >= maxerr:
574 576 print(" (too many errors, giving up)")
575 577 break
576 578
577 579 return result
578 580
579 581 if __name__ == "__main__":
580 582 parser = optparse.OptionParser("%prog [options] [files]")
581 583 parser.add_option("-w", "--warnings", action="store_true",
582 584 help="include warning-level checks")
583 585 parser.add_option("-p", "--per-file", type="int",
584 586 help="max warnings per file")
585 587 parser.add_option("-b", "--blame", action="store_true",
586 588 help="use annotate to generate blame info")
587 589 parser.add_option("", "--debug", action="store_true",
588 590 help="show debug information")
589 591 parser.add_option("", "--nolineno", action="store_false",
590 592 dest='lineno', help="don't show line numbers")
591 593
592 594 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
593 595 lineno=True)
594 596 (options, args) = parser.parse_args()
595 597
596 598 if len(args) == 0:
597 599 check = glob.glob("*")
598 600 else:
599 601 check = args
600 602
601 603 ret = 0
602 604 for f in check:
603 605 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
604 606 blame=options.blame, debug=options.debug,
605 607 lineno=options.lineno):
606 608 ret = 1
607 609 sys.exit(ret)
General Comments 0
You need to be logged in to leave comments. Login now