##// END OF EJS Templates
check-code: recognise %= as an operator
Pierre-Yves David -
r17167:5f131ae0 default
parent child Browse files
Show More
@@ -1,449 +1,449
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| $ ).*(\S[ \t]+|^[ \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 ],
209 ],
210 # warnings
210 # warnings
211 [
211 [
212 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
212 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
213 "warning: unwrapped ui message"),
213 "warning: unwrapped ui message"),
214 ]
214 ]
215 ]
215 ]
216
216
217 pyfilters = [
217 pyfilters = [
218 (r"""(?msx)(?P<comment>\#.*?$)|
218 (r"""(?msx)(?P<comment>\#.*?$)|
219 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
219 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
220 (?P<text>(([^\\]|\\.)*?))
220 (?P<text>(([^\\]|\\.)*?))
221 (?P=quote))""", reppython),
221 (?P=quote))""", reppython),
222 ]
222 ]
223
223
224 cpats = [
224 cpats = [
225 [
225 [
226 (r'//', "don't use //-style comments"),
226 (r'//', "don't use //-style comments"),
227 (r'^ ', "don't use spaces to indent"),
227 (r'^ ', "don't use spaces to indent"),
228 (r'\S\t', "don't use tabs except for indent"),
228 (r'\S\t', "don't use tabs except for indent"),
229 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
229 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
230 (r'.{81}', "line too long"),
230 (r'.{81}', "line too long"),
231 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
231 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
232 (r'return\(', "return is not a function"),
232 (r'return\(', "return is not a function"),
233 (r' ;', "no space before ;"),
233 (r' ;', "no space before ;"),
234 (r'\w+\* \w+', "use int *foo, not int* foo"),
234 (r'\w+\* \w+', "use int *foo, not int* foo"),
235 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
235 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
236 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
236 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
237 (r'\w,\w', "missing whitespace after ,"),
237 (r'\w,\w', "missing whitespace after ,"),
238 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
238 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
239 (r'^#\s+\w', "use #foo, not # foo"),
239 (r'^#\s+\w', "use #foo, not # foo"),
240 (r'[^\n]\Z', "no trailing newline"),
240 (r'[^\n]\Z', "no trailing newline"),
241 (r'^\s*#import\b', "use only #include in standard C code"),
241 (r'^\s*#import\b', "use only #include in standard C code"),
242 ],
242 ],
243 # warnings
243 # warnings
244 []
244 []
245 ]
245 ]
246
246
247 cfilters = [
247 cfilters = [
248 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
248 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
249 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
249 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
250 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
250 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
251 (r'(\()([^)]+\))', repcallspaces),
251 (r'(\()([^)]+\))', repcallspaces),
252 ]
252 ]
253
253
254 inutilpats = [
254 inutilpats = [
255 [
255 [
256 (r'\bui\.', "don't use ui in util"),
256 (r'\bui\.', "don't use ui in util"),
257 ],
257 ],
258 # warnings
258 # warnings
259 []
259 []
260 ]
260 ]
261
261
262 inrevlogpats = [
262 inrevlogpats = [
263 [
263 [
264 (r'\brepo\.', "don't use repo in revlog"),
264 (r'\brepo\.', "don't use repo in revlog"),
265 ],
265 ],
266 # warnings
266 # warnings
267 []
267 []
268 ]
268 ]
269
269
270 checks = [
270 checks = [
271 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
271 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
272 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
272 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
273 ('c', r'.*\.c$', cfilters, cpats),
273 ('c', r'.*\.c$', cfilters, cpats),
274 ('unified test', r'.*\.t$', utestfilters, utestpats),
274 ('unified test', r'.*\.t$', utestfilters, utestpats),
275 ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters,
275 ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters,
276 inrevlogpats),
276 inrevlogpats),
277 ('layering violation ui in util', r'mercurial/util\.py', pyfilters,
277 ('layering violation ui in util', r'mercurial/util\.py', pyfilters,
278 inutilpats),
278 inutilpats),
279 ]
279 ]
280
280
281 class norepeatlogger(object):
281 class norepeatlogger(object):
282 def __init__(self):
282 def __init__(self):
283 self._lastseen = None
283 self._lastseen = None
284
284
285 def log(self, fname, lineno, line, msg, blame):
285 def log(self, fname, lineno, line, msg, blame):
286 """print error related a to given line of a given file.
286 """print error related a to given line of a given file.
287
287
288 The faulty line will also be printed but only once in the case
288 The faulty line will also be printed but only once in the case
289 of multiple errors.
289 of multiple errors.
290
290
291 :fname: filename
291 :fname: filename
292 :lineno: line number
292 :lineno: line number
293 :line: actual content of the line
293 :line: actual content of the line
294 :msg: error message
294 :msg: error message
295 """
295 """
296 msgid = fname, lineno, line
296 msgid = fname, lineno, line
297 if msgid != self._lastseen:
297 if msgid != self._lastseen:
298 if blame:
298 if blame:
299 print "%s:%d (%s):" % (fname, lineno, blame)
299 print "%s:%d (%s):" % (fname, lineno, blame)
300 else:
300 else:
301 print "%s:%d:" % (fname, lineno)
301 print "%s:%d:" % (fname, lineno)
302 print " > %s" % line
302 print " > %s" % line
303 self._lastseen = msgid
303 self._lastseen = msgid
304 print " " + msg
304 print " " + msg
305
305
306 _defaultlogger = norepeatlogger()
306 _defaultlogger = norepeatlogger()
307
307
308 def getblame(f):
308 def getblame(f):
309 lines = []
309 lines = []
310 for l in os.popen('hg annotate -un %s' % f):
310 for l in os.popen('hg annotate -un %s' % f):
311 start, line = l.split(':', 1)
311 start, line = l.split(':', 1)
312 user, rev = start.split()
312 user, rev = start.split()
313 lines.append((line[1:-1], user, rev))
313 lines.append((line[1:-1], user, rev))
314 return lines
314 return lines
315
315
316 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
316 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
317 blame=False, debug=False, lineno=True):
317 blame=False, debug=False, lineno=True):
318 """checks style and portability of a given file
318 """checks style and portability of a given file
319
319
320 :f: filepath
320 :f: filepath
321 :logfunc: function used to report error
321 :logfunc: function used to report error
322 logfunc(filename, linenumber, linecontent, errormessage)
322 logfunc(filename, linenumber, linecontent, errormessage)
323 :maxerr: number of error to display before arborting.
323 :maxerr: number of error to display before arborting.
324 Set to false (default) to report all errors
324 Set to false (default) to report all errors
325
325
326 return True if no error is found, False otherwise.
326 return True if no error is found, False otherwise.
327 """
327 """
328 blamecache = None
328 blamecache = None
329 result = True
329 result = True
330 for name, match, filters, pats in checks:
330 for name, match, filters, pats in checks:
331 if debug:
331 if debug:
332 print name, f
332 print name, f
333 fc = 0
333 fc = 0
334 if not re.match(match, f):
334 if not re.match(match, f):
335 if debug:
335 if debug:
336 print "Skipping %s for %s it doesn't match %s" % (
336 print "Skipping %s for %s it doesn't match %s" % (
337 name, match, f)
337 name, match, f)
338 continue
338 continue
339 fp = open(f)
339 fp = open(f)
340 pre = post = fp.read()
340 pre = post = fp.read()
341 fp.close()
341 fp.close()
342 if "no-" + "check-code" in pre:
342 if "no-" + "check-code" in pre:
343 if debug:
343 if debug:
344 print "Skipping %s for %s it has no- and check-code" % (
344 print "Skipping %s for %s it has no- and check-code" % (
345 name, f)
345 name, f)
346 break
346 break
347 for p, r in filters:
347 for p, r in filters:
348 post = re.sub(p, r, post)
348 post = re.sub(p, r, post)
349 if warnings:
349 if warnings:
350 pats = pats[0] + pats[1]
350 pats = pats[0] + pats[1]
351 else:
351 else:
352 pats = pats[0]
352 pats = pats[0]
353 # print post # uncomment to show filtered version
353 # print post # uncomment to show filtered version
354
354
355 if debug:
355 if debug:
356 print "Checking %s for %s" % (name, f)
356 print "Checking %s for %s" % (name, f)
357
357
358 prelines = None
358 prelines = None
359 errors = []
359 errors = []
360 for pat in pats:
360 for pat in pats:
361 if len(pat) == 3:
361 if len(pat) == 3:
362 p, msg, ignore = pat
362 p, msg, ignore = pat
363 else:
363 else:
364 p, msg = pat
364 p, msg = pat
365 ignore = None
365 ignore = None
366
366
367 # fix-up regexes for multiline searches
367 # fix-up regexes for multiline searches
368 po = p
368 po = p
369 # \s doesn't match \n
369 # \s doesn't match \n
370 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
370 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
371 # [^...] doesn't match newline
371 # [^...] doesn't match newline
372 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
372 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
373
373
374 #print po, '=>', p
374 #print po, '=>', p
375
375
376 pos = 0
376 pos = 0
377 n = 0
377 n = 0
378 for m in re.finditer(p, post, re.MULTILINE):
378 for m in re.finditer(p, post, re.MULTILINE):
379 if prelines is None:
379 if prelines is None:
380 prelines = pre.splitlines()
380 prelines = pre.splitlines()
381 postlines = post.splitlines(True)
381 postlines = post.splitlines(True)
382
382
383 start = m.start()
383 start = m.start()
384 while n < len(postlines):
384 while n < len(postlines):
385 step = len(postlines[n])
385 step = len(postlines[n])
386 if pos + step > start:
386 if pos + step > start:
387 break
387 break
388 pos += step
388 pos += step
389 n += 1
389 n += 1
390 l = prelines[n]
390 l = prelines[n]
391
391
392 if "check-code" + "-ignore" in l:
392 if "check-code" + "-ignore" in l:
393 if debug:
393 if debug:
394 print "Skipping %s for %s:%s (check-code -ignore)" % (
394 print "Skipping %s for %s:%s (check-code -ignore)" % (
395 name, f, n)
395 name, f, n)
396 continue
396 continue
397 elif ignore and re.search(ignore, l, re.MULTILINE):
397 elif ignore and re.search(ignore, l, re.MULTILINE):
398 continue
398 continue
399 bd = ""
399 bd = ""
400 if blame:
400 if blame:
401 bd = 'working directory'
401 bd = 'working directory'
402 if not blamecache:
402 if not blamecache:
403 blamecache = getblame(f)
403 blamecache = getblame(f)
404 if n < len(blamecache):
404 if n < len(blamecache):
405 bl, bu, br = blamecache[n]
405 bl, bu, br = blamecache[n]
406 if bl == l:
406 if bl == l:
407 bd = '%s@%s' % (bu, br)
407 bd = '%s@%s' % (bu, br)
408 errors.append((f, lineno and n + 1, l, msg, bd))
408 errors.append((f, lineno and n + 1, l, msg, bd))
409 result = False
409 result = False
410
410
411 errors.sort()
411 errors.sort()
412 for e in errors:
412 for e in errors:
413 logfunc(*e)
413 logfunc(*e)
414 fc += 1
414 fc += 1
415 if maxerr and fc >= maxerr:
415 if maxerr and fc >= maxerr:
416 print " (too many errors, giving up)"
416 print " (too many errors, giving up)"
417 break
417 break
418
418
419 return result
419 return result
420
420
421 if __name__ == "__main__":
421 if __name__ == "__main__":
422 parser = optparse.OptionParser("%prog [options] [files]")
422 parser = optparse.OptionParser("%prog [options] [files]")
423 parser.add_option("-w", "--warnings", action="store_true",
423 parser.add_option("-w", "--warnings", action="store_true",
424 help="include warning-level checks")
424 help="include warning-level checks")
425 parser.add_option("-p", "--per-file", type="int",
425 parser.add_option("-p", "--per-file", type="int",
426 help="max warnings per file")
426 help="max warnings per file")
427 parser.add_option("-b", "--blame", action="store_true",
427 parser.add_option("-b", "--blame", action="store_true",
428 help="use annotate to generate blame info")
428 help="use annotate to generate blame info")
429 parser.add_option("", "--debug", action="store_true",
429 parser.add_option("", "--debug", action="store_true",
430 help="show debug information")
430 help="show debug information")
431 parser.add_option("", "--nolineno", action="store_false",
431 parser.add_option("", "--nolineno", action="store_false",
432 dest='lineno', help="don't show line numbers")
432 dest='lineno', help="don't show line numbers")
433
433
434 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
434 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
435 lineno=True)
435 lineno=True)
436 (options, args) = parser.parse_args()
436 (options, args) = parser.parse_args()
437
437
438 if len(args) == 0:
438 if len(args) == 0:
439 check = glob.glob("*")
439 check = glob.glob("*")
440 else:
440 else:
441 check = args
441 check = args
442
442
443 ret = 0
443 ret = 0
444 for f in check:
444 for f in check:
445 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
445 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
446 blame=options.blame, debug=options.debug,
446 blame=options.blame, debug=options.debug,
447 lineno=options.lineno):
447 lineno=options.lineno):
448 ret = 1
448 ret = 1
449 sys.exit(ret)
449 sys.exit(ret)
General Comments 0
You need to be logged in to leave comments. Login now