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