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