##// END OF EJS Templates
check-code: remove ban on old-style classes...
Gregory Szorc -
r49800:55d13252 default
parent child Browse files
Show More
@@ -1,1105 +1,1095
1 1 #!/usr/bin/env python3
2 2 #
3 3 # check-code - a style and portability checker for Mercurial
4 4 #
5 5 # Copyright 2010 Olivia Mackall <olivia@selenic.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 """style and portability checker for Mercurial
11 11
12 12 when a rule triggers wrong, do one of the following (prefer one from top):
13 13 * do the work-around the rule suggests
14 14 * doublecheck that it is a false match
15 15 * improve the rule pattern
16 16 * add an ignore pattern to the rule (3rd arg) which matches your good line
17 17 (you can append a short comment and match this, like: #re-raises)
18 18 * change the pattern to a warning and list the exception in test-check-code-hg
19 19 * ONLY use no--check-code for skipping entire files from external sources
20 20 """
21 21
22 22 import glob
23 23 import keyword
24 24 import optparse
25 25 import os
26 26 import re
27 27 import sys
28 28
29 29 if sys.version_info[0] < 3:
30 30 opentext = open
31 31 else:
32 32
33 33 def opentext(f):
34 34 return open(f, encoding='latin1')
35 35
36 36
37 37 try:
38 38 xrange
39 39 except NameError:
40 40 xrange = range
41 41 try:
42 42 import re2
43 43 except ImportError:
44 44 re2 = None
45 45
46 46 import testparseutil
47 47
48 48
49 49 def compilere(pat, multiline=False):
50 50 if multiline:
51 51 pat = '(?m)' + pat
52 52 if re2:
53 53 try:
54 54 return re2.compile(pat)
55 55 except re2.error:
56 56 pass
57 57 return re.compile(pat)
58 58
59 59
60 60 # check "rules depending on implementation of repquote()" in each
61 61 # patterns (especially pypats), before changing around repquote()
62 62 _repquotefixedmap = {
63 63 ' ': ' ',
64 64 '\n': '\n',
65 65 '.': 'p',
66 66 ':': 'q',
67 67 '%': '%',
68 68 '\\': 'b',
69 69 '*': 'A',
70 70 '+': 'P',
71 71 '-': 'M',
72 72 }
73 73
74 74
75 75 def _repquoteencodechr(i):
76 76 if i > 255:
77 77 return 'u'
78 78 c = chr(i)
79 79 if c in _repquotefixedmap:
80 80 return _repquotefixedmap[c]
81 81 if c.isalpha():
82 82 return 'x'
83 83 if c.isdigit():
84 84 return 'n'
85 85 return 'o'
86 86
87 87
88 88 _repquotett = ''.join(_repquoteencodechr(i) for i in xrange(256))
89 89
90 90
91 91 def repquote(m):
92 92 t = m.group('text')
93 93 t = t.translate(_repquotett)
94 94 return m.group('quote') + t + m.group('quote')
95 95
96 96
97 97 def reppython(m):
98 98 comment = m.group('comment')
99 99 if comment:
100 100 l = len(comment.rstrip())
101 101 return "#" * l + comment[l:]
102 102 return repquote(m)
103 103
104 104
105 105 def repcomment(m):
106 106 return m.group(1) + "#" * len(m.group(2))
107 107
108 108
109 109 def repccomment(m):
110 110 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
111 111 return m.group(1) + t + "*/"
112 112
113 113
114 114 def repcallspaces(m):
115 115 t = re.sub(r"\n\s+", "\n", m.group(2))
116 116 return m.group(1) + t
117 117
118 118
119 119 def repinclude(m):
120 120 return m.group(1) + "<foo>"
121 121
122 122
123 123 def rephere(m):
124 124 t = re.sub(r"\S", "x", m.group(2))
125 125 return m.group(1) + t
126 126
127 127
128 128 testpats = [
129 129 [
130 130 (r'\b(push|pop)d\b', "don't use 'pushd' or 'popd', use 'cd'"),
131 131 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
132 132 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
133 133 (r'(?<!hg )grep.* -a', "don't use 'grep -a', use in-line python"),
134 134 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
135 135 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
136 136 (r'echo -n', "don't use 'echo -n', use printf"),
137 137 (r'(^|\|\s*)\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"),
138 138 (r'head -c', "don't use 'head -c', use 'dd'"),
139 139 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
140 140 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
141 141 (r'\bls\b.*-\w*R', "don't use 'ls -R', use 'find'"),
142 142 (r'printf.*[^\\]\\([1-9]|0\d)', r"don't use 'printf \NNN', use Python"),
143 143 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
144 144 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
145 145 (
146 146 r'\[[^\]]+==',
147 147 '[ foo == bar ] is a bashism, use [ foo = bar ] instead',
148 148 ),
149 149 (
150 150 r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
151 151 "use egrep for extended grep syntax",
152 152 ),
153 153 (r'(^|\|\s*)e?grep .*\\S', "don't use \\S in regular expression"),
154 154 (r'(?<!!)/bin/', "don't use explicit paths for tools"),
155 155 (r'#!.*/bash', "don't use bash in shebang, use sh"),
156 156 (r'[^\n]\Z', "no trailing newline"),
157 157 (r'export .*=', "don't export and assign at once"),
158 158 (r'^source\b', "don't use 'source', use '.'"),
159 159 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
160 160 (r'\bls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
161 161 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
162 162 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
163 163 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
164 164 (r'\[\[\s+[^\]]*\]\]', "don't use '[[ ]]', use '[ ]'"),
165 165 (r'^alias\b.*=', "don't use alias, use a function"),
166 166 (r'if\s*!', "don't use '!' to negate exit status"),
167 167 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
168 168 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
169 169 (
170 170 r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
171 171 "put a backslash-escaped newline after sed 'i' command",
172 172 ),
173 173 (r'^diff *-\w*[uU].*$\n(^ \$ |^$)', "prefix diff -u/-U with cmp"),
174 174 (r'^\s+(if)? diff *-\w*[uU]', "prefix diff -u/-U with cmp"),
175 175 (r'[\s="`\']python\s(?!bindings)', "don't use 'python', use '$PYTHON'"),
176 176 (r'seq ', "don't use 'seq', use $TESTDIR/seq.py"),
177 177 (r'\butil\.Abort\b', "directly use error.Abort"),
178 178 (r'\|&', "don't use |&, use 2>&1"),
179 179 (r'\w = +\w', "only one space after = allowed"),
180 180 (
181 181 r'\bsed\b.*[^\\]\\n',
182 182 "don't use 'sed ... \\n', use a \\ and a newline",
183 183 ),
184 184 (r'env.*-u', "don't use 'env -u VAR', use 'unset VAR'"),
185 185 (r'cp.* -r ', "don't use 'cp -r', use 'cp -R'"),
186 186 (r'grep.* -[ABC]', "don't use grep's context flags"),
187 187 (
188 188 r'find.*-printf',
189 189 "don't use 'find -printf', it doesn't exist on BSD find(1)",
190 190 ),
191 191 (r'\$RANDOM ', "don't use bash-only $RANDOM to generate random values"),
192 192 ],
193 193 # warnings
194 194 [
195 195 (r'^function', "don't use 'function', use old style"),
196 196 (r'^diff.*-\w*N', "don't use 'diff -N'"),
197 197 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`", "no-pwd-check"),
198 198 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
199 199 (r'kill (`|\$\()', "don't use kill, use killdaemons.py"),
200 200 ],
201 201 ]
202 202
203 203 testfilters = [
204 204 (r"( *)(#([^!][^\n]*\S)?)", repcomment),
205 205 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
206 206 ]
207 207
208 208 uprefix = r"^ \$ "
209 209 utestpats = [
210 210 [
211 211 (r'^(\S.*|| [$>] \S.*)[ \t]\n', "trailing whitespace on non-output"),
212 212 (
213 213 uprefix + r'.*\|\s*sed[^|>\n]*\n',
214 214 "use regex test output patterns instead of sed",
215 215 ),
216 216 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
217 217 (
218 218 uprefix + r'.*\|\| echo.*(fail|error)',
219 219 "explicit exit code checks unnecessary",
220 220 ),
221 221 (uprefix + r'set -e', "don't use set -e"),
222 222 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
223 223 (
224 224 uprefix + r'.*:\.\S*/',
225 225 "x:.y in a path does not work on msys, rewrite "
226 226 "as x://.y, or see `hg log -k msys` for alternatives",
227 227 r'-\S+:\.|' '# no-msys', # -Rxxx
228 228 ), # in test-pull.t which is skipped on windows
229 229 (
230 230 r'^ [^$>].*27\.0\.0\.1',
231 231 'use $LOCALIP not an explicit loopback address',
232 232 ),
233 233 (
234 234 r'^ (?![>$] ).*\$LOCALIP.*[^)]$',
235 235 'mark $LOCALIP output lines with (glob) to help tests in BSD jails',
236 236 ),
237 237 (
238 238 r'^ (cat|find): .*: \$ENOENT\$',
239 239 'use test -f to test for file existence',
240 240 ),
241 241 (
242 242 r'^ diff -[^ -]*p',
243 243 "don't use (external) diff with -p for portability",
244 244 ),
245 245 (r' readlink ', 'use readlink.py instead of readlink'),
246 246 (
247 247 r'^ [-+][-+][-+] .* [-+]0000 \(glob\)',
248 248 "glob timezone field in diff output for portability",
249 249 ),
250 250 (
251 251 r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@',
252 252 "use '@@ -N* +N,n @@ (glob)' style chunk header for portability",
253 253 ),
254 254 (
255 255 r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@',
256 256 "use '@@ -N,n +N* @@ (glob)' style chunk header for portability",
257 257 ),
258 258 (
259 259 r'^ @@ -[0-9]+ [+][0-9]+ @@',
260 260 "use '@@ -N* +N* @@ (glob)' style chunk header for portability",
261 261 ),
262 262 (
263 263 uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff'
264 264 r'( +(-[^ po-]+|--(?!program|option)[^ ]+|[^-][^ ]*))*$',
265 265 "use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)",
266 266 ),
267 267 ],
268 268 # warnings
269 269 [
270 270 (
271 271 r'^ (?!.*\$LOCALIP)[^*?/\n]* \(glob\)$',
272 272 "glob match with no glob string (?, *, /, and $LOCALIP)",
273 273 ),
274 274 ],
275 275 ]
276 276
277 277 # transform plain test rules to unified test's
278 278 for i in [0, 1]:
279 279 for tp in testpats[i]:
280 280 p = tp[0]
281 281 m = tp[1]
282 282 if p.startswith('^'):
283 283 p = "^ [$>] (%s)" % p[1:]
284 284 else:
285 285 p = "^ [$>] .*(%s)" % p
286 286 utestpats[i].append((p, m) + tp[2:])
287 287
288 288 # don't transform the following rules:
289 289 # " > \t" and " \t" should be allowed in unified tests
290 290 testpats[0].append((r'^( *)\t', "don't use tabs to indent"))
291 291 utestpats[0].append((r'^( ?)\t', "don't use tabs to indent"))
292 292
293 293 utestfilters = [
294 294 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
295 295 (r"( +)(#([^!][^\n]*\S)?)", repcomment),
296 296 ]
297 297
298 298 # common patterns to check *.py
299 299 commonpypats = [
300 300 [
301 301 (r'\\$', 'Use () to wrap long lines in Python, not \\'),
302 302 (
303 303 r'^\s*def\s*\w+\s*\(.*,\s*\(',
304 304 "tuple parameter unpacking not available in Python 3+",
305 305 ),
306 306 (
307 307 r'lambda\s*\(.*,.*\)',
308 308 "tuple parameter unpacking not available in Python 3+",
309 309 ),
310 310 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
311 311 (r'(?<!\.)\breduce\s*\(.*', "reduce is not available in Python 3+"),
312 312 (
313 313 r'\bdict\(.*=',
314 314 'dict() is different in Py2 and 3 and is slower than {}',
315 315 'dict-from-generator',
316 316 ),
317 317 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
318 318 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
319 319 (r'^\s*\t', "don't use tabs"),
320 320 (r'\S;\s*\n', "semicolon"),
321 321 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
322 322 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
323 323 (r'(\w|\)),\w', "missing whitespace after ,"),
324 324 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
325 325 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
326 326 (
327 327 (
328 328 # a line ending with a colon, potentially with trailing comments
329 329 r':([ \t]*#[^\n]*)?\n'
330 330 # one that is not a pass and not only a comment
331 331 r'(?P<indent>[ \t]+)[^#][^\n]+\n'
332 332 # more lines at the same indent level
333 333 r'((?P=indent)[^\n]+\n)*'
334 334 # a pass at the same indent level, which is bogus
335 335 r'(?P=indent)pass[ \t\n#]'
336 336 ),
337 337 'omit superfluous pass',
338 338 ),
339 339 (r'[^\n]\Z', "no trailing newline"),
340 340 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
341 341 (
342 342 r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
343 343 "linebreak after :",
344 344 ),
345 345 (
346 r'class\s[^( \n]+:',
347 "old-style class, use class foo(object)",
348 r'#.*old-style',
349 ),
350 (
351 r'class\s[^( \n]+\(\):',
352 "class foo() creates old style object, use class foo(object)",
353 r'#.*old-style',
354 ),
355 (
356 346 r'\b(%s)\('
357 347 % '|'.join(k for k in keyword.kwlist if k not in ('print', 'exec')),
358 348 "Python keyword is not a function",
359 349 ),
360 350 # (r'class\s[A-Z][^\(]*\((?!Exception)',
361 351 # "don't capitalize non-exception classes"),
362 352 # (r'in range\(', "use xrange"),
363 353 # (r'^\s*print\s+', "avoid using print in core and extensions"),
364 354 (r'[\x80-\xff]', "non-ASCII character literal"),
365 355 (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"),
366 356 (
367 357 r'([\(\[][ \t]\S)|(\S[ \t][\)\]])',
368 358 "gratuitous whitespace in () or []",
369 359 ),
370 360 # (r'\s\s=', "gratuitous whitespace before ="),
371 361 (
372 362 r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
373 363 "missing whitespace around operator",
374 364 ),
375 365 (
376 366 r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
377 367 "missing whitespace around operator",
378 368 ),
379 369 (
380 370 r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
381 371 "missing whitespace around operator",
382 372 ),
383 373 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]', "wrong whitespace around ="),
384 374 (
385 375 r'\([^()]*( =[^=]|[^<>!=]= )',
386 376 "no whitespace around = for named parameters",
387 377 ),
388 378 (
389 379 r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
390 380 "don't use old-style two-argument raise, use Exception(message)",
391 381 ),
392 382 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
393 383 (
394 384 r' [=!]=\s+(True|False|None)',
395 385 "comparison with singleton, use 'is' or 'is not' instead",
396 386 ),
397 387 (
398 388 r'^\s*(while|if) [01]:',
399 389 "use True/False for constant Boolean expression",
400 390 ),
401 391 (r'^\s*if False(:| +and)', 'Remove code instead of using `if False`'),
402 392 (
403 393 r'(?:(?<!def)\s+|\()hasattr\(',
404 394 'hasattr(foo, bar) is broken on py2, use util.safehasattr(foo, bar) '
405 395 'instead',
406 396 r'#.*hasattr-py3-only',
407 397 ),
408 398 (r'opener\([^)]*\).read\(', "use opener.read() instead"),
409 399 (r'opener\([^)]*\).write\(', "use opener.write() instead"),
410 400 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
411 401 (r'\.debug\(\_', "don't mark debug messages for translation"),
412 402 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
413 403 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
414 404 (
415 405 r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
416 406 'legacy exception syntax; use "as" instead of ","',
417 407 ),
418 408 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
419 409 (r'\bdef\s+__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
420 410 (
421 411 r'os\.path\.join\(.*, *(""|\'\')\)',
422 412 "use pathutil.normasprefix(path) instead of os.path.join(path, '')",
423 413 ),
424 414 (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
425 415 # XXX only catch mutable arguments on the first line of the definition
426 416 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
427 417 (r'\butil\.Abort\b', "directly use error.Abort"),
428 418 (
429 419 r'^@(\w*\.)?cachefunc',
430 420 "module-level @cachefunc is risky, please avoid",
431 421 ),
432 422 (
433 423 r'^(from|import) mercurial\.(cext|pure|cffi)',
434 424 "use mercurial.policy.importmod instead",
435 425 ),
436 426 (r'\.next\(\)', "don't use .next(), use next(...)"),
437 427 (
438 428 r'([a-z]*).revision\(\1\.node\(',
439 429 "don't convert rev to node before passing to revision(nodeorrev)",
440 430 ),
441 431 (r'platform\.system\(\)', "don't use platform.system(), use pycompat"),
442 432 ],
443 433 # warnings
444 434 [],
445 435 ]
446 436
447 437 # patterns to check normal *.py files
448 438 pypats = [
449 439 [
450 440 # Ideally, these should be placed in "commonpypats" for
451 441 # consistency of coding rules in Mercurial source tree.
452 442 # But on the other hand, these are not so seriously required for
453 443 # python code fragments embedded in test scripts. Fixing test
454 444 # scripts for these patterns requires many changes, and has less
455 445 # profit than effort.
456 446 (r'raise Exception', "don't raise generic exceptions"),
457 447 (r'[\s\(](open|file)\([^)]*\)\.read\(', "use util.readfile() instead"),
458 448 (
459 449 r'[\s\(](open|file)\([^)]*\)\.write\(',
460 450 "use util.writefile() instead",
461 451 ),
462 452 (
463 453 r'^[\s\(]*(open(er)?|file)\([^)]*\)(?!\.close\(\))',
464 454 "always assign an opened file to a variable, and close it afterwards",
465 455 ),
466 456 (
467 457 r'[\s\(](open|file)\([^)]*\)\.(?!close\(\))',
468 458 "always assign an opened file to a variable, and close it afterwards",
469 459 ),
470 460 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
471 461 (r'^import atexit', "don't use atexit, use ui.atexit"),
472 462 # rules depending on implementation of repquote()
473 463 (
474 464 r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
475 465 'string join across lines with no space',
476 466 ),
477 467 (
478 468 r'''(?x)ui\.(status|progress|write|note|warn)\(
479 469 [ \t\n#]*
480 470 (?# any strings/comments might precede a string, which
481 471 # contains translatable message)
482 472 b?((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
483 473 (?# sequence consisting of below might precede translatable message
484 474 # - formatting string: "% 10s", "%05d", "% -3.2f", "%*s", "%%" ...
485 475 # - escaped character: "\\", "\n", "\0" ...
486 476 # - character other than '%', 'b' as '\', and 'x' as alphabet)
487 477 (['"]|\'\'\'|""")
488 478 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
489 479 (?# this regexp can't use [^...] style,
490 480 # because _preparepats forcibly adds "\n" into [^...],
491 481 # even though this regexp wants match it against "\n")''',
492 482 "missing _() in ui message (use () to hide false-positives)",
493 483 ),
494 484 ]
495 485 + commonpypats[0],
496 486 # warnings
497 487 [
498 488 # rules depending on implementation of repquote()
499 489 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
500 490 ]
501 491 + commonpypats[1],
502 492 ]
503 493
504 494 # patterns to check *.py for embedded ones in test script
505 495 embeddedpypats = [
506 496 [] + commonpypats[0],
507 497 # warnings
508 498 [] + commonpypats[1],
509 499 ]
510 500
511 501 # common filters to convert *.py
512 502 commonpyfilters = [
513 503 (
514 504 r"""(?msx)(?P<comment>\#.*?$)|
515 505 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
516 506 (?P<text>(([^\\]|\\.)*?))
517 507 (?P=quote))""",
518 508 reppython,
519 509 ),
520 510 ]
521 511
522 512 # pattern only for mercurial and extensions
523 513 core_py_pats = [
524 514 [
525 515 # Windows tend to get confused about capitalization of the drive letter
526 516 #
527 517 # see mercurial.windows.abspath for details
528 518 (
529 519 r'os\.path\.abspath',
530 520 "use util.abspath instead (windows)",
531 521 r'#.*re-exports',
532 522 ),
533 523 ],
534 524 # warnings
535 525 [],
536 526 ]
537 527
538 528 # filters to convert normal *.py files
539 529 pyfilters = [] + commonpyfilters
540 530
541 531 # non-filter patterns
542 532 pynfpats = [
543 533 [
544 534 (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"),
545 535 (r'pycompat\.osname\s*[=!]=\s*[\'"]posix[\'"]', "use pycompat.isposix"),
546 536 (
547 537 r'pycompat\.sysplatform\s*[!=]=\s*[\'"]darwin[\'"]',
548 538 "use pycompat.isdarwin",
549 539 ),
550 540 ],
551 541 # warnings
552 542 [],
553 543 ]
554 544
555 545 # filters to convert *.py for embedded ones in test script
556 546 embeddedpyfilters = [] + commonpyfilters
557 547
558 548 # extension non-filter patterns
559 549 pyextnfpats = [
560 550 [(r'^"""\n?[A-Z]', "don't capitalize docstring title")],
561 551 # warnings
562 552 [],
563 553 ]
564 554
565 555 txtfilters = []
566 556
567 557 txtpats = [
568 558 [
569 559 (r'\s$', 'trailing whitespace'),
570 560 ('.. note::[ \n][^\n]', 'add two newlines after note::'),
571 561 ],
572 562 [],
573 563 ]
574 564
575 565 cpats = [
576 566 [
577 567 (r'//', "don't use //-style comments"),
578 568 (r'\S\t', "don't use tabs except for indent"),
579 569 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
580 570 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
581 571 (r'return\(', "return is not a function"),
582 572 (r' ;', "no space before ;"),
583 573 (r'[^;] \)', "no space before )"),
584 574 (r'[)][{]', "space between ) and {"),
585 575 (r'\w+\* \w+', "use int *foo, not int* foo"),
586 576 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
587 577 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
588 578 (r'\w,\w', "missing whitespace after ,"),
589 579 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
590 580 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
591 581 (r'^#\s+\w', "use #foo, not # foo"),
592 582 (r'[^\n]\Z', "no trailing newline"),
593 583 (r'^\s*#import\b', "use only #include in standard C code"),
594 584 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
595 585 (r'strcat\(', "don't use strcat"),
596 586 # rules depending on implementation of repquote()
597 587 ],
598 588 # warnings
599 589 [
600 590 # rules depending on implementation of repquote()
601 591 ],
602 592 ]
603 593
604 594 cfilters = [
605 595 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
606 596 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
607 597 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
608 598 (r'(\()([^)]+\))', repcallspaces),
609 599 ]
610 600
611 601 inutilpats = [
612 602 [
613 603 (r'\bui\.', "don't use ui in util"),
614 604 ],
615 605 # warnings
616 606 [],
617 607 ]
618 608
619 609 inrevlogpats = [
620 610 [
621 611 (r'\brepo\.', "don't use repo in revlog"),
622 612 ],
623 613 # warnings
624 614 [],
625 615 ]
626 616
627 617 webtemplatefilters = []
628 618
629 619 webtemplatepats = [
630 620 [],
631 621 [
632 622 (
633 623 r'{desc(\|(?!websub|firstline)[^\|]*)+}',
634 624 'follow desc keyword with either firstline or websub',
635 625 ),
636 626 ],
637 627 ]
638 628
639 629 allfilesfilters = []
640 630
641 631 allfilespats = [
642 632 [
643 633 (
644 634 r'(http|https)://[a-zA-Z0-9./]*selenic.com/',
645 635 'use mercurial-scm.org domain URL',
646 636 ),
647 637 (
648 638 r'mercurial@selenic\.com',
649 639 'use mercurial-scm.org domain for mercurial ML address',
650 640 ),
651 641 (
652 642 r'mercurial-devel@selenic\.com',
653 643 'use mercurial-scm.org domain for mercurial-devel ML address',
654 644 ),
655 645 ],
656 646 # warnings
657 647 [],
658 648 ]
659 649
660 650 py3pats = [
661 651 [
662 652 (
663 653 r'os\.environ',
664 654 "use encoding.environ instead (py3)",
665 655 r'#.*re-exports',
666 656 ),
667 657 (r'os\.name', "use pycompat.osname instead (py3)"),
668 658 (r'os\.getcwd', "use encoding.getcwd instead (py3)", r'#.*re-exports'),
669 659 (r'os\.sep', "use pycompat.ossep instead (py3)"),
670 660 (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"),
671 661 (r'os\.altsep', "use pycompat.osaltsep instead (py3)"),
672 662 (r'sys\.platform', "use pycompat.sysplatform instead (py3)"),
673 663 (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"),
674 664 (r'os\.getenv', "use encoding.environ.get instead"),
675 665 (r'os\.setenv', "modifying the environ dict is not preferred"),
676 666 (r'(?<!pycompat\.)xrange', "use pycompat.xrange instead (py3)"),
677 667 ],
678 668 # warnings
679 669 [],
680 670 ]
681 671
682 672 checks = [
683 673 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
684 674 ('python', r'.*\.(py|cgi)$', r'^#!.*python', [], pynfpats),
685 675 ('python', r'.*hgext.*\.py$', '', [], pyextnfpats),
686 676 (
687 677 'python 3',
688 678 r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
689 679 '',
690 680 pyfilters,
691 681 py3pats,
692 682 ),
693 683 (
694 684 'core files',
695 685 r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
696 686 '',
697 687 pyfilters,
698 688 core_py_pats,
699 689 ),
700 690 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
701 691 ('c', r'.*\.[ch]$', '', cfilters, cpats),
702 692 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
703 693 (
704 694 'layering violation repo in revlog',
705 695 r'mercurial/revlog\.py',
706 696 '',
707 697 pyfilters,
708 698 inrevlogpats,
709 699 ),
710 700 (
711 701 'layering violation ui in util',
712 702 r'mercurial/util\.py',
713 703 '',
714 704 pyfilters,
715 705 inutilpats,
716 706 ),
717 707 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
718 708 (
719 709 'web template',
720 710 r'mercurial/templates/.*\.tmpl',
721 711 '',
722 712 webtemplatefilters,
723 713 webtemplatepats,
724 714 ),
725 715 ('all except for .po', r'.*(?<!\.po)$', '', allfilesfilters, allfilespats),
726 716 ]
727 717
728 718 # (desc,
729 719 # func to pick up embedded code fragments,
730 720 # list of patterns to convert target files
731 721 # list of patterns to detect errors/warnings)
732 722 embeddedchecks = [
733 723 (
734 724 'embedded python',
735 725 testparseutil.pyembedded,
736 726 embeddedpyfilters,
737 727 embeddedpypats,
738 728 )
739 729 ]
740 730
741 731
742 732 def _preparepats():
743 733 def preparefailandwarn(failandwarn):
744 734 for pats in failandwarn:
745 735 for i, pseq in enumerate(pats):
746 736 # fix-up regexes for multi-line searches
747 737 p = pseq[0]
748 738 # \s doesn't match \n (done in two steps)
749 739 # first, we replace \s that appears in a set already
750 740 p = re.sub(r'\[\\s', r'[ \\t', p)
751 741 # now we replace other \s instances.
752 742 p = re.sub(r'(?<!(\\|\[))\\s', r'[ \\t]', p)
753 743 # [^...] doesn't match newline
754 744 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
755 745
756 746 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
757 747
758 748 def preparefilters(filters):
759 749 for i, flt in enumerate(filters):
760 750 filters[i] = re.compile(flt[0]), flt[1]
761 751
762 752 for cs in (checks, embeddedchecks):
763 753 for c in cs:
764 754 failandwarn = c[-1]
765 755 preparefailandwarn(failandwarn)
766 756
767 757 filters = c[-2]
768 758 preparefilters(filters)
769 759
770 760
771 761 class norepeatlogger(object):
772 762 def __init__(self):
773 763 self._lastseen = None
774 764
775 765 def log(self, fname, lineno, line, msg, blame):
776 766 """print error related a to given line of a given file.
777 767
778 768 The faulty line will also be printed but only once in the case
779 769 of multiple errors.
780 770
781 771 :fname: filename
782 772 :lineno: line number
783 773 :line: actual content of the line
784 774 :msg: error message
785 775 """
786 776 msgid = fname, lineno, line
787 777 if msgid != self._lastseen:
788 778 if blame:
789 779 print("%s:%d (%s):" % (fname, lineno, blame))
790 780 else:
791 781 print("%s:%d:" % (fname, lineno))
792 782 print(" > %s" % line)
793 783 self._lastseen = msgid
794 784 print(" " + msg)
795 785
796 786
797 787 _defaultlogger = norepeatlogger()
798 788
799 789
800 790 def getblame(f):
801 791 lines = []
802 792 for l in os.popen('hg annotate -un %s' % f):
803 793 start, line = l.split(':', 1)
804 794 user, rev = start.split()
805 795 lines.append((line[1:-1], user, rev))
806 796 return lines
807 797
808 798
809 799 def checkfile(
810 800 f,
811 801 logfunc=_defaultlogger.log,
812 802 maxerr=None,
813 803 warnings=False,
814 804 blame=False,
815 805 debug=False,
816 806 lineno=True,
817 807 ):
818 808 """checks style and portability of a given file
819 809
820 810 :f: filepath
821 811 :logfunc: function used to report error
822 812 logfunc(filename, linenumber, linecontent, errormessage)
823 813 :maxerr: number of error to display before aborting.
824 814 Set to false (default) to report all errors
825 815
826 816 return True if no error is found, False otherwise.
827 817 """
828 818 result = True
829 819
830 820 try:
831 821 with opentext(f) as fp:
832 822 try:
833 823 pre = fp.read()
834 824 except UnicodeDecodeError as e:
835 825 print("%s while reading %s" % (e, f))
836 826 return result
837 827 except IOError as e:
838 828 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
839 829 return result
840 830
841 831 # context information shared while single checkfile() invocation
842 832 context = {'blamecache': None}
843 833
844 834 for name, match, magic, filters, pats in checks:
845 835 if debug:
846 836 print(name, f)
847 837 if not (re.match(match, f) or (magic and re.search(magic, pre))):
848 838 if debug:
849 839 print(
850 840 "Skipping %s for %s it doesn't match %s" % (name, match, f)
851 841 )
852 842 continue
853 843 if "no-" "check-code" in pre:
854 844 # If you're looking at this line, it's because a file has:
855 845 # no- check- code
856 846 # but the reason to output skipping is to make life for
857 847 # tests easier. So, instead of writing it with a normal
858 848 # spelling, we write it with the expected spelling from
859 849 # tests/test-check-code.t
860 850 print("Skipping %s it has no-che?k-code (glob)" % f)
861 851 return "Skip" # skip checking this file
862 852
863 853 fc = _checkfiledata(
864 854 name,
865 855 f,
866 856 pre,
867 857 filters,
868 858 pats,
869 859 context,
870 860 logfunc,
871 861 maxerr,
872 862 warnings,
873 863 blame,
874 864 debug,
875 865 lineno,
876 866 )
877 867 if fc:
878 868 result = False
879 869
880 870 if f.endswith('.t') and "no-" "check-code" not in pre:
881 871 if debug:
882 872 print("Checking embedded code in %s" % f)
883 873
884 874 prelines = pre.splitlines()
885 875 embeddederros = []
886 876 for name, embedded, filters, pats in embeddedchecks:
887 877 # "reset curmax at each repetition" treats maxerr as "max
888 878 # nubmer of errors in an actual file per entry of
889 879 # (embedded)checks"
890 880 curmaxerr = maxerr
891 881
892 882 for found in embedded(f, prelines, embeddederros):
893 883 filename, starts, ends, code = found
894 884 fc = _checkfiledata(
895 885 name,
896 886 f,
897 887 code,
898 888 filters,
899 889 pats,
900 890 context,
901 891 logfunc,
902 892 curmaxerr,
903 893 warnings,
904 894 blame,
905 895 debug,
906 896 lineno,
907 897 offset=starts - 1,
908 898 )
909 899 if fc:
910 900 result = False
911 901 if curmaxerr:
912 902 if fc >= curmaxerr:
913 903 break
914 904 curmaxerr -= fc
915 905
916 906 return result
917 907
918 908
919 909 def _checkfiledata(
920 910 name,
921 911 f,
922 912 filedata,
923 913 filters,
924 914 pats,
925 915 context,
926 916 logfunc,
927 917 maxerr,
928 918 warnings,
929 919 blame,
930 920 debug,
931 921 lineno,
932 922 offset=None,
933 923 ):
934 924 """Execute actual error check for file data
935 925
936 926 :name: of the checking category
937 927 :f: filepath
938 928 :filedata: content of a file
939 929 :filters: to be applied before checking
940 930 :pats: to detect errors
941 931 :context: a dict of information shared while single checkfile() invocation
942 932 Valid keys: 'blamecache'.
943 933 :logfunc: function used to report error
944 934 logfunc(filename, linenumber, linecontent, errormessage)
945 935 :maxerr: number of error to display before aborting, or False to
946 936 report all errors
947 937 :warnings: whether warning level checks should be applied
948 938 :blame: whether blame information should be displayed at error reporting
949 939 :debug: whether debug information should be displayed
950 940 :lineno: whether lineno should be displayed at error reporting
951 941 :offset: line number offset of 'filedata' in 'f' for checking
952 942 an embedded code fragment, or None (offset=0 is different
953 943 from offset=None)
954 944
955 945 returns number of detected errors.
956 946 """
957 947 blamecache = context['blamecache']
958 948 if offset is None:
959 949 lineoffset = 0
960 950 else:
961 951 lineoffset = offset
962 952
963 953 fc = 0
964 954 pre = post = filedata
965 955
966 956 if True: # TODO: get rid of this redundant 'if' block
967 957 for p, r in filters:
968 958 post = re.sub(p, r, post)
969 959 nerrs = len(pats[0]) # nerr elements are errors
970 960 if warnings:
971 961 pats = pats[0] + pats[1]
972 962 else:
973 963 pats = pats[0]
974 964 # print post # uncomment to show filtered version
975 965
976 966 if debug:
977 967 print("Checking %s for %s" % (name, f))
978 968
979 969 prelines = None
980 970 errors = []
981 971 for i, pat in enumerate(pats):
982 972 if len(pat) == 3:
983 973 p, msg, ignore = pat
984 974 else:
985 975 p, msg = pat
986 976 ignore = None
987 977 if i >= nerrs:
988 978 msg = "warning: " + msg
989 979
990 980 pos = 0
991 981 n = 0
992 982 for m in p.finditer(post):
993 983 if prelines is None:
994 984 prelines = pre.splitlines()
995 985 postlines = post.splitlines(True)
996 986
997 987 start = m.start()
998 988 while n < len(postlines):
999 989 step = len(postlines[n])
1000 990 if pos + step > start:
1001 991 break
1002 992 pos += step
1003 993 n += 1
1004 994 l = prelines[n]
1005 995
1006 996 if ignore and re.search(ignore, l, re.MULTILINE):
1007 997 if debug:
1008 998 print(
1009 999 "Skipping %s for %s:%s (ignore pattern)"
1010 1000 % (name, f, (n + lineoffset))
1011 1001 )
1012 1002 continue
1013 1003 bd = ""
1014 1004 if blame:
1015 1005 bd = 'working directory'
1016 1006 if blamecache is None:
1017 1007 blamecache = getblame(f)
1018 1008 context['blamecache'] = blamecache
1019 1009 if (n + lineoffset) < len(blamecache):
1020 1010 bl, bu, br = blamecache[(n + lineoffset)]
1021 1011 if offset is None and bl == l:
1022 1012 bd = '%s@%s' % (bu, br)
1023 1013 elif offset is not None and bl.endswith(l):
1024 1014 # "offset is not None" means "checking
1025 1015 # embedded code fragment". In this case,
1026 1016 # "l" does not have information about the
1027 1017 # beginning of an *original* line in the
1028 1018 # file (e.g. ' > ').
1029 1019 # Therefore, use "str.endswith()", and
1030 1020 # show "maybe" for a little loose
1031 1021 # examination.
1032 1022 bd = '%s@%s, maybe' % (bu, br)
1033 1023
1034 1024 errors.append((f, lineno and (n + lineoffset + 1), l, msg, bd))
1035 1025
1036 1026 errors.sort()
1037 1027 for e in errors:
1038 1028 logfunc(*e)
1039 1029 fc += 1
1040 1030 if maxerr and fc >= maxerr:
1041 1031 print(" (too many errors, giving up)")
1042 1032 break
1043 1033
1044 1034 return fc
1045 1035
1046 1036
1047 1037 def main():
1048 1038 parser = optparse.OptionParser("%prog [options] [files | -]")
1049 1039 parser.add_option(
1050 1040 "-w",
1051 1041 "--warnings",
1052 1042 action="store_true",
1053 1043 help="include warning-level checks",
1054 1044 )
1055 1045 parser.add_option(
1056 1046 "-p", "--per-file", type="int", help="max warnings per file"
1057 1047 )
1058 1048 parser.add_option(
1059 1049 "-b",
1060 1050 "--blame",
1061 1051 action="store_true",
1062 1052 help="use annotate to generate blame info",
1063 1053 )
1064 1054 parser.add_option(
1065 1055 "", "--debug", action="store_true", help="show debug information"
1066 1056 )
1067 1057 parser.add_option(
1068 1058 "",
1069 1059 "--nolineno",
1070 1060 action="store_false",
1071 1061 dest='lineno',
1072 1062 help="don't show line numbers",
1073 1063 )
1074 1064
1075 1065 parser.set_defaults(
1076 1066 per_file=15, warnings=False, blame=False, debug=False, lineno=True
1077 1067 )
1078 1068 (options, args) = parser.parse_args()
1079 1069
1080 1070 if len(args) == 0:
1081 1071 check = glob.glob("*")
1082 1072 elif args == ['-']:
1083 1073 # read file list from stdin
1084 1074 check = sys.stdin.read().splitlines()
1085 1075 else:
1086 1076 check = args
1087 1077
1088 1078 _preparepats()
1089 1079
1090 1080 ret = 0
1091 1081 for f in check:
1092 1082 if not checkfile(
1093 1083 f,
1094 1084 maxerr=options.per_file,
1095 1085 warnings=options.warnings,
1096 1086 blame=options.blame,
1097 1087 debug=options.debug,
1098 1088 lineno=options.lineno,
1099 1089 ):
1100 1090 ret = 1
1101 1091 return ret
1102 1092
1103 1093
1104 1094 if __name__ == "__main__":
1105 1095 sys.exit(main())
@@ -1,429 +1,423
1 1 $ cat > correct.py <<NO_CHECK_EOF
2 2 > def toto(arg1, arg2):
3 3 > del arg2
4 4 > return (5 + 6, 9)
5 5 > NO_CHECK_EOF
6 6 $ cat > wrong.py <<NO_CHECK_EOF
7 7 > def toto( arg1, arg2):
8 8 > del(arg2)
9 9 > return ( 5+6, 9)
10 10 > def badwrap():
11 11 > return 1 + \\
12 12 > 2
13 13 > NO_CHECK_EOF
14 14 $ cat > quote.py <<NO_CHECK_EOF
15 15 > # let's use quote in comments
16 16 > (''' ( 4x5 )
17 17 > but """\\''' and finally''',
18 18 > """let's fool checkpatch""", '1+2',
19 19 > '"""', 42+1, """and
20 20 > ( 4-1 ) """, "( 1+1 )\" and ")
21 21 > a, '\\\\\\\\', "\\\\\\" x-2", "c-1"
22 22 > NO_CHECK_EOF
23 23 $ cat > classstyle.py <<NO_CHECK_EOF
24 24 > class newstyle_class(object):
25 25 > pass
26 26 >
27 27 > class oldstyle_class:
28 28 > pass
29 29 >
30 30 > class empty():
31 31 > pass
32 32 >
33 33 > no_class = 1:
34 34 > pass
35 35 > NO_CHECK_EOF
36 36 $ check_code="$TESTDIR"/../contrib/check-code.py
37 37 $ "$check_code" ./wrong.py ./correct.py ./quote.py ./classstyle.py
38 38 ./wrong.py:1:
39 39 > def toto( arg1, arg2):
40 40 gratuitous whitespace in () or []
41 41 ./wrong.py:2:
42 42 > del(arg2)
43 43 Python keyword is not a function
44 44 ./wrong.py:3:
45 45 > return ( 5+6, 9)
46 46 gratuitous whitespace in () or []
47 47 missing whitespace in expression
48 48 ./wrong.py:5:
49 49 > return 1 + \
50 50 Use () to wrap long lines in Python, not \
51 51 ./quote.py:5:
52 52 > '"""', 42+1, """and
53 53 missing whitespace in expression
54 ./classstyle.py:4:
55 > class oldstyle_class:
56 old-style class, use class foo(object)
57 ./classstyle.py:7:
58 > class empty():
59 class foo() creates old style object, use class foo(object)
60 54 [1]
61 55 $ cat > python3-compat.py << NO_CHECK_EOF
62 56 > foo <> bar
63 57 > reduce(lambda a, b: a + b, [1, 2, 3, 4])
64 58 > dict(key=value)
65 59 > NO_CHECK_EOF
66 60 $ "$check_code" python3-compat.py
67 61 python3-compat.py:1:
68 62 > foo <> bar
69 63 <> operator is not available in Python 3+, use !=
70 64 python3-compat.py:2:
71 65 > reduce(lambda a, b: a + b, [1, 2, 3, 4])
72 66 reduce is not available in Python 3+
73 67 python3-compat.py:3:
74 68 > dict(key=value)
75 69 dict() is different in Py2 and 3 and is slower than {}
76 70 [1]
77 71
78 72 $ cat > foo.c <<NO_CHECK_EOF
79 73 > void narf() {
80 74 > strcpy(foo, bar);
81 75 > // strcpy_s is okay, but this comment is not
82 76 > strcpy_s(foo, bar);
83 77 > }
84 78 > NO_CHECK_EOF
85 79 $ "$check_code" ./foo.c
86 80 ./foo.c:2:
87 81 > strcpy(foo, bar);
88 82 don't use strcpy, use strlcpy or memcpy
89 83 ./foo.c:3:
90 84 > // strcpy_s is okay, but this comment is not
91 85 don't use //-style comments
92 86 [1]
93 87
94 88 $ cat > is-op.py <<NO_CHECK_EOF
95 89 > # is-operator comparing number or string literal
96 90 > x = None
97 91 > y = x is 'foo'
98 92 > y = x is "foo"
99 93 > y = x is 5346
100 94 > y = x is -6
101 95 > y = x is not 'foo'
102 96 > y = x is not "foo"
103 97 > y = x is not 5346
104 98 > y = x is not -6
105 99 > NO_CHECK_EOF
106 100
107 101 $ "$check_code" ./is-op.py
108 102 ./is-op.py:3:
109 103 > y = x is 'foo'
110 104 object comparison with literal
111 105 ./is-op.py:4:
112 106 > y = x is "foo"
113 107 object comparison with literal
114 108 ./is-op.py:5:
115 109 > y = x is 5346
116 110 object comparison with literal
117 111 ./is-op.py:6:
118 112 > y = x is -6
119 113 object comparison with literal
120 114 ./is-op.py:7:
121 115 > y = x is not 'foo'
122 116 object comparison with literal
123 117 ./is-op.py:8:
124 118 > y = x is not "foo"
125 119 object comparison with literal
126 120 ./is-op.py:9:
127 121 > y = x is not 5346
128 122 object comparison with literal
129 123 ./is-op.py:10:
130 124 > y = x is not -6
131 125 object comparison with literal
132 126 [1]
133 127
134 128 $ cat > for-nolineno.py <<NO_CHECK_EOF
135 129 > except:
136 130 > NO_CHECK_EOF
137 131 $ "$check_code" for-nolineno.py --nolineno
138 132 for-nolineno.py:0:
139 133 > except:
140 134 naked except clause
141 135 [1]
142 136
143 137 $ cat > warning.t <<NO_CHECK_EOF
144 138 > $ function warnonly {
145 139 > > }
146 140 > $ diff -N aaa
147 141 > $ function onwarn {}
148 142 > NO_CHECK_EOF
149 143 $ "$check_code" warning.t
150 144 $ "$check_code" --warn warning.t
151 145 warning.t:1:
152 146 > $ function warnonly {
153 147 warning: don't use 'function', use old style
154 148 warning.t:3:
155 149 > $ diff -N aaa
156 150 warning: don't use 'diff -N'
157 151 warning.t:4:
158 152 > $ function onwarn {}
159 153 warning: don't use 'function', use old style
160 154 [1]
161 155 $ cat > error.t <<NO_CHECK_EOF
162 156 > $ [ foo == bar ]
163 157 > NO_CHECK_EOF
164 158 $ "$check_code" error.t
165 159 error.t:1:
166 160 > $ [ foo == bar ]
167 161 [ foo == bar ] is a bashism, use [ foo = bar ] instead
168 162 [1]
169 163 $ rm error.t
170 164 $ cat > raise-format.py <<NO_CHECK_EOF
171 165 > raise SomeException, message
172 166 > # this next line is okay
173 167 > raise SomeException(arg1, arg2)
174 168 > NO_CHECK_EOF
175 169 $ "$check_code" not-existing.py raise-format.py
176 170 Skipping*not-existing.py* (glob)
177 171 raise-format.py:1:
178 172 > raise SomeException, message
179 173 don't use old-style two-argument raise, use Exception(message)
180 174 [1]
181 175
182 176 $ cat <<NO_CHECK_EOF > tab.t
183 177 > indent
184 178 > > heredoc
185 179 > NO_CHECK_EOF
186 180 $ "$check_code" tab.t
187 181 tab.t:1:
188 182 > indent
189 183 don't use tabs to indent
190 184 [1]
191 185 $ rm tab.t
192 186
193 187 $ cat > rst.py <<NO_CHECK_EOF
194 188 > """problematic rst text
195 189 >
196 190 > .. note::
197 191 > wrong
198 192 > """
199 193 >
200 194 > '''
201 195 >
202 196 > .. note::
203 197 >
204 198 > valid
205 199 >
206 200 > new text
207 201 >
208 202 > .. note::
209 203 >
210 204 > also valid
211 205 > '''
212 206 >
213 207 > """mixed
214 208 >
215 209 > .. note::
216 210 >
217 211 > good
218 212 >
219 213 > .. note::
220 214 > plus bad
221 215 > """
222 216 > NO_CHECK_EOF
223 217 $ $check_code -w rst.py
224 218 rst.py:3:
225 219 > .. note::
226 220 warning: add two newlines after '.. note::'
227 221 rst.py:26:
228 222 > .. note::
229 223 warning: add two newlines after '.. note::'
230 224 [1]
231 225
232 226 $ cat > ./map-inside-gettext.py <<NO_CHECK_EOF
233 227 > print(_("map inside gettext %s" % v))
234 228 >
235 229 > print(_("concatenating " " by " " space %s" % v))
236 230 > print(_("concatenating " + " by " + " '+' %s" % v))
237 231 >
238 232 > print(_("mapping operation in different line %s"
239 233 > % v))
240 234 >
241 235 > print(_(
242 236 > "leading spaces inside of '(' %s" % v))
243 237 > NO_CHECK_EOF
244 238 $ "$check_code" ./map-inside-gettext.py
245 239 ./map-inside-gettext.py:1:
246 240 > print(_("map inside gettext %s" % v))
247 241 don't use % inside _()
248 242 ./map-inside-gettext.py:3:
249 243 > print(_("concatenating " " by " " space %s" % v))
250 244 don't use % inside _()
251 245 ./map-inside-gettext.py:4:
252 246 > print(_("concatenating " + " by " + " '+' %s" % v))
253 247 don't use % inside _()
254 248 ./map-inside-gettext.py:6:
255 249 > print(_("mapping operation in different line %s"
256 250 don't use % inside _()
257 251 ./map-inside-gettext.py:9:
258 252 > print(_(
259 253 don't use % inside _()
260 254 [1]
261 255
262 256 web templates
263 257
264 258 $ mkdir -p mercurial/templates
265 259 $ cat > mercurial/templates/example.tmpl <<NO_CHECK_EOF
266 260 > {desc}
267 261 > {desc|escape}
268 262 > {desc|firstline}
269 263 > {desc|websub}
270 264 > NO_CHECK_EOF
271 265
272 266 $ "$check_code" --warnings mercurial/templates/example.tmpl
273 267 mercurial/templates/example.tmpl:2:
274 268 > {desc|escape}
275 269 warning: follow desc keyword with either firstline or websub
276 270 [1]
277 271
278 272 'string join across lines with no space' detection
279 273
280 274 $ cat > stringjoin.py <<NO_CHECK_EOF
281 275 > foo = (' foo'
282 276 > 'bar foo.'
283 277 > 'bar foo:'
284 278 > 'bar foo@'
285 279 > 'bar foo%'
286 280 > 'bar foo*'
287 281 > 'bar foo+'
288 282 > 'bar foo-'
289 283 > 'bar')
290 284 > NO_CHECK_EOF
291 285
292 286 'missing _() in ui message' detection
293 287
294 288 $ cat > uigettext.py <<NO_CHECK_EOF
295 289 > ui.status("% 10s %05d % -3.2f %*s %%"
296 290 > # this use '\\\\' instead of '\\', because the latter in
297 291 > # heredoc on shell becomes just '\'
298 292 > '\\\\ \n \t \0'
299 293 > """12345
300 294 > """
301 295 > '''.:*+-=
302 296 > ''' "%-6d \n 123456 .:*+-= foobar")
303 297 > NO_CHECK_EOF
304 298
305 299 superfluous pass
306 300
307 301 $ cat > superfluous_pass.py <<NO_CHECK_EOF
308 302 > # correct examples
309 303 > if foo:
310 304 > pass
311 305 > else:
312 306 > # comment-only line means still need pass
313 307 > pass
314 308 > def nothing():
315 309 > pass
316 310 > class empty(object):
317 311 > pass
318 312 > if whatever:
319 313 > passvalue(value)
320 314 > # bad examples
321 315 > if foo:
322 316 > "foo"
323 317 > pass
324 318 > else: # trailing comment doesn't fool checker
325 319 > wat()
326 320 > pass
327 321 > def nothing():
328 322 > "docstring means no pass"
329 323 > pass
330 324 > class empty(object):
331 325 > """multiline
332 326 > docstring also
333 327 > means no pass"""
334 328 > pass
335 329 > NO_CHECK_EOF
336 330
337 331 (Checking multiple invalid files at once examines whether caching
338 332 translation table for repquote() works as expected or not. All files
339 333 should break rules depending on result of repquote(), in this case)
340 334
341 335 $ "$check_code" stringjoin.py uigettext.py superfluous_pass.py
342 336 stringjoin.py:1:
343 337 > foo = (' foo'
344 338 string join across lines with no space
345 339 stringjoin.py:2:
346 340 > 'bar foo.'
347 341 string join across lines with no space
348 342 stringjoin.py:3:
349 343 > 'bar foo:'
350 344 string join across lines with no space
351 345 stringjoin.py:4:
352 346 > 'bar foo@'
353 347 string join across lines with no space
354 348 stringjoin.py:5:
355 349 > 'bar foo%'
356 350 string join across lines with no space
357 351 stringjoin.py:6:
358 352 > 'bar foo*'
359 353 string join across lines with no space
360 354 stringjoin.py:7:
361 355 > 'bar foo+'
362 356 string join across lines with no space
363 357 stringjoin.py:8:
364 358 > 'bar foo-'
365 359 string join across lines with no space
366 360 uigettext.py:1:
367 361 > ui.status("% 10s %05d % -3.2f %*s %%"
368 362 missing _() in ui message (use () to hide false-positives)
369 363 superfluous_pass.py:14:
370 364 > if foo:
371 365 omit superfluous pass
372 366 superfluous_pass.py:17:
373 367 > else: # trailing comment doesn't fool checker
374 368 omit superfluous pass
375 369 superfluous_pass.py:20:
376 370 > def nothing():
377 371 omit superfluous pass
378 372 superfluous_pass.py:23:
379 373 > class empty(object):
380 374 omit superfluous pass
381 375 [1]
382 376
383 377 Check code fragments embedded in test script
384 378
385 379 $ cat > embedded-code.t <<NO_CHECK_EOF
386 380 > code fragment in doctest style
387 381 > >>> x = (1,2)
388 382 > ...
389 383 > ... x = (1,2)
390 384 >
391 385 > code fragment in heredoc style
392 386 > $ python <<EOF
393 387 > > x = (1,2)
394 388 > > EOF
395 389 >
396 390 > code fragment in file heredoc style
397 391 > $ python > file.py <<EOF
398 392 > > x = (1,2)
399 393 > > EOF
400 394 > NO_CHECK_EOF
401 395 $ "$check_code" embedded-code.t
402 396 embedded-code.t:2:
403 397 > x = (1,2)
404 398 missing whitespace after ,
405 399 embedded-code.t:4:
406 400 > x = (1,2)
407 401 missing whitespace after ,
408 402 embedded-code.t:8:
409 403 > x = (1,2)
410 404 missing whitespace after ,
411 405 embedded-code.t:13:
412 406 > x = (1,2)
413 407 missing whitespace after ,
414 408 [1]
415 409
416 410 "max warnings per file" is shared by all embedded code fragments
417 411
418 412 $ "$check_code" --per-file=3 embedded-code.t
419 413 embedded-code.t:2:
420 414 > x = (1,2)
421 415 missing whitespace after ,
422 416 embedded-code.t:4:
423 417 > x = (1,2)
424 418 missing whitespace after ,
425 419 embedded-code.t:8:
426 420 > x = (1,2)
427 421 missing whitespace after ,
428 422 (too many errors, giving up)
429 423 [1]
General Comments 0
You need to be logged in to leave comments. Login now