##// END OF EJS Templates
check-code: make casting style check more precise
Matt Mackall -
r19731:436a3f72 default
parent child Browse files
Show More
@@ -1,522 +1,522 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 import re, glob, os, sys
10 import re, glob, os, sys
11 import keyword
11 import keyword
12 import optparse
12 import optparse
13 try:
13 try:
14 import re2
14 import re2
15 except ImportError:
15 except ImportError:
16 re2 = None
16 re2 = None
17
17
18 def compilere(pat, multiline=False):
18 def compilere(pat, multiline=False):
19 if multiline:
19 if multiline:
20 pat = '(?m)' + pat
20 pat = '(?m)' + pat
21 if re2:
21 if re2:
22 try:
22 try:
23 return re2.compile(pat)
23 return re2.compile(pat)
24 except re2.error:
24 except re2.error:
25 pass
25 pass
26 return re.compile(pat)
26 return re.compile(pat)
27
27
28 def repquote(m):
28 def repquote(m):
29 t = re.sub(r"\w", "x", m.group('text'))
29 t = re.sub(r"\w", "x", m.group('text'))
30 t = re.sub(r"[^\s\nx]", "o", t)
30 t = re.sub(r"[^\s\nx]", "o", t)
31 return m.group('quote') + t + m.group('quote')
31 return m.group('quote') + t + m.group('quote')
32
32
33 def reppython(m):
33 def reppython(m):
34 comment = m.group('comment')
34 comment = m.group('comment')
35 if comment:
35 if comment:
36 l = len(comment.rstrip())
36 l = len(comment.rstrip())
37 return "#" * l + comment[l:]
37 return "#" * l + comment[l:]
38 return repquote(m)
38 return repquote(m)
39
39
40 def repcomment(m):
40 def repcomment(m):
41 return m.group(1) + "#" * len(m.group(2))
41 return m.group(1) + "#" * len(m.group(2))
42
42
43 def repccomment(m):
43 def repccomment(m):
44 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
44 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
45 return m.group(1) + t + "*/"
45 return m.group(1) + t + "*/"
46
46
47 def repcallspaces(m):
47 def repcallspaces(m):
48 t = re.sub(r"\n\s+", "\n", m.group(2))
48 t = re.sub(r"\n\s+", "\n", m.group(2))
49 return m.group(1) + t
49 return m.group(1) + t
50
50
51 def repinclude(m):
51 def repinclude(m):
52 return m.group(1) + "<foo>"
52 return m.group(1) + "<foo>"
53
53
54 def rephere(m):
54 def rephere(m):
55 t = re.sub(r"\S", "x", m.group(2))
55 t = re.sub(r"\S", "x", m.group(2))
56 return m.group(1) + t
56 return m.group(1) + t
57
57
58
58
59 testpats = [
59 testpats = [
60 [
60 [
61 (r'pushd|popd', "don't use 'pushd' or 'popd', use 'cd'"),
61 (r'pushd|popd', "don't use 'pushd' or 'popd', use 'cd'"),
62 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
62 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
63 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
63 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
64 (r'(?<!hg )grep.*-a', "don't use 'grep -a', use in-line python"),
64 (r'(?<!hg )grep.*-a', "don't use 'grep -a', use in-line python"),
65 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
65 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
66 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
66 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
67 (r'echo -n', "don't use 'echo -n', use printf"),
67 (r'echo -n', "don't use 'echo -n', use printf"),
68 (r'(^| )wc[^|]*$\n(?!.*\(re\))', "filter wc output"),
68 (r'(^| )wc[^|]*$\n(?!.*\(re\))', "filter wc output"),
69 (r'head -c', "don't use 'head -c', use 'dd'"),
69 (r'head -c', "don't use 'head -c', use 'dd'"),
70 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
70 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
71 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
71 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
72 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
72 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
73 (r'printf.*[^\\]\\([1-9]|0\d)', "don't use 'printf \NNN', use Python"),
73 (r'printf.*[^\\]\\([1-9]|0\d)', "don't use 'printf \NNN', use Python"),
74 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
74 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
75 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
75 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
76 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
76 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
77 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
77 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
78 "use egrep for extended grep syntax"),
78 "use egrep for extended grep syntax"),
79 (r'/bin/', "don't use explicit paths for tools"),
79 (r'/bin/', "don't use explicit paths for tools"),
80 (r'[^\n]\Z', "no trailing newline"),
80 (r'[^\n]\Z', "no trailing newline"),
81 (r'export.*=', "don't export and assign at once"),
81 (r'export.*=', "don't export and assign at once"),
82 (r'^source\b', "don't use 'source', use '.'"),
82 (r'^source\b', "don't use 'source', use '.'"),
83 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
83 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
84 (r'ls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
84 (r'ls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
85 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
85 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
86 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
86 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
87 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
87 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
88 (r'^alias\b.*=', "don't use alias, use a function"),
88 (r'^alias\b.*=', "don't use alias, use a function"),
89 (r'if\s*!', "don't use '!' to negate exit status"),
89 (r'if\s*!', "don't use '!' to negate exit status"),
90 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
90 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
91 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
91 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
92 (r'^( *)\t', "don't use tabs to indent"),
92 (r'^( *)\t', "don't use tabs to indent"),
93 (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
93 (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
94 "put a backslash-escaped newline after sed 'i' command"),
94 "put a backslash-escaped newline after sed 'i' command"),
95 ],
95 ],
96 # warnings
96 # warnings
97 [
97 [
98 (r'^function', "don't use 'function', use old style"),
98 (r'^function', "don't use 'function', use old style"),
99 (r'^diff.*-\w*N', "don't use 'diff -N'"),
99 (r'^diff.*-\w*N', "don't use 'diff -N'"),
100 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
100 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
101 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
101 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
102 (r'kill (`|\$\()', "don't use kill, use killdaemons.py")
102 (r'kill (`|\$\()', "don't use kill, use killdaemons.py")
103 ]
103 ]
104 ]
104 ]
105
105
106 testfilters = [
106 testfilters = [
107 (r"( *)(#([^\n]*\S)?)", repcomment),
107 (r"( *)(#([^\n]*\S)?)", repcomment),
108 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
108 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
109 ]
109 ]
110
110
111 winglobmsg = "use (glob) to match Windows paths too"
111 winglobmsg = "use (glob) to match Windows paths too"
112 uprefix = r"^ \$ "
112 uprefix = r"^ \$ "
113 utestpats = [
113 utestpats = [
114 [
114 [
115 (r'^(\S.*|| [$>] .*)[ \t]\n', "trailing whitespace on non-output"),
115 (r'^(\S.*|| [$>] .*)[ \t]\n', "trailing whitespace on non-output"),
116 (uprefix + r'.*\|\s*sed[^|>\n]*\n',
116 (uprefix + r'.*\|\s*sed[^|>\n]*\n',
117 "use regex test output patterns instead of sed"),
117 "use regex test output patterns instead of sed"),
118 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
118 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
119 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
119 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
120 (uprefix + r'.*\|\| echo.*(fail|error)',
120 (uprefix + r'.*\|\| echo.*(fail|error)',
121 "explicit exit code checks unnecessary"),
121 "explicit exit code checks unnecessary"),
122 (uprefix + r'set -e', "don't use set -e"),
122 (uprefix + r'set -e', "don't use set -e"),
123 (uprefix + r'\s', "don't indent commands, use > for continued lines"),
123 (uprefix + r'\s', "don't indent commands, use > for continued lines"),
124 (r'^ saved backup bundle to \$TESTTMP.*\.hg$', winglobmsg),
124 (r'^ saved backup bundle to \$TESTTMP.*\.hg$', winglobmsg),
125 (r'^ changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
125 (r'^ changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
126 winglobmsg),
126 winglobmsg),
127 (r'^ pulling from \$TESTTMP/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
127 (r'^ pulling from \$TESTTMP/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
128 (r'^ reverting .*/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
128 (r'^ reverting .*/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
129 (r'^ cloning subrepo \S+/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
129 (r'^ cloning subrepo \S+/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
130 (r'^ pushing to \$TESTTMP/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
130 (r'^ pushing to \$TESTTMP/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
131 (r'^ pushing subrepo \S+/\S+ to.*[^)]$', winglobmsg,
131 (r'^ pushing subrepo \S+/\S+ to.*[^)]$', winglobmsg,
132 '\$TESTTMP/unix-repo$'),
132 '\$TESTTMP/unix-repo$'),
133 (r'^ moving \S+/.*[^)]$', winglobmsg),
133 (r'^ moving \S+/.*[^)]$', winglobmsg),
134 (r'^ no changes made to subrepo since.*/.*[^)]$',
134 (r'^ no changes made to subrepo since.*/.*[^)]$',
135 winglobmsg, '\$TESTTMP/unix-repo$'),
135 winglobmsg, '\$TESTTMP/unix-repo$'),
136 (r'^ .*: largefile \S+ not available from file:.*/.*[^)]$',
136 (r'^ .*: largefile \S+ not available from file:.*/.*[^)]$',
137 winglobmsg, '\$TESTTMP/unix-repo$'),
137 winglobmsg, '\$TESTTMP/unix-repo$'),
138 ],
138 ],
139 # warnings
139 # warnings
140 [
140 [
141 (r'^ [^*?/\n]* \(glob\)$',
141 (r'^ [^*?/\n]* \(glob\)$',
142 "glob match with no glob character (?*/)"),
142 "glob match with no glob character (?*/)"),
143 ]
143 ]
144 ]
144 ]
145
145
146 for i in [0, 1]:
146 for i in [0, 1]:
147 for p, m in testpats[i]:
147 for p, m in testpats[i]:
148 if p.startswith(r'^'):
148 if p.startswith(r'^'):
149 p = r"^ [$>] (%s)" % p[1:]
149 p = r"^ [$>] (%s)" % p[1:]
150 else:
150 else:
151 p = r"^ [$>] .*(%s)" % p
151 p = r"^ [$>] .*(%s)" % p
152 utestpats[i].append((p, m))
152 utestpats[i].append((p, m))
153
153
154 utestfilters = [
154 utestfilters = [
155 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
155 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
156 (r"( *)(#([^\n]*\S)?)", repcomment),
156 (r"( *)(#([^\n]*\S)?)", repcomment),
157 ]
157 ]
158
158
159 pypats = [
159 pypats = [
160 [
160 [
161 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
161 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
162 "tuple parameter unpacking not available in Python 3+"),
162 "tuple parameter unpacking not available in Python 3+"),
163 (r'lambda\s*\(.*,.*\)',
163 (r'lambda\s*\(.*,.*\)',
164 "tuple parameter unpacking not available in Python 3+"),
164 "tuple parameter unpacking not available in Python 3+"),
165 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
165 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
166 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
166 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
167 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
167 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
168 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
168 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
169 (r'^\s*\t', "don't use tabs"),
169 (r'^\s*\t', "don't use tabs"),
170 (r'\S;\s*\n', "semicolon"),
170 (r'\S;\s*\n', "semicolon"),
171 (r'[^_]_\("[^"]+"\s*%', "don't use % inside _()"),
171 (r'[^_]_\("[^"]+"\s*%', "don't use % inside _()"),
172 (r"[^_]_\('[^']+'\s*%", "don't use % inside _()"),
172 (r"[^_]_\('[^']+'\s*%", "don't use % inside _()"),
173 (r'(\w|\)),\w', "missing whitespace after ,"),
173 (r'(\w|\)),\w', "missing whitespace after ,"),
174 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
174 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
175 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
175 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
176 (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)\1except.*?:\n'
176 (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)\1except.*?:\n'
177 r'((?:\n|\1\s.*\n)+?)\1finally:', 'no try/except/finally in Python 2.4'),
177 r'((?:\n|\1\s.*\n)+?)\1finally:', 'no try/except/finally in Python 2.4'),
178 (r'(?<!def)(\s+|^|\()next\(.+\)',
178 (r'(?<!def)(\s+|^|\()next\(.+\)',
179 'no next(foo) in Python 2.4 and 2.5, use foo.next() instead'),
179 'no next(foo) in Python 2.4 and 2.5, use foo.next() instead'),
180 (r'(\s+)try:\n((?:\n|\1\s.*\n)*?)\1\s*yield\b.*?'
180 (r'(\s+)try:\n((?:\n|\1\s.*\n)*?)\1\s*yield\b.*?'
181 r'((?:\n|\1\s.*\n)+?)\1finally:',
181 r'((?:\n|\1\s.*\n)+?)\1finally:',
182 'no yield inside try/finally in Python 2.4'),
182 'no yield inside try/finally in Python 2.4'),
183 (r'.{81}', "line too long"),
183 (r'.{81}', "line too long"),
184 (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
184 (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
185 (r'[^\n]\Z', "no trailing newline"),
185 (r'[^\n]\Z', "no trailing newline"),
186 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
186 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
187 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
187 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
188 # "don't use underbars in identifiers"),
188 # "don't use underbars in identifiers"),
189 (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
189 (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
190 "don't use camelcase in identifiers"),
190 "don't use camelcase in identifiers"),
191 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
191 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
192 "linebreak after :"),
192 "linebreak after :"),
193 (r'class\s[^( \n]+:', "old-style class, use class foo(object)"),
193 (r'class\s[^( \n]+:', "old-style class, use class foo(object)"),
194 (r'class\s[^( \n]+\(\):',
194 (r'class\s[^( \n]+\(\):',
195 "class foo() not available in Python 2.4, use class foo(object)"),
195 "class foo() not available in Python 2.4, use class foo(object)"),
196 (r'\b(%s)\(' % '|'.join(keyword.kwlist),
196 (r'\b(%s)\(' % '|'.join(keyword.kwlist),
197 "Python keyword is not a function"),
197 "Python keyword is not a function"),
198 (r',]', "unneeded trailing ',' in list"),
198 (r',]', "unneeded trailing ',' in list"),
199 # (r'class\s[A-Z][^\(]*\((?!Exception)',
199 # (r'class\s[A-Z][^\(]*\((?!Exception)',
200 # "don't capitalize non-exception classes"),
200 # "don't capitalize non-exception classes"),
201 # (r'in range\(', "use xrange"),
201 # (r'in range\(', "use xrange"),
202 # (r'^\s*print\s+', "avoid using print in core and extensions"),
202 # (r'^\s*print\s+', "avoid using print in core and extensions"),
203 (r'[\x80-\xff]', "non-ASCII character literal"),
203 (r'[\x80-\xff]', "non-ASCII character literal"),
204 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
204 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
205 (r'^\s*with\s+', "with not available in Python 2.4"),
205 (r'^\s*with\s+', "with not available in Python 2.4"),
206 (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"),
206 (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"),
207 (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
207 (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
208 (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
208 (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
209 (r'(?<!def)\s+(any|all|format)\(',
209 (r'(?<!def)\s+(any|all|format)\(',
210 "any/all/format not available in Python 2.4"),
210 "any/all/format not available in Python 2.4"),
211 (r'(?<!def)\s+(callable)\(',
211 (r'(?<!def)\s+(callable)\(',
212 "callable not available in Python 3, use getattr(f, '__call__', None)"),
212 "callable not available in Python 3, use getattr(f, '__call__', None)"),
213 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
213 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
214 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
214 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
215 "gratuitous whitespace after Python keyword"),
215 "gratuitous whitespace after Python keyword"),
216 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
216 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
217 # (r'\s\s=', "gratuitous whitespace before ="),
217 # (r'\s\s=', "gratuitous whitespace before ="),
218 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
218 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
219 "missing whitespace around operator"),
219 "missing whitespace around operator"),
220 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
220 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
221 "missing whitespace around operator"),
221 "missing whitespace around operator"),
222 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
222 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
223 "missing whitespace around operator"),
223 "missing whitespace around operator"),
224 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
224 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
225 "wrong whitespace around ="),
225 "wrong whitespace around ="),
226 (r'raise Exception', "don't raise generic exceptions"),
226 (r'raise Exception', "don't raise generic exceptions"),
227 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
227 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
228 "don't use old-style two-argument raise, use Exception(message)"),
228 "don't use old-style two-argument raise, use Exception(message)"),
229 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
229 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
230 (r' [=!]=\s+(True|False|None)',
230 (r' [=!]=\s+(True|False|None)',
231 "comparison with singleton, use 'is' or 'is not' instead"),
231 "comparison with singleton, use 'is' or 'is not' instead"),
232 (r'^\s*(while|if) [01]:',
232 (r'^\s*(while|if) [01]:',
233 "use True/False for constant Boolean expression"),
233 "use True/False for constant Boolean expression"),
234 (r'(?:(?<!def)\s+|\()hasattr',
234 (r'(?:(?<!def)\s+|\()hasattr',
235 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
235 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
236 (r'opener\([^)]*\).read\(',
236 (r'opener\([^)]*\).read\(',
237 "use opener.read() instead"),
237 "use opener.read() instead"),
238 (r'BaseException', 'not in Python 2.4, use Exception'),
238 (r'BaseException', 'not in Python 2.4, use Exception'),
239 (r'os\.path\.relpath', 'os.path.relpath is not in Python 2.5'),
239 (r'os\.path\.relpath', 'os.path.relpath is not in Python 2.5'),
240 (r'opener\([^)]*\).write\(',
240 (r'opener\([^)]*\).write\(',
241 "use opener.write() instead"),
241 "use opener.write() instead"),
242 (r'[\s\(](open|file)\([^)]*\)\.read\(',
242 (r'[\s\(](open|file)\([^)]*\)\.read\(',
243 "use util.readfile() instead"),
243 "use util.readfile() instead"),
244 (r'[\s\(](open|file)\([^)]*\)\.write\(',
244 (r'[\s\(](open|file)\([^)]*\)\.write\(',
245 "use util.readfile() instead"),
245 "use util.readfile() instead"),
246 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
246 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
247 "always assign an opened file to a variable, and close it afterwards"),
247 "always assign an opened file to a variable, and close it afterwards"),
248 (r'[\s\(](open|file)\([^)]*\)\.',
248 (r'[\s\(](open|file)\([^)]*\)\.',
249 "always assign an opened file to a variable, and close it afterwards"),
249 "always assign an opened file to a variable, and close it afterwards"),
250 (r'(?i)descendent', "the proper spelling is descendAnt"),
250 (r'(?i)descendent', "the proper spelling is descendAnt"),
251 (r'\.debug\(\_', "don't mark debug messages for translation"),
251 (r'\.debug\(\_', "don't mark debug messages for translation"),
252 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
252 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
253 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
253 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
254 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
254 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
255 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
255 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
256 "missing _() in ui message (use () to hide false-positives)"),
256 "missing _() in ui message (use () to hide false-positives)"),
257 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
257 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
258 ],
258 ],
259 # warnings
259 # warnings
260 [
260 [
261 ]
261 ]
262 ]
262 ]
263
263
264 pyfilters = [
264 pyfilters = [
265 (r"""(?msx)(?P<comment>\#.*?$)|
265 (r"""(?msx)(?P<comment>\#.*?$)|
266 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
266 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
267 (?P<text>(([^\\]|\\.)*?))
267 (?P<text>(([^\\]|\\.)*?))
268 (?P=quote))""", reppython),
268 (?P=quote))""", reppython),
269 ]
269 ]
270
270
271 txtfilters = []
271 txtfilters = []
272
272
273 txtpats = [
273 txtpats = [
274 [
274 [
275 ('\s$', 'trailing whitespace'),
275 ('\s$', 'trailing whitespace'),
276 ],
276 ],
277 []
277 []
278 ]
278 ]
279
279
280 cpats = [
280 cpats = [
281 [
281 [
282 (r'//', "don't use //-style comments"),
282 (r'//', "don't use //-style comments"),
283 (r'^ ', "don't use spaces to indent"),
283 (r'^ ', "don't use spaces to indent"),
284 (r'\S\t', "don't use tabs except for indent"),
284 (r'\S\t', "don't use tabs except for indent"),
285 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
285 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
286 (r'.{81}', "line too long"),
286 (r'.{81}', "line too long"),
287 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
287 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
288 (r'return\(', "return is not a function"),
288 (r'return\(', "return is not a function"),
289 (r' ;', "no space before ;"),
289 (r' ;', "no space before ;"),
290 (r'\w+\* \w+', "use int *foo, not int* foo"),
290 (r'\w+\* \w+', "use int *foo, not int* foo"),
291 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
291 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
292 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
292 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
293 (r'\w,\w', "missing whitespace after ,"),
293 (r'\w,\w', "missing whitespace after ,"),
294 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
294 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
295 (r'^#\s+\w', "use #foo, not # foo"),
295 (r'^#\s+\w', "use #foo, not # foo"),
296 (r'[^\n]\Z', "no trailing newline"),
296 (r'[^\n]\Z', "no trailing newline"),
297 (r'^\s*#import\b', "use only #include in standard C code"),
297 (r'^\s*#import\b', "use only #include in standard C code"),
298 ],
298 ],
299 # warnings
299 # warnings
300 []
300 []
301 ]
301 ]
302
302
303 cfilters = [
303 cfilters = [
304 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
304 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
305 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
305 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
306 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
306 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
307 (r'(\()([^)]+\))', repcallspaces),
307 (r'(\()([^)]+\))', repcallspaces),
308 ]
308 ]
309
309
310 inutilpats = [
310 inutilpats = [
311 [
311 [
312 (r'\bui\.', "don't use ui in util"),
312 (r'\bui\.', "don't use ui in util"),
313 ],
313 ],
314 # warnings
314 # warnings
315 []
315 []
316 ]
316 ]
317
317
318 inrevlogpats = [
318 inrevlogpats = [
319 [
319 [
320 (r'\brepo\.', "don't use repo in revlog"),
320 (r'\brepo\.', "don't use repo in revlog"),
321 ],
321 ],
322 # warnings
322 # warnings
323 []
323 []
324 ]
324 ]
325
325
326 checks = [
326 checks = [
327 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
327 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
328 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
328 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
329 ('c', r'.*\.c$', cfilters, cpats),
329 ('c', r'.*\.c$', cfilters, cpats),
330 ('unified test', r'.*\.t$', utestfilters, utestpats),
330 ('unified test', r'.*\.t$', utestfilters, utestpats),
331 ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters,
331 ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters,
332 inrevlogpats),
332 inrevlogpats),
333 ('layering violation ui in util', r'mercurial/util\.py', pyfilters,
333 ('layering violation ui in util', r'mercurial/util\.py', pyfilters,
334 inutilpats),
334 inutilpats),
335 ('txt', r'.*\.txt$', txtfilters, txtpats),
335 ('txt', r'.*\.txt$', txtfilters, txtpats),
336 ]
336 ]
337
337
338 def _preparepats():
338 def _preparepats():
339 for c in checks:
339 for c in checks:
340 failandwarn = c[-1]
340 failandwarn = c[-1]
341 for pats in failandwarn:
341 for pats in failandwarn:
342 for i, pseq in enumerate(pats):
342 for i, pseq in enumerate(pats):
343 # fix-up regexes for multi-line searches
343 # fix-up regexes for multi-line searches
344 p = pseq[0]
344 p = pseq[0]
345 # \s doesn't match \n
345 # \s doesn't match \n
346 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
346 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
347 # [^...] doesn't match newline
347 # [^...] doesn't match newline
348 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
348 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
349
349
350 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
350 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
351 filters = c[2]
351 filters = c[2]
352 for i, flt in enumerate(filters):
352 for i, flt in enumerate(filters):
353 filters[i] = re.compile(flt[0]), flt[1]
353 filters[i] = re.compile(flt[0]), flt[1]
354 _preparepats()
354 _preparepats()
355
355
356 class norepeatlogger(object):
356 class norepeatlogger(object):
357 def __init__(self):
357 def __init__(self):
358 self._lastseen = None
358 self._lastseen = None
359
359
360 def log(self, fname, lineno, line, msg, blame):
360 def log(self, fname, lineno, line, msg, blame):
361 """print error related a to given line of a given file.
361 """print error related a to given line of a given file.
362
362
363 The faulty line will also be printed but only once in the case
363 The faulty line will also be printed but only once in the case
364 of multiple errors.
364 of multiple errors.
365
365
366 :fname: filename
366 :fname: filename
367 :lineno: line number
367 :lineno: line number
368 :line: actual content of the line
368 :line: actual content of the line
369 :msg: error message
369 :msg: error message
370 """
370 """
371 msgid = fname, lineno, line
371 msgid = fname, lineno, line
372 if msgid != self._lastseen:
372 if msgid != self._lastseen:
373 if blame:
373 if blame:
374 print "%s:%d (%s):" % (fname, lineno, blame)
374 print "%s:%d (%s):" % (fname, lineno, blame)
375 else:
375 else:
376 print "%s:%d:" % (fname, lineno)
376 print "%s:%d:" % (fname, lineno)
377 print " > %s" % line
377 print " > %s" % line
378 self._lastseen = msgid
378 self._lastseen = msgid
379 print " " + msg
379 print " " + msg
380
380
381 _defaultlogger = norepeatlogger()
381 _defaultlogger = norepeatlogger()
382
382
383 def getblame(f):
383 def getblame(f):
384 lines = []
384 lines = []
385 for l in os.popen('hg annotate -un %s' % f):
385 for l in os.popen('hg annotate -un %s' % f):
386 start, line = l.split(':', 1)
386 start, line = l.split(':', 1)
387 user, rev = start.split()
387 user, rev = start.split()
388 lines.append((line[1:-1], user, rev))
388 lines.append((line[1:-1], user, rev))
389 return lines
389 return lines
390
390
391 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
391 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
392 blame=False, debug=False, lineno=True):
392 blame=False, debug=False, lineno=True):
393 """checks style and portability of a given file
393 """checks style and portability of a given file
394
394
395 :f: filepath
395 :f: filepath
396 :logfunc: function used to report error
396 :logfunc: function used to report error
397 logfunc(filename, linenumber, linecontent, errormessage)
397 logfunc(filename, linenumber, linecontent, errormessage)
398 :maxerr: number of error to display before aborting.
398 :maxerr: number of error to display before aborting.
399 Set to false (default) to report all errors
399 Set to false (default) to report all errors
400
400
401 return True if no error is found, False otherwise.
401 return True if no error is found, False otherwise.
402 """
402 """
403 blamecache = None
403 blamecache = None
404 result = True
404 result = True
405 for name, match, filters, pats in checks:
405 for name, match, filters, pats in checks:
406 if debug:
406 if debug:
407 print name, f
407 print name, f
408 fc = 0
408 fc = 0
409 if not re.match(match, f):
409 if not re.match(match, f):
410 if debug:
410 if debug:
411 print "Skipping %s for %s it doesn't match %s" % (
411 print "Skipping %s for %s it doesn't match %s" % (
412 name, match, f)
412 name, match, f)
413 continue
413 continue
414 try:
414 try:
415 fp = open(f)
415 fp = open(f)
416 except IOError, e:
416 except IOError, e:
417 print "Skipping %s, %s" % (f, str(e).split(':', 1)[0])
417 print "Skipping %s, %s" % (f, str(e).split(':', 1)[0])
418 continue
418 continue
419 pre = post = fp.read()
419 pre = post = fp.read()
420 fp.close()
420 fp.close()
421 if "no-" "check-code" in pre:
421 if "no-" "check-code" in pre:
422 if debug:
422 if debug:
423 print "Skipping %s for %s it has no-" " check-code" % (
423 print "Skipping %s for %s it has no-" " check-code" % (
424 name, f)
424 name, f)
425 break
425 break
426 for p, r in filters:
426 for p, r in filters:
427 post = re.sub(p, r, post)
427 post = re.sub(p, r, post)
428 nerrs = len(pats[0]) # nerr elements are errors
428 nerrs = len(pats[0]) # nerr elements are errors
429 if warnings:
429 if warnings:
430 pats = pats[0] + pats[1]
430 pats = pats[0] + pats[1]
431 else:
431 else:
432 pats = pats[0]
432 pats = pats[0]
433 # print post # uncomment to show filtered version
433 # print post # uncomment to show filtered version
434
434
435 if debug:
435 if debug:
436 print "Checking %s for %s" % (name, f)
436 print "Checking %s for %s" % (name, f)
437
437
438 prelines = None
438 prelines = None
439 errors = []
439 errors = []
440 for i, pat in enumerate(pats):
440 for i, pat in enumerate(pats):
441 if len(pat) == 3:
441 if len(pat) == 3:
442 p, msg, ignore = pat
442 p, msg, ignore = pat
443 else:
443 else:
444 p, msg = pat
444 p, msg = pat
445 ignore = None
445 ignore = None
446
446
447 pos = 0
447 pos = 0
448 n = 0
448 n = 0
449 for m in p.finditer(post):
449 for m in p.finditer(post):
450 if prelines is None:
450 if prelines is None:
451 prelines = pre.splitlines()
451 prelines = pre.splitlines()
452 postlines = post.splitlines(True)
452 postlines = post.splitlines(True)
453
453
454 start = m.start()
454 start = m.start()
455 while n < len(postlines):
455 while n < len(postlines):
456 step = len(postlines[n])
456 step = len(postlines[n])
457 if pos + step > start:
457 if pos + step > start:
458 break
458 break
459 pos += step
459 pos += step
460 n += 1
460 n += 1
461 l = prelines[n]
461 l = prelines[n]
462
462
463 if "check-code" "-ignore" in l:
463 if "check-code" "-ignore" in l:
464 if debug:
464 if debug:
465 print "Skipping %s for %s:%s (check-code" "-ignore)" % (
465 print "Skipping %s for %s:%s (check-code" "-ignore)" % (
466 name, f, n)
466 name, f, n)
467 continue
467 continue
468 elif ignore and re.search(ignore, l, re.MULTILINE):
468 elif ignore and re.search(ignore, l, re.MULTILINE):
469 continue
469 continue
470 bd = ""
470 bd = ""
471 if blame:
471 if blame:
472 bd = 'working directory'
472 bd = 'working directory'
473 if not blamecache:
473 if not blamecache:
474 blamecache = getblame(f)
474 blamecache = getblame(f)
475 if n < len(blamecache):
475 if n < len(blamecache):
476 bl, bu, br = blamecache[n]
476 bl, bu, br = blamecache[n]
477 if bl == l:
477 if bl == l:
478 bd = '%s@%s' % (bu, br)
478 bd = '%s@%s' % (bu, br)
479 if i >= nerrs:
479 if i >= nerrs:
480 msg = "warning: " + msg
480 msg = "warning: " + msg
481 errors.append((f, lineno and n + 1, l, msg, bd))
481 errors.append((f, lineno and n + 1, l, msg, bd))
482 result = False
482 result = False
483
483
484 errors.sort()
484 errors.sort()
485 for e in errors:
485 for e in errors:
486 logfunc(*e)
486 logfunc(*e)
487 fc += 1
487 fc += 1
488 if maxerr and fc >= maxerr:
488 if maxerr and fc >= maxerr:
489 print " (too many errors, giving up)"
489 print " (too many errors, giving up)"
490 break
490 break
491
491
492 return result
492 return result
493
493
494 if __name__ == "__main__":
494 if __name__ == "__main__":
495 parser = optparse.OptionParser("%prog [options] [files]")
495 parser = optparse.OptionParser("%prog [options] [files]")
496 parser.add_option("-w", "--warnings", action="store_true",
496 parser.add_option("-w", "--warnings", action="store_true",
497 help="include warning-level checks")
497 help="include warning-level checks")
498 parser.add_option("-p", "--per-file", type="int",
498 parser.add_option("-p", "--per-file", type="int",
499 help="max warnings per file")
499 help="max warnings per file")
500 parser.add_option("-b", "--blame", action="store_true",
500 parser.add_option("-b", "--blame", action="store_true",
501 help="use annotate to generate blame info")
501 help="use annotate to generate blame info")
502 parser.add_option("", "--debug", action="store_true",
502 parser.add_option("", "--debug", action="store_true",
503 help="show debug information")
503 help="show debug information")
504 parser.add_option("", "--nolineno", action="store_false",
504 parser.add_option("", "--nolineno", action="store_false",
505 dest='lineno', help="don't show line numbers")
505 dest='lineno', help="don't show line numbers")
506
506
507 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
507 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
508 lineno=True)
508 lineno=True)
509 (options, args) = parser.parse_args()
509 (options, args) = parser.parse_args()
510
510
511 if len(args) == 0:
511 if len(args) == 0:
512 check = glob.glob("*")
512 check = glob.glob("*")
513 else:
513 else:
514 check = args
514 check = args
515
515
516 ret = 0
516 ret = 0
517 for f in check:
517 for f in check:
518 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
518 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
519 blame=options.blame, debug=options.debug,
519 blame=options.blame, debug=options.debug,
520 lineno=options.lineno):
520 lineno=options.lineno):
521 ret = 1
521 ret = 1
522 sys.exit(ret)
522 sys.exit(ret)
General Comments 0
You need to be logged in to leave comments. Login now