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