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