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