##// END OF EJS Templates
check-code: detect legacy exception syntax...
Gregory Szorc -
r25661:20de1ace default
parent child Browse files
Show More
@@ -1,571 +1,573 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # check-code - a style and portability checker for Mercurial
3 # check-code - a style and portability checker for Mercurial
4 #
4 #
5 # Copyright 2010 Matt Mackall <mpm@selenic.com>
5 # Copyright 2010 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """style and portability checker for Mercurial
10 """style and portability checker for Mercurial
11
11
12 when a rule triggers wrong, do one of the following (prefer one from top):
12 when a rule triggers wrong, do one of the following (prefer one from top):
13 * do the work-around the rule suggests
13 * do the work-around the rule suggests
14 * doublecheck that it is a false match
14 * doublecheck that it is a false match
15 * improve the rule pattern
15 * improve the rule pattern
16 * add an ignore pattern to the rule (3rd arg) which matches your good line
16 * add an ignore pattern to the rule (3rd arg) which matches your good line
17 (you can append a short comment and match this, like: #re-raises, # no-py24)
17 (you can append a short comment and match this, like: #re-raises, # no-py24)
18 * change the pattern to a warning and list the exception in test-check-code-hg
18 * change the pattern to a warning and list the exception in test-check-code-hg
19 * ONLY use no--check-code for skipping entire files from external sources
19 * ONLY use no--check-code for skipping entire files from external sources
20 """
20 """
21
21
22 import re, glob, os, sys
22 import re, glob, os, sys
23 import keyword
23 import keyword
24 import optparse
24 import optparse
25 try:
25 try:
26 import re2
26 import re2
27 except ImportError:
27 except ImportError:
28 re2 = None
28 re2 = None
29
29
30 def compilere(pat, multiline=False):
30 def compilere(pat, multiline=False):
31 if multiline:
31 if multiline:
32 pat = '(?m)' + pat
32 pat = '(?m)' + pat
33 if re2:
33 if re2:
34 try:
34 try:
35 return re2.compile(pat)
35 return re2.compile(pat)
36 except re2.error:
36 except re2.error:
37 pass
37 pass
38 return re.compile(pat)
38 return re.compile(pat)
39
39
40 def repquote(m):
40 def repquote(m):
41 fromc = '.:'
41 fromc = '.:'
42 tochr = 'pq'
42 tochr = 'pq'
43 def encodechr(i):
43 def encodechr(i):
44 if i > 255:
44 if i > 255:
45 return 'u'
45 return 'u'
46 c = chr(i)
46 c = chr(i)
47 if c in ' \n':
47 if c in ' \n':
48 return c
48 return c
49 if c.isalpha():
49 if c.isalpha():
50 return 'x'
50 return 'x'
51 if c.isdigit():
51 if c.isdigit():
52 return 'n'
52 return 'n'
53 try:
53 try:
54 return tochr[fromc.find(c)]
54 return tochr[fromc.find(c)]
55 except (ValueError, IndexError):
55 except (ValueError, IndexError):
56 return 'o'
56 return 'o'
57 t = m.group('text')
57 t = m.group('text')
58 tt = ''.join(encodechr(i) for i in xrange(256))
58 tt = ''.join(encodechr(i) for i in xrange(256))
59 t = t.translate(tt)
59 t = t.translate(tt)
60 return m.group('quote') + t + m.group('quote')
60 return m.group('quote') + t + m.group('quote')
61
61
62 def reppython(m):
62 def reppython(m):
63 comment = m.group('comment')
63 comment = m.group('comment')
64 if comment:
64 if comment:
65 l = len(comment.rstrip())
65 l = len(comment.rstrip())
66 return "#" * l + comment[l:]
66 return "#" * l + comment[l:]
67 return repquote(m)
67 return repquote(m)
68
68
69 def repcomment(m):
69 def repcomment(m):
70 return m.group(1) + "#" * len(m.group(2))
70 return m.group(1) + "#" * len(m.group(2))
71
71
72 def repccomment(m):
72 def repccomment(m):
73 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
73 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
74 return m.group(1) + t + "*/"
74 return m.group(1) + t + "*/"
75
75
76 def repcallspaces(m):
76 def repcallspaces(m):
77 t = re.sub(r"\n\s+", "\n", m.group(2))
77 t = re.sub(r"\n\s+", "\n", m.group(2))
78 return m.group(1) + t
78 return m.group(1) + t
79
79
80 def repinclude(m):
80 def repinclude(m):
81 return m.group(1) + "<foo>"
81 return m.group(1) + "<foo>"
82
82
83 def rephere(m):
83 def rephere(m):
84 t = re.sub(r"\S", "x", m.group(2))
84 t = re.sub(r"\S", "x", m.group(2))
85 return m.group(1) + t
85 return m.group(1) + t
86
86
87
87
88 testpats = [
88 testpats = [
89 [
89 [
90 (r'pushd|popd', "don't use 'pushd' or 'popd', use 'cd'"),
90 (r'pushd|popd', "don't use 'pushd' or 'popd', use 'cd'"),
91 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
91 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
92 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
92 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
93 (r'(?<!hg )grep.*-a', "don't use 'grep -a', use in-line python"),
93 (r'(?<!hg )grep.*-a', "don't use 'grep -a', use in-line python"),
94 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
94 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
95 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
95 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
96 (r'echo -n', "don't use 'echo -n', use printf"),
96 (r'echo -n', "don't use 'echo -n', use printf"),
97 (r'(^|\|\s*)\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"),
97 (r'(^|\|\s*)\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"),
98 (r'head -c', "don't use 'head -c', use 'dd'"),
98 (r'head -c', "don't use 'head -c', use 'dd'"),
99 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
99 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
100 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
100 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
101 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
101 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
102 (r'printf.*[^\\]\\([1-9]|0\d)', "don't use 'printf \NNN', use Python"),
102 (r'printf.*[^\\]\\([1-9]|0\d)', "don't use 'printf \NNN', use Python"),
103 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
103 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
104 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
104 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
105 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
105 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
106 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
106 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
107 "use egrep for extended grep syntax"),
107 "use egrep for extended grep syntax"),
108 (r'/bin/', "don't use explicit paths for tools"),
108 (r'/bin/', "don't use explicit paths for tools"),
109 (r'[^\n]\Z', "no trailing newline"),
109 (r'[^\n]\Z', "no trailing newline"),
110 (r'export.*=', "don't export and assign at once"),
110 (r'export.*=', "don't export and assign at once"),
111 (r'^source\b', "don't use 'source', use '.'"),
111 (r'^source\b', "don't use 'source', use '.'"),
112 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
112 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
113 (r'ls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
113 (r'ls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
114 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
114 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
115 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
115 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
116 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
116 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
117 (r'\[\[\s+[^\]]*\]\]', "don't use '[[ ]]', use '[ ]'"),
117 (r'\[\[\s+[^\]]*\]\]', "don't use '[[ ]]', use '[ ]'"),
118 (r'^alias\b.*=', "don't use alias, use a function"),
118 (r'^alias\b.*=', "don't use alias, use a function"),
119 (r'if\s*!', "don't use '!' to negate exit status"),
119 (r'if\s*!', "don't use '!' to negate exit status"),
120 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
120 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
121 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
121 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
122 (r'^( *)\t', "don't use tabs to indent"),
122 (r'^( *)\t', "don't use tabs to indent"),
123 (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
123 (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
124 "put a backslash-escaped newline after sed 'i' command"),
124 "put a backslash-escaped newline after sed 'i' command"),
125 (r'^diff *-\w*u.*$\n(^ \$ |^$)', "prefix diff -u with cmp"),
125 (r'^diff *-\w*u.*$\n(^ \$ |^$)', "prefix diff -u with cmp"),
126 (r'seq ', "don't use 'seq', use $TESTDIR/seq.py")
126 (r'seq ', "don't use 'seq', use $TESTDIR/seq.py")
127 ],
127 ],
128 # warnings
128 # warnings
129 [
129 [
130 (r'^function', "don't use 'function', use old style"),
130 (r'^function', "don't use 'function', use old style"),
131 (r'^diff.*-\w*N', "don't use 'diff -N'"),
131 (r'^diff.*-\w*N', "don't use 'diff -N'"),
132 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
132 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
133 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
133 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
134 (r'kill (`|\$\()', "don't use kill, use killdaemons.py")
134 (r'kill (`|\$\()', "don't use kill, use killdaemons.py")
135 ]
135 ]
136 ]
136 ]
137
137
138 testfilters = [
138 testfilters = [
139 (r"( *)(#([^\n]*\S)?)", repcomment),
139 (r"( *)(#([^\n]*\S)?)", repcomment),
140 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
140 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
141 ]
141 ]
142
142
143 winglobmsg = "use (glob) to match Windows paths too"
143 winglobmsg = "use (glob) to match Windows paths too"
144 uprefix = r"^ \$ "
144 uprefix = r"^ \$ "
145 utestpats = [
145 utestpats = [
146 [
146 [
147 (r'^(\S.*|| [$>] .*)[ \t]\n', "trailing whitespace on non-output"),
147 (r'^(\S.*|| [$>] .*)[ \t]\n', "trailing whitespace on non-output"),
148 (uprefix + r'.*\|\s*sed[^|>\n]*\n',
148 (uprefix + r'.*\|\s*sed[^|>\n]*\n',
149 "use regex test output patterns instead of sed"),
149 "use regex test output patterns instead of sed"),
150 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
150 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
151 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
151 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
152 (uprefix + r'.*\|\| echo.*(fail|error)',
152 (uprefix + r'.*\|\| echo.*(fail|error)',
153 "explicit exit code checks unnecessary"),
153 "explicit exit code checks unnecessary"),
154 (uprefix + r'set -e', "don't use set -e"),
154 (uprefix + r'set -e', "don't use set -e"),
155 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
155 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
156 (uprefix + r'.*:\.\S*/', "x:.y in a path does not work on msys, rewrite "
156 (uprefix + r'.*:\.\S*/', "x:.y in a path does not work on msys, rewrite "
157 "as x://.y, or see `hg log -k msys` for alternatives", r'-\S+:\.|' #-Rxxx
157 "as x://.y, or see `hg log -k msys` for alternatives", r'-\S+:\.|' #-Rxxx
158 '# no-msys'), # in test-pull.t which is skipped on windows
158 '# no-msys'), # in test-pull.t which is skipped on windows
159 (r'^ saved backup bundle to \$TESTTMP.*\.hg$', winglobmsg),
159 (r'^ saved backup bundle to \$TESTTMP.*\.hg$', winglobmsg),
160 (r'^ changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
160 (r'^ changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
161 winglobmsg),
161 winglobmsg),
162 (r'^ pulling from \$TESTTMP/.*[^)]$', winglobmsg,
162 (r'^ pulling from \$TESTTMP/.*[^)]$', winglobmsg,
163 '\$TESTTMP/unix-repo$'), # in test-issue1802.t which skipped on windows
163 '\$TESTTMP/unix-repo$'), # in test-issue1802.t which skipped on windows
164 (r'^ reverting (?!subrepo ).*/.*[^)]$', winglobmsg),
164 (r'^ reverting (?!subrepo ).*/.*[^)]$', winglobmsg),
165 (r'^ cloning subrepo \S+/.*[^)]$', winglobmsg),
165 (r'^ cloning subrepo \S+/.*[^)]$', winglobmsg),
166 (r'^ pushing to \$TESTTMP/.*[^)]$', winglobmsg),
166 (r'^ pushing to \$TESTTMP/.*[^)]$', winglobmsg),
167 (r'^ pushing subrepo \S+/\S+ to.*[^)]$', winglobmsg),
167 (r'^ pushing subrepo \S+/\S+ to.*[^)]$', winglobmsg),
168 (r'^ moving \S+/.*[^)]$', winglobmsg),
168 (r'^ moving \S+/.*[^)]$', winglobmsg),
169 (r'^ no changes made to subrepo since.*/.*[^)]$', winglobmsg),
169 (r'^ no changes made to subrepo since.*/.*[^)]$', winglobmsg),
170 (r'^ .*: largefile \S+ not available from file:.*/.*[^)]$', winglobmsg),
170 (r'^ .*: largefile \S+ not available from file:.*/.*[^)]$', winglobmsg),
171 (r'^ .*file://\$TESTTMP',
171 (r'^ .*file://\$TESTTMP',
172 'write "file:/*/$TESTTMP" + (glob) to match on windows too'),
172 'write "file:/*/$TESTTMP" + (glob) to match on windows too'),
173 (r'^ (cat|find): .*: No such file or directory',
173 (r'^ (cat|find): .*: No such file or directory',
174 'use test -f to test for file existence'),
174 'use test -f to test for file existence'),
175 ],
175 ],
176 # warnings
176 # warnings
177 [
177 [
178 (r'^ [^*?/\n]* \(glob\)$',
178 (r'^ [^*?/\n]* \(glob\)$',
179 "glob match with no glob character (?*/)"),
179 "glob match with no glob character (?*/)"),
180 ]
180 ]
181 ]
181 ]
182
182
183 for i in [0, 1]:
183 for i in [0, 1]:
184 for tp in testpats[i]:
184 for tp in testpats[i]:
185 p = tp[0]
185 p = tp[0]
186 m = tp[1]
186 m = tp[1]
187 if p.startswith(r'^'):
187 if p.startswith(r'^'):
188 p = r"^ [$>] (%s)" % p[1:]
188 p = r"^ [$>] (%s)" % p[1:]
189 else:
189 else:
190 p = r"^ [$>] .*(%s)" % p
190 p = r"^ [$>] .*(%s)" % p
191 utestpats[i].append((p, m) + tp[2:])
191 utestpats[i].append((p, m) + tp[2:])
192
192
193 utestfilters = [
193 utestfilters = [
194 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
194 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
195 (r"( *)(#([^\n]*\S)?)", repcomment),
195 (r"( *)(#([^\n]*\S)?)", repcomment),
196 ]
196 ]
197
197
198 pypats = [
198 pypats = [
199 [
199 [
200 (r'\([^)]*\*\w[^()]+\w+=', "can't pass varargs with keyword in Py2.5"),
200 (r'\([^)]*\*\w[^()]+\w+=', "can't pass varargs with keyword in Py2.5"),
201 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
201 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
202 "tuple parameter unpacking not available in Python 3+"),
202 "tuple parameter unpacking not available in Python 3+"),
203 (r'lambda\s*\(.*,.*\)',
203 (r'lambda\s*\(.*,.*\)',
204 "tuple parameter unpacking not available in Python 3+"),
204 "tuple parameter unpacking not available in Python 3+"),
205 (r'import (.+,[^.]+\.[^.]+|[^.]+\.[^.]+,)',
205 (r'import (.+,[^.]+\.[^.]+|[^.]+\.[^.]+,)',
206 '2to3 can\'t always rewrite "import qux, foo.bar", '
206 '2to3 can\'t always rewrite "import qux, foo.bar", '
207 'use "import foo.bar" on its own line instead.'),
207 'use "import foo.bar" on its own line instead.'),
208 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
208 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
209 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
209 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
210 (r'dict\(.*=', 'dict() is different in Py2 and 3 and is slower than {}',
210 (r'dict\(.*=', 'dict() is different in Py2 and 3 and is slower than {}',
211 'dict-from-generator'),
211 'dict-from-generator'),
212 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
212 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
213 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
213 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
214 (r'^\s*\t', "don't use tabs"),
214 (r'^\s*\t', "don't use tabs"),
215 (r'\S;\s*\n', "semicolon"),
215 (r'\S;\s*\n', "semicolon"),
216 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
216 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
217 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
217 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
218 (r'(\w|\)),\w', "missing whitespace after ,"),
218 (r'(\w|\)),\w', "missing whitespace after ,"),
219 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
219 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
220 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
220 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
221 (r'.{81}', "line too long"),
221 (r'.{81}', "line too long"),
222 (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
222 (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
223 (r'[^\n]\Z', "no trailing newline"),
223 (r'[^\n]\Z', "no trailing newline"),
224 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
224 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
225 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
225 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
226 # "don't use underbars in identifiers"),
226 # "don't use underbars in identifiers"),
227 (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
227 (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
228 "don't use camelcase in identifiers"),
228 "don't use camelcase in identifiers"),
229 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
229 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
230 "linebreak after :"),
230 "linebreak after :"),
231 (r'class\s[^( \n]+:', "old-style class, use class foo(object)"),
231 (r'class\s[^( \n]+:', "old-style class, use class foo(object)"),
232 (r'class\s[^( \n]+\(\):',
232 (r'class\s[^( \n]+\(\):',
233 "class foo() creates old style object, use class foo(object)"),
233 "class foo() creates old style object, use class foo(object)"),
234 (r'\b(%s)\(' % '|'.join(k for k in keyword.kwlist
234 (r'\b(%s)\(' % '|'.join(k for k in keyword.kwlist
235 if k not in ('print', 'exec')),
235 if k not in ('print', 'exec')),
236 "Python keyword is not a function"),
236 "Python keyword is not a function"),
237 (r',]', "unneeded trailing ',' in list"),
237 (r',]', "unneeded trailing ',' in list"),
238 # (r'class\s[A-Z][^\(]*\((?!Exception)',
238 # (r'class\s[A-Z][^\(]*\((?!Exception)',
239 # "don't capitalize non-exception classes"),
239 # "don't capitalize non-exception classes"),
240 # (r'in range\(', "use xrange"),
240 # (r'in range\(', "use xrange"),
241 # (r'^\s*print\s+', "avoid using print in core and extensions"),
241 # (r'^\s*print\s+', "avoid using print in core and extensions"),
242 (r'[\x80-\xff]', "non-ASCII character literal"),
242 (r'[\x80-\xff]', "non-ASCII character literal"),
243 (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"),
243 (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"),
244 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
244 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
245 "gratuitous whitespace after Python keyword"),
245 "gratuitous whitespace after Python keyword"),
246 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
246 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
247 # (r'\s\s=', "gratuitous whitespace before ="),
247 # (r'\s\s=', "gratuitous whitespace before ="),
248 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
248 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
249 "missing whitespace around operator"),
249 "missing whitespace around operator"),
250 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
250 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
251 "missing whitespace around operator"),
251 "missing whitespace around operator"),
252 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
252 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
253 "missing whitespace around operator"),
253 "missing whitespace around operator"),
254 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
254 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
255 "wrong whitespace around ="),
255 "wrong whitespace around ="),
256 (r'\([^()]*( =[^=]|[^<>!=]= )',
256 (r'\([^()]*( =[^=]|[^<>!=]= )',
257 "no whitespace around = for named parameters"),
257 "no whitespace around = for named parameters"),
258 (r'raise Exception', "don't raise generic exceptions"),
258 (r'raise Exception', "don't raise generic exceptions"),
259 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
259 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
260 "don't use old-style two-argument raise, use Exception(message)"),
260 "don't use old-style two-argument raise, use Exception(message)"),
261 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
261 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
262 (r' [=!]=\s+(True|False|None)',
262 (r' [=!]=\s+(True|False|None)',
263 "comparison with singleton, use 'is' or 'is not' instead"),
263 "comparison with singleton, use 'is' or 'is not' instead"),
264 (r'^\s*(while|if) [01]:',
264 (r'^\s*(while|if) [01]:',
265 "use True/False for constant Boolean expression"),
265 "use True/False for constant Boolean expression"),
266 (r'(?:(?<!def)\s+|\()hasattr',
266 (r'(?:(?<!def)\s+|\()hasattr',
267 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
267 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
268 (r'opener\([^)]*\).read\(',
268 (r'opener\([^)]*\).read\(',
269 "use opener.read() instead"),
269 "use opener.read() instead"),
270 (r'opener\([^)]*\).write\(',
270 (r'opener\([^)]*\).write\(',
271 "use opener.write() instead"),
271 "use opener.write() instead"),
272 (r'[\s\(](open|file)\([^)]*\)\.read\(',
272 (r'[\s\(](open|file)\([^)]*\)\.read\(',
273 "use util.readfile() instead"),
273 "use util.readfile() instead"),
274 (r'[\s\(](open|file)\([^)]*\)\.write\(',
274 (r'[\s\(](open|file)\([^)]*\)\.write\(',
275 "use util.writefile() instead"),
275 "use util.writefile() instead"),
276 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
276 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
277 "always assign an opened file to a variable, and close it afterwards"),
277 "always assign an opened file to a variable, and close it afterwards"),
278 (r'[\s\(](open|file)\([^)]*\)\.',
278 (r'[\s\(](open|file)\([^)]*\)\.',
279 "always assign an opened file to a variable, and close it afterwards"),
279 "always assign an opened file to a variable, and close it afterwards"),
280 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
280 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
281 (r'\.debug\(\_', "don't mark debug messages for translation"),
281 (r'\.debug\(\_', "don't mark debug messages for translation"),
282 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
282 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
283 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
283 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
284 (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
285 'legacy exception syntax; use "as" instead of ","'),
284 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
286 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
285 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
287 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
286 "missing _() in ui message (use () to hide false-positives)"),
288 "missing _() in ui message (use () to hide false-positives)"),
287 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
289 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
288 (r'\b__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
290 (r'\b__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
289 (r'os\.path\.join\(.*, *(""|\'\')\)',
291 (r'os\.path\.join\(.*, *(""|\'\')\)',
290 "use pathutil.normasprefix(path) instead of os.path.join(path, '')"),
292 "use pathutil.normasprefix(path) instead of os.path.join(path, '')"),
291 (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
293 (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
292 ],
294 ],
293 # warnings
295 # warnings
294 [
296 [
295 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
297 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
296 ]
298 ]
297 ]
299 ]
298
300
299 pyfilters = [
301 pyfilters = [
300 (r"""(?msx)(?P<comment>\#.*?$)|
302 (r"""(?msx)(?P<comment>\#.*?$)|
301 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
303 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
302 (?P<text>(([^\\]|\\.)*?))
304 (?P<text>(([^\\]|\\.)*?))
303 (?P=quote))""", reppython),
305 (?P=quote))""", reppython),
304 ]
306 ]
305
307
306 txtfilters = []
308 txtfilters = []
307
309
308 txtpats = [
310 txtpats = [
309 [
311 [
310 ('\s$', 'trailing whitespace'),
312 ('\s$', 'trailing whitespace'),
311 ('.. note::[ \n][^\n]', 'add two newlines after note::')
313 ('.. note::[ \n][^\n]', 'add two newlines after note::')
312 ],
314 ],
313 []
315 []
314 ]
316 ]
315
317
316 cpats = [
318 cpats = [
317 [
319 [
318 (r'//', "don't use //-style comments"),
320 (r'//', "don't use //-style comments"),
319 (r'^ ', "don't use spaces to indent"),
321 (r'^ ', "don't use spaces to indent"),
320 (r'\S\t', "don't use tabs except for indent"),
322 (r'\S\t', "don't use tabs except for indent"),
321 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
323 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
322 (r'.{81}', "line too long"),
324 (r'.{81}', "line too long"),
323 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
325 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
324 (r'return\(', "return is not a function"),
326 (r'return\(', "return is not a function"),
325 (r' ;', "no space before ;"),
327 (r' ;', "no space before ;"),
326 (r'[^;] \)', "no space before )"),
328 (r'[^;] \)', "no space before )"),
327 (r'[)][{]', "space between ) and {"),
329 (r'[)][{]', "space between ) and {"),
328 (r'\w+\* \w+', "use int *foo, not int* foo"),
330 (r'\w+\* \w+', "use int *foo, not int* foo"),
329 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
331 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
330 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
332 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
331 (r'\w,\w', "missing whitespace after ,"),
333 (r'\w,\w', "missing whitespace after ,"),
332 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
334 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
333 (r'^#\s+\w', "use #foo, not # foo"),
335 (r'^#\s+\w', "use #foo, not # foo"),
334 (r'[^\n]\Z', "no trailing newline"),
336 (r'[^\n]\Z', "no trailing newline"),
335 (r'^\s*#import\b', "use only #include in standard C code"),
337 (r'^\s*#import\b', "use only #include in standard C code"),
336 ],
338 ],
337 # warnings
339 # warnings
338 []
340 []
339 ]
341 ]
340
342
341 cfilters = [
343 cfilters = [
342 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
344 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
343 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
345 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
344 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
346 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
345 (r'(\()([^)]+\))', repcallspaces),
347 (r'(\()([^)]+\))', repcallspaces),
346 ]
348 ]
347
349
348 inutilpats = [
350 inutilpats = [
349 [
351 [
350 (r'\bui\.', "don't use ui in util"),
352 (r'\bui\.', "don't use ui in util"),
351 ],
353 ],
352 # warnings
354 # warnings
353 []
355 []
354 ]
356 ]
355
357
356 inrevlogpats = [
358 inrevlogpats = [
357 [
359 [
358 (r'\brepo\.', "don't use repo in revlog"),
360 (r'\brepo\.', "don't use repo in revlog"),
359 ],
361 ],
360 # warnings
362 # warnings
361 []
363 []
362 ]
364 ]
363
365
364 webtemplatefilters = []
366 webtemplatefilters = []
365
367
366 webtemplatepats = [
368 webtemplatepats = [
367 [],
369 [],
368 [
370 [
369 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
371 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
370 'follow desc keyword with either firstline or websub'),
372 'follow desc keyword with either firstline or websub'),
371 ]
373 ]
372 ]
374 ]
373
375
374 checks = [
376 checks = [
375 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
377 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
376 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
378 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
377 ('c', r'.*\.[ch]$', '', cfilters, cpats),
379 ('c', r'.*\.[ch]$', '', cfilters, cpats),
378 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
380 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
379 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
381 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
380 pyfilters, inrevlogpats),
382 pyfilters, inrevlogpats),
381 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
383 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
382 inutilpats),
384 inutilpats),
383 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
385 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
384 ('web template', r'mercurial/templates/.*\.tmpl', '',
386 ('web template', r'mercurial/templates/.*\.tmpl', '',
385 webtemplatefilters, webtemplatepats),
387 webtemplatefilters, webtemplatepats),
386 ]
388 ]
387
389
388 def _preparepats():
390 def _preparepats():
389 for c in checks:
391 for c in checks:
390 failandwarn = c[-1]
392 failandwarn = c[-1]
391 for pats in failandwarn:
393 for pats in failandwarn:
392 for i, pseq in enumerate(pats):
394 for i, pseq in enumerate(pats):
393 # fix-up regexes for multi-line searches
395 # fix-up regexes for multi-line searches
394 p = pseq[0]
396 p = pseq[0]
395 # \s doesn't match \n
397 # \s doesn't match \n
396 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
398 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
397 # [^...] doesn't match newline
399 # [^...] doesn't match newline
398 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
400 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
399
401
400 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
402 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
401 filters = c[3]
403 filters = c[3]
402 for i, flt in enumerate(filters):
404 for i, flt in enumerate(filters):
403 filters[i] = re.compile(flt[0]), flt[1]
405 filters[i] = re.compile(flt[0]), flt[1]
404 _preparepats()
406 _preparepats()
405
407
406 class norepeatlogger(object):
408 class norepeatlogger(object):
407 def __init__(self):
409 def __init__(self):
408 self._lastseen = None
410 self._lastseen = None
409
411
410 def log(self, fname, lineno, line, msg, blame):
412 def log(self, fname, lineno, line, msg, blame):
411 """print error related a to given line of a given file.
413 """print error related a to given line of a given file.
412
414
413 The faulty line will also be printed but only once in the case
415 The faulty line will also be printed but only once in the case
414 of multiple errors.
416 of multiple errors.
415
417
416 :fname: filename
418 :fname: filename
417 :lineno: line number
419 :lineno: line number
418 :line: actual content of the line
420 :line: actual content of the line
419 :msg: error message
421 :msg: error message
420 """
422 """
421 msgid = fname, lineno, line
423 msgid = fname, lineno, line
422 if msgid != self._lastseen:
424 if msgid != self._lastseen:
423 if blame:
425 if blame:
424 print "%s:%d (%s):" % (fname, lineno, blame)
426 print "%s:%d (%s):" % (fname, lineno, blame)
425 else:
427 else:
426 print "%s:%d:" % (fname, lineno)
428 print "%s:%d:" % (fname, lineno)
427 print " > %s" % line
429 print " > %s" % line
428 self._lastseen = msgid
430 self._lastseen = msgid
429 print " " + msg
431 print " " + msg
430
432
431 _defaultlogger = norepeatlogger()
433 _defaultlogger = norepeatlogger()
432
434
433 def getblame(f):
435 def getblame(f):
434 lines = []
436 lines = []
435 for l in os.popen('hg annotate -un %s' % f):
437 for l in os.popen('hg annotate -un %s' % f):
436 start, line = l.split(':', 1)
438 start, line = l.split(':', 1)
437 user, rev = start.split()
439 user, rev = start.split()
438 lines.append((line[1:-1], user, rev))
440 lines.append((line[1:-1], user, rev))
439 return lines
441 return lines
440
442
441 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
443 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
442 blame=False, debug=False, lineno=True):
444 blame=False, debug=False, lineno=True):
443 """checks style and portability of a given file
445 """checks style and portability of a given file
444
446
445 :f: filepath
447 :f: filepath
446 :logfunc: function used to report error
448 :logfunc: function used to report error
447 logfunc(filename, linenumber, linecontent, errormessage)
449 logfunc(filename, linenumber, linecontent, errormessage)
448 :maxerr: number of error to display before aborting.
450 :maxerr: number of error to display before aborting.
449 Set to false (default) to report all errors
451 Set to false (default) to report all errors
450
452
451 return True if no error is found, False otherwise.
453 return True if no error is found, False otherwise.
452 """
454 """
453 blamecache = None
455 blamecache = None
454 result = True
456 result = True
455
457
456 try:
458 try:
457 fp = open(f)
459 fp = open(f)
458 except IOError as e:
460 except IOError as e:
459 print "Skipping %s, %s" % (f, str(e).split(':', 1)[0])
461 print "Skipping %s, %s" % (f, str(e).split(':', 1)[0])
460 return result
462 return result
461 pre = post = fp.read()
463 pre = post = fp.read()
462 fp.close()
464 fp.close()
463
465
464 for name, match, magic, filters, pats in checks:
466 for name, match, magic, filters, pats in checks:
465 if debug:
467 if debug:
466 print name, f
468 print name, f
467 fc = 0
469 fc = 0
468 if not (re.match(match, f) or (magic and re.search(magic, f))):
470 if not (re.match(match, f) or (magic and re.search(magic, f))):
469 if debug:
471 if debug:
470 print "Skipping %s for %s it doesn't match %s" % (
472 print "Skipping %s for %s it doesn't match %s" % (
471 name, match, f)
473 name, match, f)
472 continue
474 continue
473 if "no-" "check-code" in pre:
475 if "no-" "check-code" in pre:
474 print "Skipping %s it has no-" "check-code" % f
476 print "Skipping %s it has no-" "check-code" % f
475 return "Skip" # skip checking this file
477 return "Skip" # skip checking this file
476 for p, r in filters:
478 for p, r in filters:
477 post = re.sub(p, r, post)
479 post = re.sub(p, r, post)
478 nerrs = len(pats[0]) # nerr elements are errors
480 nerrs = len(pats[0]) # nerr elements are errors
479 if warnings:
481 if warnings:
480 pats = pats[0] + pats[1]
482 pats = pats[0] + pats[1]
481 else:
483 else:
482 pats = pats[0]
484 pats = pats[0]
483 # print post # uncomment to show filtered version
485 # print post # uncomment to show filtered version
484
486
485 if debug:
487 if debug:
486 print "Checking %s for %s" % (name, f)
488 print "Checking %s for %s" % (name, f)
487
489
488 prelines = None
490 prelines = None
489 errors = []
491 errors = []
490 for i, pat in enumerate(pats):
492 for i, pat in enumerate(pats):
491 if len(pat) == 3:
493 if len(pat) == 3:
492 p, msg, ignore = pat
494 p, msg, ignore = pat
493 else:
495 else:
494 p, msg = pat
496 p, msg = pat
495 ignore = None
497 ignore = None
496 if i >= nerrs:
498 if i >= nerrs:
497 msg = "warning: " + msg
499 msg = "warning: " + msg
498
500
499 pos = 0
501 pos = 0
500 n = 0
502 n = 0
501 for m in p.finditer(post):
503 for m in p.finditer(post):
502 if prelines is None:
504 if prelines is None:
503 prelines = pre.splitlines()
505 prelines = pre.splitlines()
504 postlines = post.splitlines(True)
506 postlines = post.splitlines(True)
505
507
506 start = m.start()
508 start = m.start()
507 while n < len(postlines):
509 while n < len(postlines):
508 step = len(postlines[n])
510 step = len(postlines[n])
509 if pos + step > start:
511 if pos + step > start:
510 break
512 break
511 pos += step
513 pos += step
512 n += 1
514 n += 1
513 l = prelines[n]
515 l = prelines[n]
514
516
515 if ignore and re.search(ignore, l, re.MULTILINE):
517 if ignore and re.search(ignore, l, re.MULTILINE):
516 if debug:
518 if debug:
517 print "Skipping %s for %s:%s (ignore pattern)" % (
519 print "Skipping %s for %s:%s (ignore pattern)" % (
518 name, f, n)
520 name, f, n)
519 continue
521 continue
520 bd = ""
522 bd = ""
521 if blame:
523 if blame:
522 bd = 'working directory'
524 bd = 'working directory'
523 if not blamecache:
525 if not blamecache:
524 blamecache = getblame(f)
526 blamecache = getblame(f)
525 if n < len(blamecache):
527 if n < len(blamecache):
526 bl, bu, br = blamecache[n]
528 bl, bu, br = blamecache[n]
527 if bl == l:
529 if bl == l:
528 bd = '%s@%s' % (bu, br)
530 bd = '%s@%s' % (bu, br)
529
531
530 errors.append((f, lineno and n + 1, l, msg, bd))
532 errors.append((f, lineno and n + 1, l, msg, bd))
531 result = False
533 result = False
532
534
533 errors.sort()
535 errors.sort()
534 for e in errors:
536 for e in errors:
535 logfunc(*e)
537 logfunc(*e)
536 fc += 1
538 fc += 1
537 if maxerr and fc >= maxerr:
539 if maxerr and fc >= maxerr:
538 print " (too many errors, giving up)"
540 print " (too many errors, giving up)"
539 break
541 break
540
542
541 return result
543 return result
542
544
543 if __name__ == "__main__":
545 if __name__ == "__main__":
544 parser = optparse.OptionParser("%prog [options] [files]")
546 parser = optparse.OptionParser("%prog [options] [files]")
545 parser.add_option("-w", "--warnings", action="store_true",
547 parser.add_option("-w", "--warnings", action="store_true",
546 help="include warning-level checks")
548 help="include warning-level checks")
547 parser.add_option("-p", "--per-file", type="int",
549 parser.add_option("-p", "--per-file", type="int",
548 help="max warnings per file")
550 help="max warnings per file")
549 parser.add_option("-b", "--blame", action="store_true",
551 parser.add_option("-b", "--blame", action="store_true",
550 help="use annotate to generate blame info")
552 help="use annotate to generate blame info")
551 parser.add_option("", "--debug", action="store_true",
553 parser.add_option("", "--debug", action="store_true",
552 help="show debug information")
554 help="show debug information")
553 parser.add_option("", "--nolineno", action="store_false",
555 parser.add_option("", "--nolineno", action="store_false",
554 dest='lineno', help="don't show line numbers")
556 dest='lineno', help="don't show line numbers")
555
557
556 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
558 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
557 lineno=True)
559 lineno=True)
558 (options, args) = parser.parse_args()
560 (options, args) = parser.parse_args()
559
561
560 if len(args) == 0:
562 if len(args) == 0:
561 check = glob.glob("*")
563 check = glob.glob("*")
562 else:
564 else:
563 check = args
565 check = args
564
566
565 ret = 0
567 ret = 0
566 for f in check:
568 for f in check:
567 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
569 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
568 blame=options.blame, debug=options.debug,
570 blame=options.blame, debug=options.debug,
569 lineno=options.lineno):
571 lineno=options.lineno):
570 ret = 1
572 ret = 1
571 sys.exit(ret)
573 sys.exit(ret)
General Comments 0
You need to be logged in to leave comments. Login now