##// END OF EJS Templates
check-code: fix the error message about 'class foo():'...
Pierre-Yves David -
r25140:317333e0 default
parent child Browse files
Show More
@@ -1,579 +1,579 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'^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'(?<!def)(\s+|^|\()next\(.+\)',
220 (r'(?<!def)(\s+|^|\()next\(.+\)',
221 'no next(foo) in Python 2.4 and 2.5, use foo.next() instead'),
221 'no next(foo) in Python 2.4 and 2.5, use foo.next() instead'),
222 (r'(\s+)try:\n((?:\n|\1\s.*\n)*?)\1\s*yield\b.*?'
222 (r'(\s+)try:\n((?:\n|\1\s.*\n)*?)\1\s*yield\b.*?'
223 r'((?:\n|\1\s.*\n)+?)\1finally:',
223 r'((?:\n|\1\s.*\n)+?)\1finally:',
224 'no yield inside try/finally in Python 2.4'),
224 'no yield inside try/finally in Python 2.4'),
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() not available in Python 2.4, 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() not available in Python 2.4"),
247 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
248 (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"),
248 (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"),
249 (r'(?<!def)\s+(any|all|format)\(',
249 (r'(?<!def)\s+(any|all|format)\(',
250 "any/all/format not available in Python 2.4", 'no-py24'),
250 "any/all/format not available in Python 2.4", 'no-py24'),
251 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
251 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
252 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
252 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
253 "gratuitous whitespace after Python keyword"),
253 "gratuitous whitespace after Python keyword"),
254 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
254 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
255 # (r'\s\s=', "gratuitous whitespace before ="),
255 # (r'\s\s=', "gratuitous whitespace before ="),
256 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
256 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
257 "missing whitespace around operator"),
257 "missing whitespace around operator"),
258 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
258 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
259 "missing whitespace around operator"),
259 "missing whitespace around operator"),
260 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
260 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
261 "missing whitespace around operator"),
261 "missing whitespace around operator"),
262 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
262 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
263 "wrong whitespace around ="),
263 "wrong whitespace around ="),
264 (r'\([^()]*( =[^=]|[^<>!=]= )',
264 (r'\([^()]*( =[^=]|[^<>!=]= )',
265 "no whitespace around = for named parameters"),
265 "no whitespace around = for named parameters"),
266 (r'raise Exception', "don't raise generic exceptions"),
266 (r'raise Exception', "don't raise generic exceptions"),
267 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
267 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
268 "don't use old-style two-argument raise, use Exception(message)"),
268 "don't use old-style two-argument raise, use Exception(message)"),
269 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
269 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
270 (r' [=!]=\s+(True|False|None)',
270 (r' [=!]=\s+(True|False|None)',
271 "comparison with singleton, use 'is' or 'is not' instead"),
271 "comparison with singleton, use 'is' or 'is not' instead"),
272 (r'^\s*(while|if) [01]:',
272 (r'^\s*(while|if) [01]:',
273 "use True/False for constant Boolean expression"),
273 "use True/False for constant Boolean expression"),
274 (r'(?:(?<!def)\s+|\()hasattr',
274 (r'(?:(?<!def)\s+|\()hasattr',
275 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
275 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
276 (r'opener\([^)]*\).read\(',
276 (r'opener\([^)]*\).read\(',
277 "use opener.read() instead"),
277 "use opener.read() instead"),
278 (r'BaseException', 'not in Python 2.4, use Exception'),
278 (r'BaseException', 'not in Python 2.4, use Exception'),
279 (r'opener\([^)]*\).write\(',
279 (r'opener\([^)]*\).write\(',
280 "use opener.write() instead"),
280 "use opener.write() instead"),
281 (r'[\s\(](open|file)\([^)]*\)\.read\(',
281 (r'[\s\(](open|file)\([^)]*\)\.read\(',
282 "use util.readfile() instead"),
282 "use util.readfile() instead"),
283 (r'[\s\(](open|file)\([^)]*\)\.write\(',
283 (r'[\s\(](open|file)\([^)]*\)\.write\(',
284 "use util.writefile() instead"),
284 "use util.writefile() instead"),
285 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
285 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
286 "always assign an opened file to a variable, and close it afterwards"),
286 "always assign an opened file to a variable, and close it afterwards"),
287 (r'[\s\(](open|file)\([^)]*\)\.',
287 (r'[\s\(](open|file)\([^)]*\)\.',
288 "always assign an opened file to a variable, and close it afterwards"),
288 "always assign an opened file to a variable, and close it afterwards"),
289 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
289 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
290 (r'\.debug\(\_', "don't mark debug messages for translation"),
290 (r'\.debug\(\_', "don't mark debug messages for translation"),
291 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
291 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
292 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
292 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
293 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
293 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
294 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
294 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
295 "missing _() in ui message (use () to hide false-positives)"),
295 "missing _() in ui message (use () to hide false-positives)"),
296 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
296 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
297 (r'\b__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
297 (r'\b__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
298 (r'os\.path\.join\(.*, *(""|\'\')\)',
298 (r'os\.path\.join\(.*, *(""|\'\')\)',
299 "use pathutil.normasprefix(path) instead of os.path.join(path, '')"),
299 "use pathutil.normasprefix(path) instead of os.path.join(path, '')"),
300 ],
300 ],
301 # warnings
301 # warnings
302 [
302 [
303 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
303 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
304 ]
304 ]
305 ]
305 ]
306
306
307 pyfilters = [
307 pyfilters = [
308 (r"""(?msx)(?P<comment>\#.*?$)|
308 (r"""(?msx)(?P<comment>\#.*?$)|
309 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
309 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
310 (?P<text>(([^\\]|\\.)*?))
310 (?P<text>(([^\\]|\\.)*?))
311 (?P=quote))""", reppython),
311 (?P=quote))""", reppython),
312 ]
312 ]
313
313
314 txtfilters = []
314 txtfilters = []
315
315
316 txtpats = [
316 txtpats = [
317 [
317 [
318 ('\s$', 'trailing whitespace'),
318 ('\s$', 'trailing whitespace'),
319 ('.. note::[ \n][^\n]', 'add two newlines after note::')
319 ('.. note::[ \n][^\n]', 'add two newlines after note::')
320 ],
320 ],
321 []
321 []
322 ]
322 ]
323
323
324 cpats = [
324 cpats = [
325 [
325 [
326 (r'//', "don't use //-style comments"),
326 (r'//', "don't use //-style comments"),
327 (r'^ ', "don't use spaces to indent"),
327 (r'^ ', "don't use spaces to indent"),
328 (r'\S\t', "don't use tabs except for indent"),
328 (r'\S\t', "don't use tabs except for indent"),
329 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
329 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
330 (r'.{81}', "line too long"),
330 (r'.{81}', "line too long"),
331 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
331 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
332 (r'return\(', "return is not a function"),
332 (r'return\(', "return is not a function"),
333 (r' ;', "no space before ;"),
333 (r' ;', "no space before ;"),
334 (r'[^;] \)', "no space before )"),
334 (r'[^;] \)', "no space before )"),
335 (r'[)][{]', "space between ) and {"),
335 (r'[)][{]', "space between ) and {"),
336 (r'\w+\* \w+', "use int *foo, not int* foo"),
336 (r'\w+\* \w+', "use int *foo, not int* foo"),
337 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
337 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
338 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
338 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
339 (r'\w,\w', "missing whitespace after ,"),
339 (r'\w,\w', "missing whitespace after ,"),
340 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
340 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
341 (r'^#\s+\w', "use #foo, not # foo"),
341 (r'^#\s+\w', "use #foo, not # foo"),
342 (r'[^\n]\Z', "no trailing newline"),
342 (r'[^\n]\Z', "no trailing newline"),
343 (r'^\s*#import\b', "use only #include in standard C code"),
343 (r'^\s*#import\b', "use only #include in standard C code"),
344 ],
344 ],
345 # warnings
345 # warnings
346 []
346 []
347 ]
347 ]
348
348
349 cfilters = [
349 cfilters = [
350 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
350 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
351 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
351 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
352 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
352 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
353 (r'(\()([^)]+\))', repcallspaces),
353 (r'(\()([^)]+\))', repcallspaces),
354 ]
354 ]
355
355
356 inutilpats = [
356 inutilpats = [
357 [
357 [
358 (r'\bui\.', "don't use ui in util"),
358 (r'\bui\.', "don't use ui in util"),
359 ],
359 ],
360 # warnings
360 # warnings
361 []
361 []
362 ]
362 ]
363
363
364 inrevlogpats = [
364 inrevlogpats = [
365 [
365 [
366 (r'\brepo\.', "don't use repo in revlog"),
366 (r'\brepo\.', "don't use repo in revlog"),
367 ],
367 ],
368 # warnings
368 # warnings
369 []
369 []
370 ]
370 ]
371
371
372 webtemplatefilters = []
372 webtemplatefilters = []
373
373
374 webtemplatepats = [
374 webtemplatepats = [
375 [],
375 [],
376 [
376 [
377 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
377 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
378 'follow desc keyword with either firstline or websub'),
378 'follow desc keyword with either firstline or websub'),
379 ]
379 ]
380 ]
380 ]
381
381
382 checks = [
382 checks = [
383 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
383 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
384 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
384 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
385 ('c', r'.*\.[ch]$', '', cfilters, cpats),
385 ('c', r'.*\.[ch]$', '', cfilters, cpats),
386 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
386 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
387 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
387 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
388 pyfilters, inrevlogpats),
388 pyfilters, inrevlogpats),
389 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
389 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
390 inutilpats),
390 inutilpats),
391 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
391 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
392 ('web template', r'mercurial/templates/.*\.tmpl', '',
392 ('web template', r'mercurial/templates/.*\.tmpl', '',
393 webtemplatefilters, webtemplatepats),
393 webtemplatefilters, webtemplatepats),
394 ]
394 ]
395
395
396 def _preparepats():
396 def _preparepats():
397 for c in checks:
397 for c in checks:
398 failandwarn = c[-1]
398 failandwarn = c[-1]
399 for pats in failandwarn:
399 for pats in failandwarn:
400 for i, pseq in enumerate(pats):
400 for i, pseq in enumerate(pats):
401 # fix-up regexes for multi-line searches
401 # fix-up regexes for multi-line searches
402 p = pseq[0]
402 p = pseq[0]
403 # \s doesn't match \n
403 # \s doesn't match \n
404 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
404 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
405 # [^...] doesn't match newline
405 # [^...] doesn't match newline
406 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
406 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
407
407
408 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
408 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
409 filters = c[3]
409 filters = c[3]
410 for i, flt in enumerate(filters):
410 for i, flt in enumerate(filters):
411 filters[i] = re.compile(flt[0]), flt[1]
411 filters[i] = re.compile(flt[0]), flt[1]
412 _preparepats()
412 _preparepats()
413
413
414 class norepeatlogger(object):
414 class norepeatlogger(object):
415 def __init__(self):
415 def __init__(self):
416 self._lastseen = None
416 self._lastseen = None
417
417
418 def log(self, fname, lineno, line, msg, blame):
418 def log(self, fname, lineno, line, msg, blame):
419 """print error related a to given line of a given file.
419 """print error related a to given line of a given file.
420
420
421 The faulty line will also be printed but only once in the case
421 The faulty line will also be printed but only once in the case
422 of multiple errors.
422 of multiple errors.
423
423
424 :fname: filename
424 :fname: filename
425 :lineno: line number
425 :lineno: line number
426 :line: actual content of the line
426 :line: actual content of the line
427 :msg: error message
427 :msg: error message
428 """
428 """
429 msgid = fname, lineno, line
429 msgid = fname, lineno, line
430 if msgid != self._lastseen:
430 if msgid != self._lastseen:
431 if blame:
431 if blame:
432 print "%s:%d (%s):" % (fname, lineno, blame)
432 print "%s:%d (%s):" % (fname, lineno, blame)
433 else:
433 else:
434 print "%s:%d:" % (fname, lineno)
434 print "%s:%d:" % (fname, lineno)
435 print " > %s" % line
435 print " > %s" % line
436 self._lastseen = msgid
436 self._lastseen = msgid
437 print " " + msg
437 print " " + msg
438
438
439 _defaultlogger = norepeatlogger()
439 _defaultlogger = norepeatlogger()
440
440
441 def getblame(f):
441 def getblame(f):
442 lines = []
442 lines = []
443 for l in os.popen('hg annotate -un %s' % f):
443 for l in os.popen('hg annotate -un %s' % f):
444 start, line = l.split(':', 1)
444 start, line = l.split(':', 1)
445 user, rev = start.split()
445 user, rev = start.split()
446 lines.append((line[1:-1], user, rev))
446 lines.append((line[1:-1], user, rev))
447 return lines
447 return lines
448
448
449 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
449 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
450 blame=False, debug=False, lineno=True):
450 blame=False, debug=False, lineno=True):
451 """checks style and portability of a given file
451 """checks style and portability of a given file
452
452
453 :f: filepath
453 :f: filepath
454 :logfunc: function used to report error
454 :logfunc: function used to report error
455 logfunc(filename, linenumber, linecontent, errormessage)
455 logfunc(filename, linenumber, linecontent, errormessage)
456 :maxerr: number of error to display before aborting.
456 :maxerr: number of error to display before aborting.
457 Set to false (default) to report all errors
457 Set to false (default) to report all errors
458
458
459 return True if no error is found, False otherwise.
459 return True if no error is found, False otherwise.
460 """
460 """
461 blamecache = None
461 blamecache = None
462 result = True
462 result = True
463
463
464 try:
464 try:
465 fp = open(f)
465 fp = open(f)
466 except IOError, e:
466 except IOError, e:
467 print "Skipping %s, %s" % (f, str(e).split(':', 1)[0])
467 print "Skipping %s, %s" % (f, str(e).split(':', 1)[0])
468 return result
468 return result
469 pre = post = fp.read()
469 pre = post = fp.read()
470 fp.close()
470 fp.close()
471
471
472 for name, match, magic, filters, pats in checks:
472 for name, match, magic, filters, pats in checks:
473 if debug:
473 if debug:
474 print name, f
474 print name, f
475 fc = 0
475 fc = 0
476 if not (re.match(match, f) or (magic and re.search(magic, f))):
476 if not (re.match(match, f) or (magic and re.search(magic, f))):
477 if debug:
477 if debug:
478 print "Skipping %s for %s it doesn't match %s" % (
478 print "Skipping %s for %s it doesn't match %s" % (
479 name, match, f)
479 name, match, f)
480 continue
480 continue
481 if "no-" "check-code" in pre:
481 if "no-" "check-code" in pre:
482 print "Skipping %s it has no-" "check-code" % f
482 print "Skipping %s it has no-" "check-code" % f
483 return "Skip" # skip checking this file
483 return "Skip" # skip checking this file
484 for p, r in filters:
484 for p, r in filters:
485 post = re.sub(p, r, post)
485 post = re.sub(p, r, post)
486 nerrs = len(pats[0]) # nerr elements are errors
486 nerrs = len(pats[0]) # nerr elements are errors
487 if warnings:
487 if warnings:
488 pats = pats[0] + pats[1]
488 pats = pats[0] + pats[1]
489 else:
489 else:
490 pats = pats[0]
490 pats = pats[0]
491 # print post # uncomment to show filtered version
491 # print post # uncomment to show filtered version
492
492
493 if debug:
493 if debug:
494 print "Checking %s for %s" % (name, f)
494 print "Checking %s for %s" % (name, f)
495
495
496 prelines = None
496 prelines = None
497 errors = []
497 errors = []
498 for i, pat in enumerate(pats):
498 for i, pat in enumerate(pats):
499 if len(pat) == 3:
499 if len(pat) == 3:
500 p, msg, ignore = pat
500 p, msg, ignore = pat
501 else:
501 else:
502 p, msg = pat
502 p, msg = pat
503 ignore = None
503 ignore = None
504 if i >= nerrs:
504 if i >= nerrs:
505 msg = "warning: " + msg
505 msg = "warning: " + msg
506
506
507 pos = 0
507 pos = 0
508 n = 0
508 n = 0
509 for m in p.finditer(post):
509 for m in p.finditer(post):
510 if prelines is None:
510 if prelines is None:
511 prelines = pre.splitlines()
511 prelines = pre.splitlines()
512 postlines = post.splitlines(True)
512 postlines = post.splitlines(True)
513
513
514 start = m.start()
514 start = m.start()
515 while n < len(postlines):
515 while n < len(postlines):
516 step = len(postlines[n])
516 step = len(postlines[n])
517 if pos + step > start:
517 if pos + step > start:
518 break
518 break
519 pos += step
519 pos += step
520 n += 1
520 n += 1
521 l = prelines[n]
521 l = prelines[n]
522
522
523 if ignore and re.search(ignore, l, re.MULTILINE):
523 if ignore and re.search(ignore, l, re.MULTILINE):
524 if debug:
524 if debug:
525 print "Skipping %s for %s:%s (ignore pattern)" % (
525 print "Skipping %s for %s:%s (ignore pattern)" % (
526 name, f, n)
526 name, f, n)
527 continue
527 continue
528 bd = ""
528 bd = ""
529 if blame:
529 if blame:
530 bd = 'working directory'
530 bd = 'working directory'
531 if not blamecache:
531 if not blamecache:
532 blamecache = getblame(f)
532 blamecache = getblame(f)
533 if n < len(blamecache):
533 if n < len(blamecache):
534 bl, bu, br = blamecache[n]
534 bl, bu, br = blamecache[n]
535 if bl == l:
535 if bl == l:
536 bd = '%s@%s' % (bu, br)
536 bd = '%s@%s' % (bu, br)
537
537
538 errors.append((f, lineno and n + 1, l, msg, bd))
538 errors.append((f, lineno and n + 1, l, msg, bd))
539 result = False
539 result = False
540
540
541 errors.sort()
541 errors.sort()
542 for e in errors:
542 for e in errors:
543 logfunc(*e)
543 logfunc(*e)
544 fc += 1
544 fc += 1
545 if maxerr and fc >= maxerr:
545 if maxerr and fc >= maxerr:
546 print " (too many errors, giving up)"
546 print " (too many errors, giving up)"
547 break
547 break
548
548
549 return result
549 return result
550
550
551 if __name__ == "__main__":
551 if __name__ == "__main__":
552 parser = optparse.OptionParser("%prog [options] [files]")
552 parser = optparse.OptionParser("%prog [options] [files]")
553 parser.add_option("-w", "--warnings", action="store_true",
553 parser.add_option("-w", "--warnings", action="store_true",
554 help="include warning-level checks")
554 help="include warning-level checks")
555 parser.add_option("-p", "--per-file", type="int",
555 parser.add_option("-p", "--per-file", type="int",
556 help="max warnings per file")
556 help="max warnings per file")
557 parser.add_option("-b", "--blame", action="store_true",
557 parser.add_option("-b", "--blame", action="store_true",
558 help="use annotate to generate blame info")
558 help="use annotate to generate blame info")
559 parser.add_option("", "--debug", action="store_true",
559 parser.add_option("", "--debug", action="store_true",
560 help="show debug information")
560 help="show debug information")
561 parser.add_option("", "--nolineno", action="store_false",
561 parser.add_option("", "--nolineno", action="store_false",
562 dest='lineno', help="don't show line numbers")
562 dest='lineno', help="don't show line numbers")
563
563
564 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
564 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
565 lineno=True)
565 lineno=True)
566 (options, args) = parser.parse_args()
566 (options, args) = parser.parse_args()
567
567
568 if len(args) == 0:
568 if len(args) == 0:
569 check = glob.glob("*")
569 check = glob.glob("*")
570 else:
570 else:
571 check = args
571 check = args
572
572
573 ret = 0
573 ret = 0
574 for f in check:
574 for f in check:
575 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
575 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
576 blame=options.blame, debug=options.debug,
576 blame=options.blame, debug=options.debug,
577 lineno=options.lineno):
577 lineno=options.lineno):
578 ret = 1
578 ret = 1
579 sys.exit(ret)
579 sys.exit(ret)
@@ -1,299 +1,299 b''
1 $ cat > correct.py <<EOF
1 $ cat > correct.py <<EOF
2 > def toto(arg1, arg2):
2 > def toto(arg1, arg2):
3 > del arg2
3 > del arg2
4 > return (5 + 6, 9)
4 > return (5 + 6, 9)
5 > EOF
5 > EOF
6 $ cat > wrong.py <<EOF
6 $ cat > wrong.py <<EOF
7 > def toto( arg1, arg2):
7 > def toto( arg1, arg2):
8 > del(arg2)
8 > del(arg2)
9 > return ( 5+6, 9)
9 > return ( 5+6, 9)
10 > EOF
10 > EOF
11 $ cat > quote.py <<EOF
11 $ cat > quote.py <<EOF
12 > # let's use quote in comments
12 > # let's use quote in comments
13 > (''' ( 4x5 )
13 > (''' ( 4x5 )
14 > but """\\''' and finally''',
14 > but """\\''' and finally''',
15 > """let's fool checkpatch""", '1+2',
15 > """let's fool checkpatch""", '1+2',
16 > '"""', 42+1, """and
16 > '"""', 42+1, """and
17 > ( 4-1 ) """, "( 1+1 )\" and ")
17 > ( 4-1 ) """, "( 1+1 )\" and ")
18 > a, '\\\\\\\\', "\\\\\\" x-2", "c-1"
18 > a, '\\\\\\\\', "\\\\\\" x-2", "c-1"
19 > EOF
19 > EOF
20 $ cat > non-py24.py <<EOF
20 $ cat > non-py24.py <<EOF
21 > # Using builtins that does not exist in Python 2.4
21 > # Using builtins that does not exist in Python 2.4
22 > if any():
22 > if any():
23 > x = all()
23 > x = all()
24 > y = format(x)
24 > y = format(x)
25 > # next(generator) is new in 2.6
25 > # next(generator) is new in 2.6
26 > z = next(x)
26 > z = next(x)
27 > # but generator.next() is okay
27 > # but generator.next() is okay
28 > x.next()
28 > x.next()
29 > # and we can make our own next
29 > # and we can make our own next
30 > def next(stuff):
30 > def next(stuff):
31 > pass
31 > pass
32 >
32 >
33 > # Do not complain about our own definition
33 > # Do not complain about our own definition
34 > def any(x):
34 > def any(x):
35 > pass
35 > pass
36 >
36 >
37 > # try/except/finally block does not exist in Python 2.4
37 > # try/except/finally block does not exist in Python 2.4
38 > try:
38 > try:
39 > pass
39 > pass
40 > except StandardError, inst:
40 > except StandardError, inst:
41 > pass
41 > pass
42 > finally:
42 > finally:
43 > pass
43 > pass
44 >
44 >
45 > # nested try/finally+try/except is allowed
45 > # nested try/finally+try/except is allowed
46 > try:
46 > try:
47 > try:
47 > try:
48 > pass
48 > pass
49 > except StandardError, inst:
49 > except StandardError, inst:
50 > pass
50 > pass
51 > finally:
51 > finally:
52 > pass
52 > pass
53 >
53 >
54 > # yield inside a try/finally block is not allowed in Python 2.4
54 > # yield inside a try/finally block is not allowed in Python 2.4
55 > try:
55 > try:
56 > pass
56 > pass
57 > yield 1
57 > yield 1
58 > finally:
58 > finally:
59 > pass
59 > pass
60 > try:
60 > try:
61 > yield
61 > yield
62 > pass
62 > pass
63 > finally:
63 > finally:
64 > pass
64 > pass
65 >
65 >
66 > EOF
66 > EOF
67 $ cat > classstyle.py <<EOF
67 $ cat > classstyle.py <<EOF
68 > class newstyle_class(object):
68 > class newstyle_class(object):
69 > pass
69 > pass
70 >
70 >
71 > class oldstyle_class:
71 > class oldstyle_class:
72 > pass
72 > pass
73 >
73 >
74 > class empty():
74 > class empty():
75 > pass
75 > pass
76 >
76 >
77 > no_class = 1:
77 > no_class = 1:
78 > pass
78 > pass
79 > EOF
79 > EOF
80 $ check_code="$TESTDIR"/../contrib/check-code.py
80 $ check_code="$TESTDIR"/../contrib/check-code.py
81 $ "$check_code" ./wrong.py ./correct.py ./quote.py ./non-py24.py ./classstyle.py
81 $ "$check_code" ./wrong.py ./correct.py ./quote.py ./non-py24.py ./classstyle.py
82 ./wrong.py:1:
82 ./wrong.py:1:
83 > def toto( arg1, arg2):
83 > def toto( arg1, arg2):
84 gratuitous whitespace in () or []
84 gratuitous whitespace in () or []
85 ./wrong.py:2:
85 ./wrong.py:2:
86 > del(arg2)
86 > del(arg2)
87 Python keyword is not a function
87 Python keyword is not a function
88 ./wrong.py:3:
88 ./wrong.py:3:
89 > return ( 5+6, 9)
89 > return ( 5+6, 9)
90 gratuitous whitespace in () or []
90 gratuitous whitespace in () or []
91 missing whitespace in expression
91 missing whitespace in expression
92 ./quote.py:5:
92 ./quote.py:5:
93 > '"""', 42+1, """and
93 > '"""', 42+1, """and
94 missing whitespace in expression
94 missing whitespace in expression
95 ./non-py24.py:2:
95 ./non-py24.py:2:
96 > if any():
96 > if any():
97 any/all/format not available in Python 2.4
97 any/all/format not available in Python 2.4
98 ./non-py24.py:3:
98 ./non-py24.py:3:
99 > x = all()
99 > x = all()
100 any/all/format not available in Python 2.4
100 any/all/format not available in Python 2.4
101 ./non-py24.py:4:
101 ./non-py24.py:4:
102 > y = format(x)
102 > y = format(x)
103 any/all/format not available in Python 2.4
103 any/all/format not available in Python 2.4
104 ./non-py24.py:6:
104 ./non-py24.py:6:
105 > z = next(x)
105 > z = next(x)
106 no next(foo) in Python 2.4 and 2.5, use foo.next() instead
106 no next(foo) in Python 2.4 and 2.5, use foo.next() instead
107 ./non-py24.py:35:
107 ./non-py24.py:35:
108 > try:
108 > try:
109 no yield inside try/finally in Python 2.4
109 no yield inside try/finally in Python 2.4
110 ./non-py24.py:40:
110 ./non-py24.py:40:
111 > try:
111 > try:
112 no yield inside try/finally in Python 2.4
112 no yield inside try/finally in Python 2.4
113 ./classstyle.py:4:
113 ./classstyle.py:4:
114 > class oldstyle_class:
114 > class oldstyle_class:
115 old-style class, use class foo(object)
115 old-style class, use class foo(object)
116 ./classstyle.py:7:
116 ./classstyle.py:7:
117 > class empty():
117 > class empty():
118 class foo() not available in Python 2.4, use class foo(object)
118 class foo() creates old style object, use class foo(object)
119 [1]
119 [1]
120 $ cat > python3-compat.py << EOF
120 $ cat > python3-compat.py << EOF
121 > foo <> bar
121 > foo <> bar
122 > reduce(lambda a, b: a + b, [1, 2, 3, 4])
122 > reduce(lambda a, b: a + b, [1, 2, 3, 4])
123 > dict(key=value)
123 > dict(key=value)
124 > EOF
124 > EOF
125 $ "$check_code" python3-compat.py
125 $ "$check_code" python3-compat.py
126 python3-compat.py:1:
126 python3-compat.py:1:
127 > foo <> bar
127 > foo <> bar
128 <> operator is not available in Python 3+, use !=
128 <> operator is not available in Python 3+, use !=
129 python3-compat.py:2:
129 python3-compat.py:2:
130 > reduce(lambda a, b: a + b, [1, 2, 3, 4])
130 > reduce(lambda a, b: a + b, [1, 2, 3, 4])
131 reduce is not available in Python 3+
131 reduce is not available in Python 3+
132 python3-compat.py:3:
132 python3-compat.py:3:
133 > dict(key=value)
133 > dict(key=value)
134 dict() is different in Py2 and 3 and is slower than {}
134 dict() is different in Py2 and 3 and is slower than {}
135 [1]
135 [1]
136
136
137 $ cat > is-op.py <<EOF
137 $ cat > is-op.py <<EOF
138 > # is-operator comparing number or string literal
138 > # is-operator comparing number or string literal
139 > x = None
139 > x = None
140 > y = x is 'foo'
140 > y = x is 'foo'
141 > y = x is "foo"
141 > y = x is "foo"
142 > y = x is 5346
142 > y = x is 5346
143 > y = x is -6
143 > y = x is -6
144 > y = x is not 'foo'
144 > y = x is not 'foo'
145 > y = x is not "foo"
145 > y = x is not "foo"
146 > y = x is not 5346
146 > y = x is not 5346
147 > y = x is not -6
147 > y = x is not -6
148 > EOF
148 > EOF
149
149
150 $ "$check_code" ./is-op.py
150 $ "$check_code" ./is-op.py
151 ./is-op.py:3:
151 ./is-op.py:3:
152 > y = x is 'foo'
152 > y = x is 'foo'
153 object comparison with literal
153 object comparison with literal
154 ./is-op.py:4:
154 ./is-op.py:4:
155 > y = x is "foo"
155 > y = x is "foo"
156 object comparison with literal
156 object comparison with literal
157 ./is-op.py:5:
157 ./is-op.py:5:
158 > y = x is 5346
158 > y = x is 5346
159 object comparison with literal
159 object comparison with literal
160 ./is-op.py:6:
160 ./is-op.py:6:
161 > y = x is -6
161 > y = x is -6
162 object comparison with literal
162 object comparison with literal
163 ./is-op.py:7:
163 ./is-op.py:7:
164 > y = x is not 'foo'
164 > y = x is not 'foo'
165 object comparison with literal
165 object comparison with literal
166 ./is-op.py:8:
166 ./is-op.py:8:
167 > y = x is not "foo"
167 > y = x is not "foo"
168 object comparison with literal
168 object comparison with literal
169 ./is-op.py:9:
169 ./is-op.py:9:
170 > y = x is not 5346
170 > y = x is not 5346
171 object comparison with literal
171 object comparison with literal
172 ./is-op.py:10:
172 ./is-op.py:10:
173 > y = x is not -6
173 > y = x is not -6
174 object comparison with literal
174 object comparison with literal
175 [1]
175 [1]
176
176
177 $ cat > for-nolineno.py <<EOF
177 $ cat > for-nolineno.py <<EOF
178 > except:
178 > except:
179 > EOF
179 > EOF
180 $ "$check_code" for-nolineno.py --nolineno
180 $ "$check_code" for-nolineno.py --nolineno
181 for-nolineno.py:0:
181 for-nolineno.py:0:
182 > except:
182 > except:
183 naked except clause
183 naked except clause
184 [1]
184 [1]
185
185
186 $ cat > warning.t <<EOF
186 $ cat > warning.t <<EOF
187 > $ function warnonly {
187 > $ function warnonly {
188 > > }
188 > > }
189 > $ diff -N aaa
189 > $ diff -N aaa
190 > $ function onwarn {}
190 > $ function onwarn {}
191 > EOF
191 > EOF
192 $ "$check_code" warning.t
192 $ "$check_code" warning.t
193 $ "$check_code" --warn warning.t
193 $ "$check_code" --warn warning.t
194 warning.t:1:
194 warning.t:1:
195 > $ function warnonly {
195 > $ function warnonly {
196 warning: don't use 'function', use old style
196 warning: don't use 'function', use old style
197 warning.t:3:
197 warning.t:3:
198 > $ diff -N aaa
198 > $ diff -N aaa
199 warning: don't use 'diff -N'
199 warning: don't use 'diff -N'
200 warning.t:4:
200 warning.t:4:
201 > $ function onwarn {}
201 > $ function onwarn {}
202 warning: don't use 'function', use old style
202 warning: don't use 'function', use old style
203 [1]
203 [1]
204 $ cat > raise-format.py <<EOF
204 $ cat > raise-format.py <<EOF
205 > raise SomeException, message
205 > raise SomeException, message
206 > # this next line is okay
206 > # this next line is okay
207 > raise SomeException(arg1, arg2)
207 > raise SomeException(arg1, arg2)
208 > EOF
208 > EOF
209 $ "$check_code" not-existing.py raise-format.py
209 $ "$check_code" not-existing.py raise-format.py
210 Skipping*not-existing.py* (glob)
210 Skipping*not-existing.py* (glob)
211 raise-format.py:1:
211 raise-format.py:1:
212 > raise SomeException, message
212 > raise SomeException, message
213 don't use old-style two-argument raise, use Exception(message)
213 don't use old-style two-argument raise, use Exception(message)
214 [1]
214 [1]
215
215
216 $ cat > rst.py <<EOF
216 $ cat > rst.py <<EOF
217 > """problematic rst text
217 > """problematic rst text
218 >
218 >
219 > .. note::
219 > .. note::
220 > wrong
220 > wrong
221 > """
221 > """
222 >
222 >
223 > '''
223 > '''
224 >
224 >
225 > .. note::
225 > .. note::
226 >
226 >
227 > valid
227 > valid
228 >
228 >
229 > new text
229 > new text
230 >
230 >
231 > .. note::
231 > .. note::
232 >
232 >
233 > also valid
233 > also valid
234 > '''
234 > '''
235 >
235 >
236 > """mixed
236 > """mixed
237 >
237 >
238 > .. note::
238 > .. note::
239 >
239 >
240 > good
240 > good
241 >
241 >
242 > .. note::
242 > .. note::
243 > plus bad
243 > plus bad
244 > """
244 > """
245 > EOF
245 > EOF
246 $ $check_code -w rst.py
246 $ $check_code -w rst.py
247 rst.py:3:
247 rst.py:3:
248 > .. note::
248 > .. note::
249 warning: add two newlines after '.. note::'
249 warning: add two newlines after '.. note::'
250 rst.py:26:
250 rst.py:26:
251 > .. note::
251 > .. note::
252 warning: add two newlines after '.. note::'
252 warning: add two newlines after '.. note::'
253 [1]
253 [1]
254
254
255 $ cat > ./map-inside-gettext.py <<EOF
255 $ cat > ./map-inside-gettext.py <<EOF
256 > print _("map inside gettext %s" % v)
256 > print _("map inside gettext %s" % v)
257 >
257 >
258 > print _("concatenating " " by " " space %s" % v)
258 > print _("concatenating " " by " " space %s" % v)
259 > print _("concatenating " + " by " + " '+' %s" % v)
259 > print _("concatenating " + " by " + " '+' %s" % v)
260 >
260 >
261 > print _("mapping operation in different line %s"
261 > print _("mapping operation in different line %s"
262 > % v)
262 > % v)
263 >
263 >
264 > print _(
264 > print _(
265 > "leading spaces inside of '(' %s" % v)
265 > "leading spaces inside of '(' %s" % v)
266 > EOF
266 > EOF
267 $ "$check_code" ./map-inside-gettext.py
267 $ "$check_code" ./map-inside-gettext.py
268 ./map-inside-gettext.py:1:
268 ./map-inside-gettext.py:1:
269 > print _("map inside gettext %s" % v)
269 > print _("map inside gettext %s" % v)
270 don't use % inside _()
270 don't use % inside _()
271 ./map-inside-gettext.py:3:
271 ./map-inside-gettext.py:3:
272 > print _("concatenating " " by " " space %s" % v)
272 > print _("concatenating " " by " " space %s" % v)
273 don't use % inside _()
273 don't use % inside _()
274 ./map-inside-gettext.py:4:
274 ./map-inside-gettext.py:4:
275 > print _("concatenating " + " by " + " '+' %s" % v)
275 > print _("concatenating " + " by " + " '+' %s" % v)
276 don't use % inside _()
276 don't use % inside _()
277 ./map-inside-gettext.py:6:
277 ./map-inside-gettext.py:6:
278 > print _("mapping operation in different line %s"
278 > print _("mapping operation in different line %s"
279 don't use % inside _()
279 don't use % inside _()
280 ./map-inside-gettext.py:9:
280 ./map-inside-gettext.py:9:
281 > print _(
281 > print _(
282 don't use % inside _()
282 don't use % inside _()
283 [1]
283 [1]
284
284
285 web templates
285 web templates
286
286
287 $ mkdir -p mercurial/templates
287 $ mkdir -p mercurial/templates
288 $ cat > mercurial/templates/example.tmpl <<EOF
288 $ cat > mercurial/templates/example.tmpl <<EOF
289 > {desc}
289 > {desc}
290 > {desc|escape}
290 > {desc|escape}
291 > {desc|firstline}
291 > {desc|firstline}
292 > {desc|websub}
292 > {desc|websub}
293 > EOF
293 > EOF
294
294
295 $ "$check_code" --warnings mercurial/templates/example.tmpl
295 $ "$check_code" --warnings mercurial/templates/example.tmpl
296 mercurial/templates/example.tmpl:2:
296 mercurial/templates/example.tmpl:2:
297 > {desc|escape}
297 > {desc|escape}
298 warning: follow desc keyword with either firstline or websub
298 warning: follow desc keyword with either firstline or websub
299 [1]
299 [1]
General Comments 0
You need to be logged in to leave comments. Login now