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