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