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