##// END OF EJS Templates
check-code: add --nolineno option for hiding line numbers...
Mads Kiilerich -
r15502:7917a104 default
parent child Browse files
Show More
@@ -1,425 +1,429 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 import re, glob, os, sys
11 11 import keyword
12 12 import optparse
13 13
14 14 def repquote(m):
15 15 t = re.sub(r"\w", "x", m.group('text'))
16 16 t = re.sub(r"[^\s\nx]", "o", t)
17 17 return m.group('quote') + t + m.group('quote')
18 18
19 19 def reppython(m):
20 20 comment = m.group('comment')
21 21 if comment:
22 22 return "#" * len(comment)
23 23 return repquote(m)
24 24
25 25 def repcomment(m):
26 26 return m.group(1) + "#" * len(m.group(2))
27 27
28 28 def repccomment(m):
29 29 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
30 30 return m.group(1) + t + "*/"
31 31
32 32 def repcallspaces(m):
33 33 t = re.sub(r"\n\s+", "\n", m.group(2))
34 34 return m.group(1) + t
35 35
36 36 def repinclude(m):
37 37 return m.group(1) + "<foo>"
38 38
39 39 def rephere(m):
40 40 t = re.sub(r"\S", "x", m.group(2))
41 41 return m.group(1) + t
42 42
43 43
44 44 testpats = [
45 45 [
46 46 (r'(pushd|popd)', "don't use 'pushd' or 'popd', use 'cd'"),
47 47 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
48 48 (r'^function', "don't use 'function', use old style"),
49 49 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
50 50 (r'echo.*\\n', "don't use 'echo \\n', use printf"),
51 51 (r'echo -n', "don't use 'echo -n', use printf"),
52 52 (r'^diff.*-\w*N', "don't use 'diff -N'"),
53 53 (r'(^| )wc[^|]*$\n(?!.*\(re\))', "filter wc output"),
54 54 (r'head -c', "don't use 'head -c', use 'dd'"),
55 55 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
56 56 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
57 57 (r'printf.*\\\d\d\d', "don't use 'printf \NNN', use Python"),
58 58 (r'printf.*\\x', "don't use printf \\x, use Python"),
59 59 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
60 60 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
61 61 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
62 62 "use egrep for extended grep syntax"),
63 63 (r'/bin/', "don't use explicit paths for tools"),
64 64 (r'\$PWD', "don't use $PWD, use `pwd`"),
65 65 (r'[^\n]\Z', "no trailing newline"),
66 66 (r'export.*=', "don't export and assign at once"),
67 67 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\\^', "^ must be quoted"),
68 68 (r'^source\b', "don't use 'source', use '.'"),
69 69 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
70 70 (r'ls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
71 71 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
72 72 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
73 73 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
74 74 ],
75 75 # warnings
76 76 []
77 77 ]
78 78
79 79 testfilters = [
80 80 (r"( *)(#([^\n]*\S)?)", repcomment),
81 81 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
82 82 ]
83 83
84 84 uprefix = r"^ \$ "
85 85 uprefixc = r"^ > "
86 86 utestpats = [
87 87 [
88 88 (r'^(\S| $ ).*(\S[ \t]+|^[ \t]+)\n', "trailing whitespace on non-output"),
89 89 (uprefix + r'.*\|\s*sed', "use regex test output patterns instead of sed"),
90 90 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
91 91 (uprefix + r'.*\$\?', "explicit exit code checks unnecessary"),
92 92 (uprefix + r'.*\|\| echo.*(fail|error)',
93 93 "explicit exit code checks unnecessary"),
94 94 (uprefix + r'set -e', "don't use set -e"),
95 95 (uprefixc + r'( *)\t', "don't use tabs to indent"),
96 96 ],
97 97 # warnings
98 98 []
99 99 ]
100 100
101 101 for i in [0, 1]:
102 102 for p, m in testpats[i]:
103 103 if p.startswith(r'^'):
104 104 p = uprefix + p[1:]
105 105 else:
106 106 p = uprefix + ".*" + p
107 107 utestpats[i].append((p, m))
108 108
109 109 utestfilters = [
110 110 (r"( *)(#([^\n]*\S)?)", repcomment),
111 111 ]
112 112
113 113 pypats = [
114 114 [
115 115 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
116 116 "tuple parameter unpacking not available in Python 3+"),
117 117 (r'lambda\s*\(.*,.*\)',
118 118 "tuple parameter unpacking not available in Python 3+"),
119 119 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
120 120 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
121 121 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
122 122 (r'^\s*\t', "don't use tabs"),
123 123 (r'\S;\s*\n', "semicolon"),
124 124 (r'\w,\w', "missing whitespace after ,"),
125 125 (r'\w[+/*\-<>]\w', "missing whitespace in expression"),
126 126 (r'^\s+\w+=\w+[^,)\n]$', "missing whitespace in assignment"),
127 127 (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)\1except.*?:\n'
128 128 r'((?:\n|\1\s.*\n)+?)\1finally:', 'no try/except/finally in Py2.4'),
129 129 (r'.{85}', "line too long"),
130 130 (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
131 131 (r'[^\n]\Z', "no trailing newline"),
132 132 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
133 133 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=', "don't use underbars in identifiers"),
134 134 (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
135 135 "don't use camelcase in identifiers"),
136 136 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
137 137 "linebreak after :"),
138 138 (r'class\s[^( \n]+:', "old-style class, use class foo(object)"),
139 139 (r'class\s[^( \n]+\(\):',
140 140 "class foo() not available in Python 2.4, use class foo(object)"),
141 141 (r'\b(%s)\(' % '|'.join(keyword.kwlist),
142 142 "Python keyword is not a function"),
143 143 (r',]', "unneeded trailing ',' in list"),
144 144 # (r'class\s[A-Z][^\(]*\((?!Exception)',
145 145 # "don't capitalize non-exception classes"),
146 146 # (r'in range\(', "use xrange"),
147 147 # (r'^\s*print\s+', "avoid using print in core and extensions"),
148 148 (r'[\x80-\xff]', "non-ASCII character literal"),
149 149 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
150 150 (r'^\s*with\s+', "with not available in Python 2.4"),
151 151 (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"),
152 152 (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
153 153 (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
154 154 (r'(?<!def)\s+(any|all|format)\(',
155 155 "any/all/format not available in Python 2.4"),
156 156 (r'(?<!def)\s+(callable)\(',
157 157 "callable not available in Python 3, use getattr(f, '__call__', None)"),
158 158 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
159 159 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
160 160 "gratuitous whitespace after Python keyword"),
161 161 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
162 162 # (r'\s\s=', "gratuitous whitespace before ="),
163 163 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
164 164 "missing whitespace around operator"),
165 165 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\s',
166 166 "missing whitespace around operator"),
167 167 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
168 168 "missing whitespace around operator"),
169 169 (r'[^+=*/!<>&| -](\s=|=\s)[^= ]',
170 170 "wrong whitespace around ="),
171 171 (r'raise Exception', "don't raise generic exceptions"),
172 172 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
173 173 (r' [=!]=\s+(True|False|None)',
174 174 "comparison with singleton, use 'is' or 'is not' instead"),
175 175 (r'^\s*(while|if) [01]:',
176 176 "use True/False for constant Boolean expression"),
177 177 (r'(?<!def)\s+hasattr',
178 178 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
179 179 (r'opener\([^)]*\).read\(',
180 180 "use opener.read() instead"),
181 181 (r'BaseException', 'not in Py2.4, use Exception'),
182 182 (r'os\.path\.relpath', 'os.path.relpath is not in Py2.5'),
183 183 (r'opener\([^)]*\).write\(',
184 184 "use opener.write() instead"),
185 185 (r'[\s\(](open|file)\([^)]*\)\.read\(',
186 186 "use util.readfile() instead"),
187 187 (r'[\s\(](open|file)\([^)]*\)\.write\(',
188 188 "use util.readfile() instead"),
189 189 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
190 190 "always assign an opened file to a variable, and close it afterwards"),
191 191 (r'[\s\(](open|file)\([^)]*\)\.',
192 192 "always assign an opened file to a variable, and close it afterwards"),
193 193 (r'(?i)descendent', "the proper spelling is descendAnt"),
194 194 (r'\.debug\(\_', "don't mark debug messages for translation"),
195 195 ],
196 196 # warnings
197 197 [
198 198 (r'.{81}', "warning: line over 80 characters"),
199 199 (r'^\s*except:$', "warning: naked except clause"),
200 200 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
201 201 "warning: unwrapped ui message"),
202 202 ]
203 203 ]
204 204
205 205 pyfilters = [
206 206 (r"""(?msx)(?P<comment>\#.*?$)|
207 207 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
208 208 (?P<text>(([^\\]|\\.)*?))
209 209 (?P=quote))""", reppython),
210 210 ]
211 211
212 212 cpats = [
213 213 [
214 214 (r'//', "don't use //-style comments"),
215 215 (r'^ ', "don't use spaces to indent"),
216 216 (r'\S\t', "don't use tabs except for indent"),
217 217 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
218 218 (r'.{85}', "line too long"),
219 219 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
220 220 (r'return\(', "return is not a function"),
221 221 (r' ;', "no space before ;"),
222 222 (r'\w+\* \w+', "use int *foo, not int* foo"),
223 223 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
224 224 (r'\S+ (\+\+|--)', "use foo++, not foo ++"),
225 225 (r'\w,\w', "missing whitespace after ,"),
226 226 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
227 227 (r'^#\s+\w', "use #foo, not # foo"),
228 228 (r'[^\n]\Z', "no trailing newline"),
229 229 (r'^\s*#import\b', "use only #include in standard C code"),
230 230 ],
231 231 # warnings
232 232 []
233 233 ]
234 234
235 235 cfilters = [
236 236 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
237 237 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
238 238 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
239 239 (r'(\()([^)]+\))', repcallspaces),
240 240 ]
241 241
242 242 inutilpats = [
243 243 [
244 244 (r'\bui\.', "don't use ui in util"),
245 245 ],
246 246 # warnings
247 247 []
248 248 ]
249 249
250 250 inrevlogpats = [
251 251 [
252 252 (r'\brepo\.', "don't use repo in revlog"),
253 253 ],
254 254 # warnings
255 255 []
256 256 ]
257 257
258 258 checks = [
259 259 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
260 260 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
261 261 ('c', r'.*\.c$', cfilters, cpats),
262 262 ('unified test', r'.*\.t$', utestfilters, utestpats),
263 263 ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters,
264 264 inrevlogpats),
265 265 ('layering violation ui in util', r'mercurial/util\.py', pyfilters,
266 266 inutilpats),
267 267 ]
268 268
269 269 class norepeatlogger(object):
270 270 def __init__(self):
271 271 self._lastseen = None
272 272
273 273 def log(self, fname, lineno, line, msg, blame):
274 274 """print error related a to given line of a given file.
275 275
276 276 The faulty line will also be printed but only once in the case
277 277 of multiple errors.
278 278
279 279 :fname: filename
280 280 :lineno: line number
281 281 :line: actual content of the line
282 282 :msg: error message
283 283 """
284 284 msgid = fname, lineno, line
285 285 if msgid != self._lastseen:
286 286 if blame:
287 287 print "%s:%d (%s):" % (fname, lineno, blame)
288 288 else:
289 289 print "%s:%d:" % (fname, lineno)
290 290 print " > %s" % line
291 291 self._lastseen = msgid
292 292 print " " + msg
293 293
294 294 _defaultlogger = norepeatlogger()
295 295
296 296 def getblame(f):
297 297 lines = []
298 298 for l in os.popen('hg annotate -un %s' % f):
299 299 start, line = l.split(':', 1)
300 300 user, rev = start.split()
301 301 lines.append((line[1:-1], user, rev))
302 302 return lines
303 303
304 304 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
305 blame=False, debug=False):
305 blame=False, debug=False, lineno=True):
306 306 """checks style and portability of a given file
307 307
308 308 :f: filepath
309 309 :logfunc: function used to report error
310 310 logfunc(filename, linenumber, linecontent, errormessage)
311 311 :maxerr: number of error to display before arborting.
312 312 Set to None (default) to report all errors
313 313
314 314 return True if no error is found, False otherwise.
315 315 """
316 316 blamecache = None
317 317 result = True
318 318 for name, match, filters, pats in checks:
319 319 if debug:
320 320 print name, f
321 321 fc = 0
322 322 if not re.match(match, f):
323 323 if debug:
324 324 print "Skipping %s for %s it doesn't match %s" % (
325 325 name, match, f)
326 326 continue
327 327 fp = open(f)
328 328 pre = post = fp.read()
329 329 fp.close()
330 330 if "no-" + "check-code" in pre:
331 331 if debug:
332 332 print "Skipping %s for %s it has no- and check-code" % (
333 333 name, f)
334 334 break
335 335 for p, r in filters:
336 336 post = re.sub(p, r, post)
337 337 if warnings:
338 338 pats = pats[0] + pats[1]
339 339 else:
340 340 pats = pats[0]
341 341 # print post # uncomment to show filtered version
342 342
343 343 if debug:
344 344 print "Checking %s for %s" % (name, f)
345 345
346 346 prelines = None
347 347 errors = []
348 348 for p, msg in pats:
349 349 # fix-up regexes for multiline searches
350 350 po = p
351 351 # \s doesn't match \n
352 352 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
353 353 # [^...] doesn't match newline
354 354 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
355 355
356 356 #print po, '=>', p
357 357
358 358 pos = 0
359 359 n = 0
360 360 for m in re.finditer(p, post, re.MULTILINE):
361 361 if prelines is None:
362 362 prelines = pre.splitlines()
363 363 postlines = post.splitlines(True)
364 364
365 365 start = m.start()
366 366 while n < len(postlines):
367 367 step = len(postlines[n])
368 368 if pos + step > start:
369 369 break
370 370 pos += step
371 371 n += 1
372 372 l = prelines[n]
373 373
374 374 if "check-code" + "-ignore" in l:
375 375 if debug:
376 376 print "Skipping %s for %s:%s (check-code -ignore)" % (
377 377 name, f, n)
378 378 continue
379 379 bd = ""
380 380 if blame:
381 381 bd = 'working directory'
382 382 if not blamecache:
383 383 blamecache = getblame(f)
384 384 if n < len(blamecache):
385 385 bl, bu, br = blamecache[n]
386 386 if bl == l:
387 387 bd = '%s@%s' % (bu, br)
388 errors.append((f, n + 1, l, msg, bd))
388 errors.append((f, lineno and n + 1, l, msg, bd))
389 389 result = False
390 390
391 391 errors.sort()
392 392 for e in errors:
393 393 logfunc(*e)
394 394 fc += 1
395 395 if maxerr is not None and fc >= maxerr:
396 396 print " (too many errors, giving up)"
397 397 break
398 398
399 399 return result
400 400
401 401 if __name__ == "__main__":
402 402 parser = optparse.OptionParser("%prog [options] [files]")
403 403 parser.add_option("-w", "--warnings", action="store_true",
404 404 help="include warning-level checks")
405 405 parser.add_option("-p", "--per-file", type="int",
406 406 help="max warnings per file")
407 407 parser.add_option("-b", "--blame", action="store_true",
408 408 help="use annotate to generate blame info")
409 409 parser.add_option("", "--debug", action="store_true",
410 410 help="show debug information")
411 parser.add_option("", "--nolineno", action="store_false",
412 dest='lineno', help="don't show line numbers")
411 413
412 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False)
414 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
415 lineno=True)
413 416 (options, args) = parser.parse_args()
414 417
415 418 if len(args) == 0:
416 419 check = glob.glob("*")
417 420 else:
418 421 check = args
419 422
420 423 for f in check:
421 424 ret = 0
422 425 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
423 blame=options.blame, debug=options.debug):
426 blame=options.blame, debug=options.debug,
427 lineno=options.lineno):
424 428 ret = 1
425 429 sys.exit(ret)
@@ -1,134 +1,142 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 >
26 26 > # Do not complain about our own definition
27 27 > def any(x):
28 28 > pass
29 29 >
30 30 > # try/except/finally block does not exist in Python 2.4
31 31 > try:
32 32 > pass
33 33 > except StandardError, inst:
34 34 > pass
35 35 > finally:
36 36 > pass
37 37 >
38 38 > # nested try/finally+try/except is allowed
39 39 > try:
40 40 > try:
41 41 > pass
42 42 > except StandardError, inst:
43 43 > pass
44 44 > finally:
45 45 > pass
46 46 > EOF
47 47 $ cat > classstyle.py <<EOF
48 48 > class newstyle_class(object):
49 49 > pass
50 50 >
51 51 > class oldstyle_class:
52 52 > pass
53 53 >
54 54 > class empty():
55 55 > pass
56 56 >
57 57 > no_class = 1:
58 58 > pass
59 59 > EOF
60 60 $ check_code="$TESTDIR"/../contrib/check-code.py
61 61 $ "$check_code" ./wrong.py ./correct.py ./quote.py ./non-py24.py ./classstyle.py
62 62 ./wrong.py:1:
63 63 > def toto( arg1, arg2):
64 64 gratuitous whitespace in () or []
65 65 ./wrong.py:2:
66 66 > del(arg2)
67 67 Python keyword is not a function
68 68 ./wrong.py:3:
69 69 > return ( 5+6, 9)
70 70 gratuitous whitespace in () or []
71 71 missing whitespace in expression
72 72 ./quote.py:5:
73 73 > '"""', 42+1, """and
74 74 missing whitespace in expression
75 75 ./non-py24.py:2:
76 76 > if any():
77 77 any/all/format not available in Python 2.4
78 78 ./non-py24.py:3:
79 79 > x = all()
80 80 any/all/format not available in Python 2.4
81 81 ./non-py24.py:4:
82 82 > y = format(x)
83 83 any/all/format not available in Python 2.4
84 84 ./non-py24.py:11:
85 85 > try:
86 86 no try/except/finally in Py2.4
87 87 ./classstyle.py:4:
88 88 > class oldstyle_class:
89 89 old-style class, use class foo(object)
90 90 ./classstyle.py:7:
91 91 > class empty():
92 92 class foo() not available in Python 2.4, use class foo(object)
93 93 [1]
94 94
95 95 $ cat > is-op.py <<EOF
96 96 > # is-operator comparing number or string literal
97 97 > x = None
98 98 > y = x is 'foo'
99 99 > y = x is "foo"
100 100 > y = x is 5346
101 101 > y = x is -6
102 102 > y = x is not 'foo'
103 103 > y = x is not "foo"
104 104 > y = x is not 5346
105 105 > y = x is not -6
106 106 > EOF
107 107
108 108 $ "$check_code" ./is-op.py
109 109 ./is-op.py:3:
110 110 > y = x is 'foo'
111 111 object comparison with literal
112 112 ./is-op.py:4:
113 113 > y = x is "foo"
114 114 object comparison with literal
115 115 ./is-op.py:5:
116 116 > y = x is 5346
117 117 object comparison with literal
118 118 ./is-op.py:6:
119 119 > y = x is -6
120 120 object comparison with literal
121 121 ./is-op.py:7:
122 122 > y = x is not 'foo'
123 123 object comparison with literal
124 124 ./is-op.py:8:
125 125 > y = x is not "foo"
126 126 object comparison with literal
127 127 ./is-op.py:9:
128 128 > y = x is not 5346
129 129 object comparison with literal
130 130 ./is-op.py:10:
131 131 > y = x is not -6
132 132 object comparison with literal
133 133 [1]
134 134
135 $ cat > warning.py <<EOF
136 > except:
137 > EOF
138 $ "$check_code" warning.py --warning --nolineno
139 warning.py:0:
140 > except:
141 warning: naked except clause
142 [1]
General Comments 0
You need to be logged in to leave comments. Login now