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