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