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