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