##// END OF EJS Templates
check-code: check for consistent usage of the websub filter in hgweb templates...
Steven Brown -
r21487:c26464ce default
parent child Browse files
Show More
@@ -1,565 +1,577 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 210 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
211 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 webtemplatefilters = []
371
372 webtemplatepats = [
373 [],
374 [
375 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
376 'follow desc keyword with either firstline or websub'),
377 ]
378 ]
379
370 380 checks = [
371 381 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
372 382 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
373 383 ('c', r'.*\.[ch]$', '', cfilters, cpats),
374 384 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
375 385 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
376 386 pyfilters, inrevlogpats),
377 387 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
378 388 inutilpats),
379 389 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
390 ('web template', r'mercurial/templates/.*\.tmpl', '',
391 webtemplatefilters, webtemplatepats),
380 392 ]
381 393
382 394 def _preparepats():
383 395 for c in checks:
384 396 failandwarn = c[-1]
385 397 for pats in failandwarn:
386 398 for i, pseq in enumerate(pats):
387 399 # fix-up regexes for multi-line searches
388 400 p = pseq[0]
389 401 # \s doesn't match \n
390 402 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
391 403 # [^...] doesn't match newline
392 404 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
393 405
394 406 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
395 407 filters = c[3]
396 408 for i, flt in enumerate(filters):
397 409 filters[i] = re.compile(flt[0]), flt[1]
398 410 _preparepats()
399 411
400 412 class norepeatlogger(object):
401 413 def __init__(self):
402 414 self._lastseen = None
403 415
404 416 def log(self, fname, lineno, line, msg, blame):
405 417 """print error related a to given line of a given file.
406 418
407 419 The faulty line will also be printed but only once in the case
408 420 of multiple errors.
409 421
410 422 :fname: filename
411 423 :lineno: line number
412 424 :line: actual content of the line
413 425 :msg: error message
414 426 """
415 427 msgid = fname, lineno, line
416 428 if msgid != self._lastseen:
417 429 if blame:
418 430 print "%s:%d (%s):" % (fname, lineno, blame)
419 431 else:
420 432 print "%s:%d:" % (fname, lineno)
421 433 print " > %s" % line
422 434 self._lastseen = msgid
423 435 print " " + msg
424 436
425 437 _defaultlogger = norepeatlogger()
426 438
427 439 def getblame(f):
428 440 lines = []
429 441 for l in os.popen('hg annotate -un %s' % f):
430 442 start, line = l.split(':', 1)
431 443 user, rev = start.split()
432 444 lines.append((line[1:-1], user, rev))
433 445 return lines
434 446
435 447 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
436 448 blame=False, debug=False, lineno=True):
437 449 """checks style and portability of a given file
438 450
439 451 :f: filepath
440 452 :logfunc: function used to report error
441 453 logfunc(filename, linenumber, linecontent, errormessage)
442 454 :maxerr: number of error to display before aborting.
443 455 Set to false (default) to report all errors
444 456
445 457 return True if no error is found, False otherwise.
446 458 """
447 459 blamecache = None
448 460 result = True
449 461
450 462 try:
451 463 fp = open(f)
452 464 except IOError, e:
453 465 print "Skipping %s, %s" % (f, str(e).split(':', 1)[0])
454 466 return result
455 467 pre = post = fp.read()
456 468 fp.close()
457 469
458 470 for name, match, magic, filters, pats in checks:
459 471 if debug:
460 472 print name, f
461 473 fc = 0
462 474 if not (re.match(match, f) or (magic and re.search(magic, f))):
463 475 if debug:
464 476 print "Skipping %s for %s it doesn't match %s" % (
465 477 name, match, f)
466 478 continue
467 479 if "no-" "check-code" in pre:
468 480 print "Skipping %s it has no-" "check-code" % f
469 481 return "Skip" # skip checking this file
470 482 for p, r in filters:
471 483 post = re.sub(p, r, post)
472 484 nerrs = len(pats[0]) # nerr elements are errors
473 485 if warnings:
474 486 pats = pats[0] + pats[1]
475 487 else:
476 488 pats = pats[0]
477 489 # print post # uncomment to show filtered version
478 490
479 491 if debug:
480 492 print "Checking %s for %s" % (name, f)
481 493
482 494 prelines = None
483 495 errors = []
484 496 for i, pat in enumerate(pats):
485 497 if len(pat) == 3:
486 498 p, msg, ignore = pat
487 499 else:
488 500 p, msg = pat
489 501 ignore = None
490 502 if i >= nerrs:
491 503 msg = "warning: " + msg
492 504
493 505 pos = 0
494 506 n = 0
495 507 for m in p.finditer(post):
496 508 if prelines is None:
497 509 prelines = pre.splitlines()
498 510 postlines = post.splitlines(True)
499 511
500 512 start = m.start()
501 513 while n < len(postlines):
502 514 step = len(postlines[n])
503 515 if pos + step > start:
504 516 break
505 517 pos += step
506 518 n += 1
507 519 l = prelines[n]
508 520
509 521 if ignore and re.search(ignore, l, re.MULTILINE):
510 522 if debug:
511 523 print "Skipping %s for %s:%s (ignore pattern)" % (
512 524 name, f, n)
513 525 continue
514 526 bd = ""
515 527 if blame:
516 528 bd = 'working directory'
517 529 if not blamecache:
518 530 blamecache = getblame(f)
519 531 if n < len(blamecache):
520 532 bl, bu, br = blamecache[n]
521 533 if bl == l:
522 534 bd = '%s@%s' % (bu, br)
523 535
524 536 errors.append((f, lineno and n + 1, l, msg, bd))
525 537 result = False
526 538
527 539 errors.sort()
528 540 for e in errors:
529 541 logfunc(*e)
530 542 fc += 1
531 543 if maxerr and fc >= maxerr:
532 544 print " (too many errors, giving up)"
533 545 break
534 546
535 547 return result
536 548
537 549 if __name__ == "__main__":
538 550 parser = optparse.OptionParser("%prog [options] [files]")
539 551 parser.add_option("-w", "--warnings", action="store_true",
540 552 help="include warning-level checks")
541 553 parser.add_option("-p", "--per-file", type="int",
542 554 help="max warnings per file")
543 555 parser.add_option("-b", "--blame", action="store_true",
544 556 help="use annotate to generate blame info")
545 557 parser.add_option("", "--debug", action="store_true",
546 558 help="show debug information")
547 559 parser.add_option("", "--nolineno", action="store_false",
548 560 dest='lineno', help="don't show line numbers")
549 561
550 562 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
551 563 lineno=True)
552 564 (options, args) = parser.parse_args()
553 565
554 566 if len(args) == 0:
555 567 check = glob.glob("*")
556 568 else:
557 569 check = args
558 570
559 571 ret = 0
560 572 for f in check:
561 573 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
562 574 blame=options.blame, debug=options.debug,
563 575 lineno=options.lineno):
564 576 ret = 1
565 577 sys.exit(ret)
@@ -1,286 +1,302 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 258 $ cat > ./map-inside-gettext.py <<EOF
259 259 > print _("map inside gettext %s" % v)
260 260 >
261 261 > print _("concatenating " " by " " space %s" % v)
262 262 > print _("concatenating " + " by " + " '+' %s" % v)
263 263 >
264 264 > print _("maping operation in different line %s"
265 265 > % v)
266 266 >
267 267 > print _(
268 268 > "leading spaces inside of '(' %s" % v)
269 269 > EOF
270 270 $ "$check_code" ./map-inside-gettext.py
271 271 ./map-inside-gettext.py:1:
272 272 > print _("map inside gettext %s" % v)
273 273 don't use % inside _()
274 274 ./map-inside-gettext.py:3:
275 275 > print _("concatenating " " by " " space %s" % v)
276 276 don't use % inside _()
277 277 ./map-inside-gettext.py:4:
278 278 > print _("concatenating " + " by " + " '+' %s" % v)
279 279 don't use % inside _()
280 280 ./map-inside-gettext.py:6:
281 281 > print _("maping operation in different line %s"
282 282 don't use % inside _()
283 283 ./map-inside-gettext.py:9:
284 284 > print _(
285 285 don't use % inside _()
286 286 [1]
287
288 web templates
289
290 $ mkdir -p mercurial/templates
291 $ cat > mercurial/templates/example.tmpl <<EOF
292 > {desc}
293 > {desc|escape}
294 > {desc|firstline}
295 > {desc|websub}
296 > EOF
297
298 $ "$check_code" --warnings mercurial/templates/example.tmpl
299 mercurial/templates/example.tmpl:2:
300 > {desc|escape}
301 warning: follow desc keyword with either firstline or websub
302 [1]
General Comments 0
You need to be logged in to leave comments. Login now