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