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