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