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