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