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