##// END OF EJS Templates
check-code: disallow use of dict(key=value) construction...
Augie Fackler -
r20688:a61ed1c2 default
parent child Browse files
Show More
@@ -1,560 +1,562
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'(^| )wc[^|]*$\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 ],
172 172 # warnings
173 173 [
174 174 (r'^ [^*?/\n]* \(glob\)$',
175 175 "glob match with no glob character (?*/)"),
176 176 ]
177 177 ]
178 178
179 179 for i in [0, 1]:
180 180 for p, m in testpats[i]:
181 181 if p.startswith(r'^'):
182 182 p = r"^ [$>] (%s)" % p[1:]
183 183 else:
184 184 p = r"^ [$>] .*(%s)" % p
185 185 utestpats[i].append((p, m))
186 186
187 187 utestfilters = [
188 188 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
189 189 (r"( *)(#([^\n]*\S)?)", repcomment),
190 190 ]
191 191
192 192 pypats = [
193 193 [
194 194 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
195 195 "tuple parameter unpacking not available in Python 3+"),
196 196 (r'lambda\s*\(.*,.*\)',
197 197 "tuple parameter unpacking not available in Python 3+"),
198 198 (r'import (.+,[^.]+\.[^.]+|[^.]+\.[^.]+,)',
199 199 '2to3 can\'t always rewrite "import qux, foo.bar", '
200 200 'use "import foo.bar" on its own line instead.'),
201 201 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
202 202 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
203 (r'dict\(.*=', 'dict() is different in Py2 and 3 and is slower than {}',
204 'dict-from-generator'),
203 205 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
204 206 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
205 207 (r'^\s*\t', "don't use tabs"),
206 208 (r'\S;\s*\n', "semicolon"),
207 209 (r'[^_]_\("[^"]+"\s*%', "don't use % inside _()"),
208 210 (r"[^_]_\('[^']+'\s*%", "don't use % inside _()"),
209 211 (r'(\w|\)),\w', "missing whitespace after ,"),
210 212 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
211 213 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
212 214 (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)\1except.*?:\n'
213 215 r'((?:\n|\1\s.*\n)+?)\1finally:', 'no try/except/finally in Python 2.4'),
214 216 (r'(?<!def)(\s+|^|\()next\(.+\)',
215 217 'no next(foo) in Python 2.4 and 2.5, use foo.next() instead'),
216 218 (r'(\s+)try:\n((?:\n|\1\s.*\n)*?)\1\s*yield\b.*?'
217 219 r'((?:\n|\1\s.*\n)+?)\1finally:',
218 220 'no yield inside try/finally in Python 2.4'),
219 221 (r'.{81}', "line too long"),
220 222 (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
221 223 (r'[^\n]\Z', "no trailing newline"),
222 224 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
223 225 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
224 226 # "don't use underbars in identifiers"),
225 227 (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
226 228 "don't use camelcase in identifiers"),
227 229 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
228 230 "linebreak after :"),
229 231 (r'class\s[^( \n]+:', "old-style class, use class foo(object)"),
230 232 (r'class\s[^( \n]+\(\):',
231 233 "class foo() not available in Python 2.4, use class foo(object)"),
232 234 (r'\b(%s)\(' % '|'.join(keyword.kwlist),
233 235 "Python keyword is not a function"),
234 236 (r',]', "unneeded trailing ',' in list"),
235 237 # (r'class\s[A-Z][^\(]*\((?!Exception)',
236 238 # "don't capitalize non-exception classes"),
237 239 # (r'in range\(', "use xrange"),
238 240 # (r'^\s*print\s+', "avoid using print in core and extensions"),
239 241 (r'[\x80-\xff]', "non-ASCII character literal"),
240 242 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
241 243 (r'^\s*with\s+', "with not available in Python 2.4"),
242 244 (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"),
243 245 (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
244 246 (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
245 247 (r'(?<!def)\s+(any|all|format)\(',
246 248 "any/all/format not available in Python 2.4", 'no-py24'),
247 249 (r'(?<!def)\s+(callable)\(',
248 250 "callable not available in Python 3, use getattr(f, '__call__', None)"),
249 251 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
250 252 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
251 253 "gratuitous whitespace after Python keyword"),
252 254 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
253 255 # (r'\s\s=', "gratuitous whitespace before ="),
254 256 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
255 257 "missing whitespace around operator"),
256 258 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
257 259 "missing whitespace around operator"),
258 260 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
259 261 "missing whitespace around operator"),
260 262 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
261 263 "wrong whitespace around ="),
262 264 (r'\([^()]*( =[^=]|[^<>!=]= )',
263 265 "no whitespace around = for named parameters"),
264 266 (r'raise Exception', "don't raise generic exceptions"),
265 267 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
266 268 "don't use old-style two-argument raise, use Exception(message)"),
267 269 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
268 270 (r' [=!]=\s+(True|False|None)',
269 271 "comparison with singleton, use 'is' or 'is not' instead"),
270 272 (r'^\s*(while|if) [01]:',
271 273 "use True/False for constant Boolean expression"),
272 274 (r'(?:(?<!def)\s+|\()hasattr',
273 275 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
274 276 (r'opener\([^)]*\).read\(',
275 277 "use opener.read() instead"),
276 278 (r'BaseException', 'not in Python 2.4, use Exception'),
277 279 (r'os\.path\.relpath', 'os.path.relpath is not in Python 2.5'),
278 280 (r'opener\([^)]*\).write\(',
279 281 "use opener.write() instead"),
280 282 (r'[\s\(](open|file)\([^)]*\)\.read\(',
281 283 "use util.readfile() instead"),
282 284 (r'[\s\(](open|file)\([^)]*\)\.write\(',
283 285 "use util.writefile() instead"),
284 286 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
285 287 "always assign an opened file to a variable, and close it afterwards"),
286 288 (r'[\s\(](open|file)\([^)]*\)\.',
287 289 "always assign an opened file to a variable, and close it afterwards"),
288 290 (r'(?i)descendent', "the proper spelling is descendAnt"),
289 291 (r'\.debug\(\_', "don't mark debug messages for translation"),
290 292 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
291 293 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
292 294 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
293 295 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
294 296 "missing _() in ui message (use () to hide false-positives)"),
295 297 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
296 298 ],
297 299 # warnings
298 300 [
299 301 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
300 302 ]
301 303 ]
302 304
303 305 pyfilters = [
304 306 (r"""(?msx)(?P<comment>\#.*?$)|
305 307 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
306 308 (?P<text>(([^\\]|\\.)*?))
307 309 (?P=quote))""", reppython),
308 310 ]
309 311
310 312 txtfilters = []
311 313
312 314 txtpats = [
313 315 [
314 316 ('\s$', 'trailing whitespace'),
315 317 ('.. note::[ \n][^\n]', 'add two newlines after note::')
316 318 ],
317 319 []
318 320 ]
319 321
320 322 cpats = [
321 323 [
322 324 (r'//', "don't use //-style comments"),
323 325 (r'^ ', "don't use spaces to indent"),
324 326 (r'\S\t', "don't use tabs except for indent"),
325 327 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
326 328 (r'.{81}', "line too long"),
327 329 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
328 330 (r'return\(', "return is not a function"),
329 331 (r' ;', "no space before ;"),
330 332 (r'[)][{]', "space between ) and {"),
331 333 (r'\w+\* \w+', "use int *foo, not int* foo"),
332 334 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
333 335 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
334 336 (r'\w,\w', "missing whitespace after ,"),
335 337 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
336 338 (r'^#\s+\w', "use #foo, not # foo"),
337 339 (r'[^\n]\Z', "no trailing newline"),
338 340 (r'^\s*#import\b', "use only #include in standard C code"),
339 341 ],
340 342 # warnings
341 343 []
342 344 ]
343 345
344 346 cfilters = [
345 347 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
346 348 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
347 349 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
348 350 (r'(\()([^)]+\))', repcallspaces),
349 351 ]
350 352
351 353 inutilpats = [
352 354 [
353 355 (r'\bui\.', "don't use ui in util"),
354 356 ],
355 357 # warnings
356 358 []
357 359 ]
358 360
359 361 inrevlogpats = [
360 362 [
361 363 (r'\brepo\.', "don't use repo in revlog"),
362 364 ],
363 365 # warnings
364 366 []
365 367 ]
366 368
367 369 checks = [
368 370 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
369 371 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
370 372 ('c', r'.*\.[ch]$', cfilters, cpats),
371 373 ('unified test', r'.*\.t$', utestfilters, utestpats),
372 374 ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters,
373 375 inrevlogpats),
374 376 ('layering violation ui in util', r'mercurial/util\.py', pyfilters,
375 377 inutilpats),
376 378 ('txt', r'.*\.txt$', txtfilters, txtpats),
377 379 ]
378 380
379 381 def _preparepats():
380 382 for c in checks:
381 383 failandwarn = c[-1]
382 384 for pats in failandwarn:
383 385 for i, pseq in enumerate(pats):
384 386 # fix-up regexes for multi-line searches
385 387 p = pseq[0]
386 388 # \s doesn't match \n
387 389 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
388 390 # [^...] doesn't match newline
389 391 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
390 392
391 393 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
392 394 filters = c[2]
393 395 for i, flt in enumerate(filters):
394 396 filters[i] = re.compile(flt[0]), flt[1]
395 397 _preparepats()
396 398
397 399 class norepeatlogger(object):
398 400 def __init__(self):
399 401 self._lastseen = None
400 402
401 403 def log(self, fname, lineno, line, msg, blame):
402 404 """print error related a to given line of a given file.
403 405
404 406 The faulty line will also be printed but only once in the case
405 407 of multiple errors.
406 408
407 409 :fname: filename
408 410 :lineno: line number
409 411 :line: actual content of the line
410 412 :msg: error message
411 413 """
412 414 msgid = fname, lineno, line
413 415 if msgid != self._lastseen:
414 416 if blame:
415 417 print "%s:%d (%s):" % (fname, lineno, blame)
416 418 else:
417 419 print "%s:%d:" % (fname, lineno)
418 420 print " > %s" % line
419 421 self._lastseen = msgid
420 422 print " " + msg
421 423
422 424 _defaultlogger = norepeatlogger()
423 425
424 426 def getblame(f):
425 427 lines = []
426 428 for l in os.popen('hg annotate -un %s' % f):
427 429 start, line = l.split(':', 1)
428 430 user, rev = start.split()
429 431 lines.append((line[1:-1], user, rev))
430 432 return lines
431 433
432 434 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
433 435 blame=False, debug=False, lineno=True):
434 436 """checks style and portability of a given file
435 437
436 438 :f: filepath
437 439 :logfunc: function used to report error
438 440 logfunc(filename, linenumber, linecontent, errormessage)
439 441 :maxerr: number of error to display before aborting.
440 442 Set to false (default) to report all errors
441 443
442 444 return True if no error is found, False otherwise.
443 445 """
444 446 blamecache = None
445 447 result = True
446 448 for name, match, filters, pats in checks:
447 449 if debug:
448 450 print name, f
449 451 fc = 0
450 452 if not re.match(match, f):
451 453 if debug:
452 454 print "Skipping %s for %s it doesn't match %s" % (
453 455 name, match, f)
454 456 continue
455 457 try:
456 458 fp = open(f)
457 459 except IOError, e:
458 460 print "Skipping %s, %s" % (f, str(e).split(':', 1)[0])
459 461 continue
460 462 pre = post = fp.read()
461 463 fp.close()
462 464 if "no-" "check-code" in pre:
463 465 print "Skipping %s it has no-" "check-code" % f
464 466 return "Skip" # skip checking this file
465 467 for p, r in filters:
466 468 post = re.sub(p, r, post)
467 469 nerrs = len(pats[0]) # nerr elements are errors
468 470 if warnings:
469 471 pats = pats[0] + pats[1]
470 472 else:
471 473 pats = pats[0]
472 474 # print post # uncomment to show filtered version
473 475
474 476 if debug:
475 477 print "Checking %s for %s" % (name, f)
476 478
477 479 prelines = None
478 480 errors = []
479 481 for i, pat in enumerate(pats):
480 482 if len(pat) == 3:
481 483 p, msg, ignore = pat
482 484 else:
483 485 p, msg = pat
484 486 ignore = None
485 487 if i >= nerrs:
486 488 msg = "warning: " + msg
487 489
488 490 pos = 0
489 491 n = 0
490 492 for m in p.finditer(post):
491 493 if prelines is None:
492 494 prelines = pre.splitlines()
493 495 postlines = post.splitlines(True)
494 496
495 497 start = m.start()
496 498 while n < len(postlines):
497 499 step = len(postlines[n])
498 500 if pos + step > start:
499 501 break
500 502 pos += step
501 503 n += 1
502 504 l = prelines[n]
503 505
504 506 if ignore and re.search(ignore, l, re.MULTILINE):
505 507 if debug:
506 508 print "Skipping %s for %s:%s (ignore pattern)" % (
507 509 name, f, n)
508 510 continue
509 511 bd = ""
510 512 if blame:
511 513 bd = 'working directory'
512 514 if not blamecache:
513 515 blamecache = getblame(f)
514 516 if n < len(blamecache):
515 517 bl, bu, br = blamecache[n]
516 518 if bl == l:
517 519 bd = '%s@%s' % (bu, br)
518 520
519 521 errors.append((f, lineno and n + 1, l, msg, bd))
520 522 result = False
521 523
522 524 errors.sort()
523 525 for e in errors:
524 526 logfunc(*e)
525 527 fc += 1
526 528 if maxerr and fc >= maxerr:
527 529 print " (too many errors, giving up)"
528 530 break
529 531
530 532 return result
531 533
532 534 if __name__ == "__main__":
533 535 parser = optparse.OptionParser("%prog [options] [files]")
534 536 parser.add_option("-w", "--warnings", action="store_true",
535 537 help="include warning-level checks")
536 538 parser.add_option("-p", "--per-file", type="int",
537 539 help="max warnings per file")
538 540 parser.add_option("-b", "--blame", action="store_true",
539 541 help="use annotate to generate blame info")
540 542 parser.add_option("", "--debug", action="store_true",
541 543 help="show debug information")
542 544 parser.add_option("", "--nolineno", action="store_false",
543 545 dest='lineno', help="don't show line numbers")
544 546
545 547 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
546 548 lineno=True)
547 549 (options, args) = parser.parse_args()
548 550
549 551 if len(args) == 0:
550 552 check = glob.glob("*")
551 553 else:
552 554 check = args
553 555
554 556 ret = 0
555 557 for f in check:
556 558 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
557 559 blame=options.blame, debug=options.debug,
558 560 lineno=options.lineno):
559 561 ret = 1
560 562 sys.exit(ret)
@@ -1,100 +1,100
1 1 # Copyright 2010-2011 Fog Creek Software
2 2 # Copyright 2010-2011 Unity Technologies
3 3 #
4 4 # This software may be used and distributed according to the terms of the
5 5 # GNU General Public License version 2 or any later version.
6 6
7 7 '''remote largefile store; the base class for wirestore'''
8 8
9 9 import urllib2
10 10
11 11 from mercurial import util
12 12 from mercurial.i18n import _
13 13 from mercurial.wireproto import remotebatch
14 14
15 15 import lfutil
16 16 import basestore
17 17
18 18 class remotestore(basestore.basestore):
19 19 '''a largefile store accessed over a network'''
20 20 def __init__(self, ui, repo, url):
21 21 super(remotestore, self).__init__(ui, repo, url)
22 22
23 23 def put(self, source, hash):
24 24 if self.sendfile(source, hash):
25 25 raise util.Abort(
26 26 _('remotestore: could not put %s to remote store %s')
27 27 % (source, util.hidepassword(self.url)))
28 28 self.ui.debug(
29 29 _('remotestore: put %s to remote store %s\n')
30 30 % (source, util.hidepassword(self.url)))
31 31
32 32 def exists(self, hashes):
33 return dict((h, s == 0) for (h, s) in self._stat(hashes).iteritems())
33 return dict((h, s == 0) for (h, s) in # dict-from-generator
34 self._stat(hashes).iteritems())
34 35
35 36 def sendfile(self, filename, hash):
36 37 self.ui.debug('remotestore: sendfile(%s, %s)\n' % (filename, hash))
37 38 fd = None
38 39 try:
39 40 try:
40 41 fd = lfutil.httpsendfile(self.ui, filename)
41 42 except IOError, e:
42 43 raise util.Abort(
43 44 _('remotestore: could not open file %s: %s')
44 45 % (filename, str(e)))
45 46 return self._put(hash, fd)
46 47 finally:
47 48 if fd:
48 49 fd.close()
49 50
50 51 def _getfile(self, tmpfile, filename, hash):
51 52 try:
52 53 chunks = self._get(hash)
53 54 except urllib2.HTTPError, e:
54 55 # 401s get converted to util.Aborts; everything else is fine being
55 56 # turned into a StoreError
56 57 raise basestore.StoreError(filename, hash, self.url, str(e))
57 58 except urllib2.URLError, e:
58 59 # This usually indicates a connection problem, so don't
59 60 # keep trying with the other files... they will probably
60 61 # all fail too.
61 62 raise util.Abort('%s: %s' %
62 63 (util.hidepassword(self.url), e.reason))
63 64 except IOError, e:
64 65 raise basestore.StoreError(filename, hash, self.url, str(e))
65 66
66 67 return lfutil.copyandhash(chunks, tmpfile)
67 68
68 69 def _verifyfile(self, cctx, cset, contents, standin, verified):
69 70 filename = lfutil.splitstandin(standin)
70 71 if not filename:
71 72 return False
72 73 fctx = cctx[standin]
73 74 key = (filename, fctx.filenode())
74 75 if key in verified:
75 76 return False
76 77
77 78 verified.add(key)
78 79
79 80 expecthash = fctx.data()[0:40]
80 81 stat = self._stat([expecthash])[expecthash]
81 82 if not stat:
82 83 return False
83 84 elif stat == 1:
84 85 self.ui.warn(
85 86 _('changeset %s: %s: contents differ\n')
86 87 % (cset, filename))
87 88 return True # failed
88 89 elif stat == 2:
89 90 self.ui.warn(
90 91 _('changeset %s: %s missing\n')
91 92 % (cset, filename))
92 93 return True # failed
93 94 else:
94 95 raise RuntimeError('verify failed: unexpected response from '
95 96 'statlfile (%r)' % stat)
96 97
97 98 def batch(self):
98 99 '''Support for remote batching.'''
99 100 return remotebatch(self)
100
@@ -1,253 +1,257
1 1 $ cat > correct.py <<EOF
2 2 > def toto(arg1, arg2):
3 3 > del arg2
4 4 > return (5 + 6, 9)
5 5 > EOF
6 6 $ cat > wrong.py <<EOF
7 7 > def toto( arg1, arg2):
8 8 > del(arg2)
9 9 > return ( 5+6, 9)
10 10 > EOF
11 11 $ cat > quote.py <<EOF
12 12 > # let's use quote in comments
13 13 > (''' ( 4x5 )
14 14 > but """\\''' and finally''',
15 15 > """let's fool checkpatch""", '1+2',
16 16 > '"""', 42+1, """and
17 17 > ( 4-1 ) """, "( 1+1 )\" and ")
18 18 > a, '\\\\\\\\', "\\\\\\" x-2", "c-1"
19 19 > EOF
20 20 $ cat > non-py24.py <<EOF
21 21 > # Using builtins that does not exist in Python 2.4
22 22 > if any():
23 23 > x = all()
24 24 > y = format(x)
25 25 > # next(generator) is new in 2.6
26 26 > z = next(x)
27 27 > # but generator.next() is okay
28 28 > x.next()
29 29 > # and we can make our own next
30 30 > def next(stuff):
31 31 > pass
32 32 >
33 33 > # Do not complain about our own definition
34 34 > def any(x):
35 35 > pass
36 36 >
37 37 > # try/except/finally block does not exist in Python 2.4
38 38 > try:
39 39 > pass
40 40 > except StandardError, inst:
41 41 > pass
42 42 > finally:
43 43 > pass
44 44 >
45 45 > # nested try/finally+try/except is allowed
46 46 > try:
47 47 > try:
48 48 > pass
49 49 > except StandardError, inst:
50 50 > pass
51 51 > finally:
52 52 > pass
53 53 >
54 54 > # yield inside a try/finally block is not allowed in Python 2.4
55 55 > try:
56 56 > pass
57 57 > yield 1
58 58 > finally:
59 59 > pass
60 60 > try:
61 61 > yield
62 62 > pass
63 63 > finally:
64 64 > pass
65 65 >
66 66 > EOF
67 67 $ cat > classstyle.py <<EOF
68 68 > class newstyle_class(object):
69 69 > pass
70 70 >
71 71 > class oldstyle_class:
72 72 > pass
73 73 >
74 74 > class empty():
75 75 > pass
76 76 >
77 77 > no_class = 1:
78 78 > pass
79 79 > EOF
80 80 $ check_code="$TESTDIR"/../contrib/check-code.py
81 81 $ "$check_code" ./wrong.py ./correct.py ./quote.py ./non-py24.py ./classstyle.py
82 82 ./wrong.py:1:
83 83 > def toto( arg1, arg2):
84 84 gratuitous whitespace in () or []
85 85 ./wrong.py:2:
86 86 > del(arg2)
87 87 Python keyword is not a function
88 88 ./wrong.py:3:
89 89 > return ( 5+6, 9)
90 90 gratuitous whitespace in () or []
91 91 missing whitespace in expression
92 92 ./quote.py:5:
93 93 > '"""', 42+1, """and
94 94 missing whitespace in expression
95 95 ./non-py24.py:2:
96 96 > if any():
97 97 any/all/format not available in Python 2.4
98 98 ./non-py24.py:3:
99 99 > x = all()
100 100 any/all/format not available in Python 2.4
101 101 ./non-py24.py:4:
102 102 > y = format(x)
103 103 any/all/format not available in Python 2.4
104 104 ./non-py24.py:6:
105 105 > z = next(x)
106 106 no next(foo) in Python 2.4 and 2.5, use foo.next() instead
107 107 ./non-py24.py:18:
108 108 > try:
109 109 no try/except/finally in Python 2.4
110 110 ./non-py24.py:35:
111 111 > try:
112 112 no yield inside try/finally in Python 2.4
113 113 ./non-py24.py:40:
114 114 > try:
115 115 no yield inside try/finally in Python 2.4
116 116 ./classstyle.py:4:
117 117 > class oldstyle_class:
118 118 old-style class, use class foo(object)
119 119 ./classstyle.py:7:
120 120 > class empty():
121 121 class foo() not available in Python 2.4, use class foo(object)
122 122 [1]
123 123 $ cat > python3-compat.py << EOF
124 124 > foo <> bar
125 125 > reduce(lambda a, b: a + b, [1, 2, 3, 4])
126 > dict(key=value)
126 127 > EOF
127 128 $ "$check_code" python3-compat.py
128 129 python3-compat.py:1:
129 130 > foo <> bar
130 131 <> operator is not available in Python 3+, use !=
131 132 python3-compat.py:2:
132 133 > reduce(lambda a, b: a + b, [1, 2, 3, 4])
133 134 reduce is not available in Python 3+
135 python3-compat.py:3:
136 > dict(key=value)
137 dict() is different in Py2 and 3 and is slower than {}
134 138 [1]
135 139
136 140 $ cat > is-op.py <<EOF
137 141 > # is-operator comparing number or string literal
138 142 > x = None
139 143 > y = x is 'foo'
140 144 > y = x is "foo"
141 145 > y = x is 5346
142 146 > y = x is -6
143 147 > y = x is not 'foo'
144 148 > y = x is not "foo"
145 149 > y = x is not 5346
146 150 > y = x is not -6
147 151 > EOF
148 152
149 153 $ "$check_code" ./is-op.py
150 154 ./is-op.py:3:
151 155 > y = x is 'foo'
152 156 object comparison with literal
153 157 ./is-op.py:4:
154 158 > y = x is "foo"
155 159 object comparison with literal
156 160 ./is-op.py:5:
157 161 > y = x is 5346
158 162 object comparison with literal
159 163 ./is-op.py:6:
160 164 > y = x is -6
161 165 object comparison with literal
162 166 ./is-op.py:7:
163 167 > y = x is not 'foo'
164 168 object comparison with literal
165 169 ./is-op.py:8:
166 170 > y = x is not "foo"
167 171 object comparison with literal
168 172 ./is-op.py:9:
169 173 > y = x is not 5346
170 174 object comparison with literal
171 175 ./is-op.py:10:
172 176 > y = x is not -6
173 177 object comparison with literal
174 178 [1]
175 179
176 180 $ cat > for-nolineno.py <<EOF
177 181 > except:
178 182 > EOF
179 183 $ "$check_code" for-nolineno.py --nolineno
180 184 for-nolineno.py:0:
181 185 > except:
182 186 naked except clause
183 187 [1]
184 188
185 189 $ cat > warning.t <<EOF
186 190 > $ function warnonly {
187 191 > > }
188 192 > $ diff -N aaa
189 193 > $ function onwarn {}
190 194 > EOF
191 195 $ "$check_code" warning.t
192 196 $ "$check_code" --warn warning.t
193 197 warning.t:1:
194 198 > $ function warnonly {
195 199 warning: don't use 'function', use old style
196 200 warning.t:3:
197 201 > $ diff -N aaa
198 202 warning: don't use 'diff -N'
199 203 warning.t:4:
200 204 > $ function onwarn {}
201 205 warning: don't use 'function', use old style
202 206 [1]
203 207 $ cat > raise-format.py <<EOF
204 208 > raise SomeException, message
205 209 > # this next line is okay
206 210 > raise SomeException(arg1, arg2)
207 211 > EOF
208 212 $ "$check_code" not-existing.py raise-format.py
209 213 Skipping*not-existing.py* (glob)
210 214 raise-format.py:1:
211 215 > raise SomeException, message
212 216 don't use old-style two-argument raise, use Exception(message)
213 217 [1]
214 218
215 219 $ cat > rst.py <<EOF
216 220 > """problematic rst text
217 221 >
218 222 > .. note::
219 223 > wrong
220 224 > """
221 225 >
222 226 > '''
223 227 >
224 228 > .. note::
225 229 >
226 230 > valid
227 231 >
228 232 > new text
229 233 >
230 234 > .. note::
231 235 >
232 236 > also valid
233 237 > '''
234 238 >
235 239 > """mixed
236 240 >
237 241 > .. note::
238 242 >
239 243 > good
240 244 >
241 245 > .. note::
242 246 > plus bad
243 247 > """
244 248 > EOF
245 249 $ $check_code -w rst.py
246 250 rst.py:3:
247 251 > .. note::
248 252 warning: add two newlines after '.. note::'
249 253 rst.py:26:
250 254 > .. note::
251 255 warning: add two newlines after '.. note::'
252 256 [1]
253 257
General Comments 0
You need to be logged in to leave comments. Login now