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