##// END OF EJS Templates
check-code: disallow calling opener(...).read() and opener(..).write()
Dan Villiom Podlaski Christiansen -
r14169:1b4b8206 default
parent child Browse files
Show More
@@ -1,363 +1,375 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"[^\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 p, m in testpats[0] + testpats[1]:
98 for p, m in testpats[0] + testpats[1]:
99 if p.startswith('^'):
99 if p.startswith('^'):
100 p = uprefix + p[1:]
100 p = uprefix + p[1:]
101 else:
101 else:
102 p = uprefix + p
102 p = uprefix + p
103 utestpats.append((p, m))
103 utestpats.append((p, m))
104
104
105 utestfilters = [
105 utestfilters = [
106 (r"( *)(#([^\n]*\S)?)", repcomment),
106 (r"( *)(#([^\n]*\S)?)", repcomment),
107 ]
107 ]
108
108
109 pypats = [
109 pypats = [
110 [
110 [
111 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
111 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
112 "tuple parameter unpacking not available in Python 3+"),
112 "tuple parameter unpacking not available in Python 3+"),
113 (r'lambda\s*\(.*,.*\)',
113 (r'lambda\s*\(.*,.*\)',
114 "tuple parameter unpacking not available in Python 3+"),
114 "tuple parameter unpacking not available in Python 3+"),
115 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
115 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
116 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
116 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
117 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
117 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
118 (r'^\s*\t', "don't use tabs"),
118 (r'^\s*\t', "don't use tabs"),
119 (r'\S;\s*\n', "semicolon"),
119 (r'\S;\s*\n', "semicolon"),
120 (r'\w,\w', "missing whitespace after ,"),
120 (r'\w,\w', "missing whitespace after ,"),
121 (r'\w[+/*\-<>]\w', "missing whitespace in expression"),
121 (r'\w[+/*\-<>]\w', "missing whitespace in expression"),
122 (r'^\s+\w+=\w+[^,)]$', "missing whitespace in assignment"),
122 (r'^\s+\w+=\w+[^,)]$', "missing whitespace in assignment"),
123 (r'.{85}', "line too long"),
123 (r'.{85}', "line too long"),
124 (r'[^\n]\Z', "no trailing newline"),
124 (r'[^\n]\Z', "no trailing newline"),
125 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
125 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
126 # (r'^\s+[^_ ][^_. ]+_[^_]+\s*=', "don't use underbars in identifiers"),
126 # (r'^\s+[^_ ][^_. ]+_[^_]+\s*=', "don't use underbars in identifiers"),
127 # (r'\w*[a-z][A-Z]\w*\s*=', "don't use camelcase in identifiers"),
127 # (r'\w*[a-z][A-Z]\w*\s*=', "don't use camelcase in identifiers"),
128 (r'^\s*(if|while|def|class|except|try)\s[^[]*:\s*[^\]#\s]+',
128 (r'^\s*(if|while|def|class|except|try)\s[^[]*:\s*[^\]#\s]+',
129 "linebreak after :"),
129 "linebreak after :"),
130 (r'class\s[^(]:', "old-style class, use class foo(object)"),
130 (r'class\s[^(]:', "old-style class, use class foo(object)"),
131 (r'\b(%s)\(' % '|'.join(keyword.kwlist),
131 (r'\b(%s)\(' % '|'.join(keyword.kwlist),
132 "Python keyword is not a function"),
132 "Python keyword is not a function"),
133 (r',]', "unneeded trailing ',' in list"),
133 (r',]', "unneeded trailing ',' in list"),
134 # (r'class\s[A-Z][^\(]*\((?!Exception)',
134 # (r'class\s[A-Z][^\(]*\((?!Exception)',
135 # "don't capitalize non-exception classes"),
135 # "don't capitalize non-exception classes"),
136 # (r'in range\(', "use xrange"),
136 # (r'in range\(', "use xrange"),
137 # (r'^\s*print\s+', "avoid using print in core and extensions"),
137 # (r'^\s*print\s+', "avoid using print in core and extensions"),
138 (r'[\x80-\xff]', "non-ASCII character literal"),
138 (r'[\x80-\xff]', "non-ASCII character literal"),
139 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
139 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
140 (r'^\s*with\s+', "with not available in Python 2.4"),
140 (r'^\s*with\s+', "with not available in Python 2.4"),
141 (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
141 (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
142 (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
142 (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
143 (r'(?<!def)\s+(any|all|format)\(',
143 (r'(?<!def)\s+(any|all|format)\(',
144 "any/all/format not available in Python 2.4"),
144 "any/all/format not available in Python 2.4"),
145 (r'(?<!def)\s+(callable)\(',
145 (r'(?<!def)\s+(callable)\(',
146 "callable not available in Python 3, use hasattr(f, '__call__')"),
146 "callable not available in Python 3, use hasattr(f, '__call__')"),
147 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
147 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
148 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
148 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
149 "gratuitous whitespace after Python keyword"),
149 "gratuitous whitespace after Python keyword"),
150 (r'([\(\[]\s\S)|(\S\s[\)\]])', "gratuitous whitespace in () or []"),
150 (r'([\(\[]\s\S)|(\S\s[\)\]])', "gratuitous whitespace in () or []"),
151 # (r'\s\s=', "gratuitous whitespace before ="),
151 # (r'\s\s=', "gratuitous whitespace before ="),
152 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
152 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
153 "missing whitespace around operator"),
153 "missing whitespace around operator"),
154 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\s',
154 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\s',
155 "missing whitespace around operator"),
155 "missing whitespace around operator"),
156 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
156 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
157 "missing whitespace around operator"),
157 "missing whitespace around operator"),
158 (r'[^+=*!<>&| -](\s=|=\s)[^= ]',
158 (r'[^+=*!<>&| -](\s=|=\s)[^= ]',
159 "wrong whitespace around ="),
159 "wrong whitespace around ="),
160 (r'raise Exception', "don't raise generic exceptions"),
160 (r'raise Exception', "don't raise generic exceptions"),
161 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
161 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
162 (r' [=!]=\s+(True|False|None)',
162 (r' [=!]=\s+(True|False|None)',
163 "comparison with singleton, use 'is' or 'is not' instead"),
163 "comparison with singleton, use 'is' or 'is not' instead"),
164 (r'opener\([^)]*\).read\(',
165 "use opener.read() instead"),
166 (r'opener\([^)]*\).write\(',
167 "use opener.write() instead"),
168 (r'[\s\(](open|file)\([^)]*\)\.read\(',
169 "use util.readfile() instead"),
170 (r'[\s\(](open|file)\([^)]*\)\.write\(',
171 "use util.readfile() instead"),
172 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
173 "always assign an opened file to a variable, and close it afterwards"),
174 (r'[\s\(](open|file)\([^)]*\)\.',
175 "always assign an opened file to a variable, and close it afterwards"),
164 ],
176 ],
165 # warnings
177 # warnings
166 [
178 [
167 (r'.{81}', "warning: line over 80 characters"),
179 (r'.{81}', "warning: line over 80 characters"),
168 (r'^\s*except:$', "warning: naked except clause"),
180 (r'^\s*except:$', "warning: naked except clause"),
169 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
181 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
170 "warning: unwrapped ui message"),
182 "warning: unwrapped ui message"),
171 ]
183 ]
172 ]
184 ]
173
185
174 pyfilters = [
186 pyfilters = [
175 (r"""(?msx)(?P<comment>\#.*?$)|
187 (r"""(?msx)(?P<comment>\#.*?$)|
176 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
188 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
177 (?P<text>(([^\\]|\\.)*?))
189 (?P<text>(([^\\]|\\.)*?))
178 (?P=quote))""", reppython),
190 (?P=quote))""", reppython),
179 ]
191 ]
180
192
181 cpats = [
193 cpats = [
182 [
194 [
183 (r'//', "don't use //-style comments"),
195 (r'//', "don't use //-style comments"),
184 (r'^ ', "don't use spaces to indent"),
196 (r'^ ', "don't use spaces to indent"),
185 (r'\S\t', "don't use tabs except for indent"),
197 (r'\S\t', "don't use tabs except for indent"),
186 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
198 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
187 (r'.{85}', "line too long"),
199 (r'.{85}', "line too long"),
188 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
200 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
189 (r'return\(', "return is not a function"),
201 (r'return\(', "return is not a function"),
190 (r' ;', "no space before ;"),
202 (r' ;', "no space before ;"),
191 (r'\w+\* \w+', "use int *foo, not int* foo"),
203 (r'\w+\* \w+', "use int *foo, not int* foo"),
192 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
204 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
193 (r'\S+ (\+\+|--)', "use foo++, not foo ++"),
205 (r'\S+ (\+\+|--)', "use foo++, not foo ++"),
194 (r'\w,\w', "missing whitespace after ,"),
206 (r'\w,\w', "missing whitespace after ,"),
195 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
207 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
196 (r'^#\s+\w', "use #foo, not # foo"),
208 (r'^#\s+\w', "use #foo, not # foo"),
197 (r'[^\n]\Z', "no trailing newline"),
209 (r'[^\n]\Z', "no trailing newline"),
198 (r'^\s*#import\b', "use only #include in standard C code"),
210 (r'^\s*#import\b', "use only #include in standard C code"),
199 ],
211 ],
200 # warnings
212 # warnings
201 []
213 []
202 ]
214 ]
203
215
204 cfilters = [
216 cfilters = [
205 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
217 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
206 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
218 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
207 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
219 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
208 (r'(\()([^)]+\))', repcallspaces),
220 (r'(\()([^)]+\))', repcallspaces),
209 ]
221 ]
210
222
211 inutilpats = [
223 inutilpats = [
212 [
224 [
213 (r'\bui\.', "don't use ui in util"),
225 (r'\bui\.', "don't use ui in util"),
214 ],
226 ],
215 # warnings
227 # warnings
216 []
228 []
217 ]
229 ]
218
230
219 inrevlogpats = [
231 inrevlogpats = [
220 [
232 [
221 (r'\brepo\.', "don't use repo in revlog"),
233 (r'\brepo\.', "don't use repo in revlog"),
222 ],
234 ],
223 # warnings
235 # warnings
224 []
236 []
225 ]
237 ]
226
238
227 checks = [
239 checks = [
228 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
240 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
229 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
241 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
230 ('c', r'.*\.c$', cfilters, cpats),
242 ('c', r'.*\.c$', cfilters, cpats),
231 ('unified test', r'.*\.t$', utestfilters, utestpats),
243 ('unified test', r'.*\.t$', utestfilters, utestpats),
232 ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters,
244 ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters,
233 inrevlogpats),
245 inrevlogpats),
234 ('layering violation ui in util', r'mercurial/util\.py', pyfilters,
246 ('layering violation ui in util', r'mercurial/util\.py', pyfilters,
235 inutilpats),
247 inutilpats),
236 ]
248 ]
237
249
238 class norepeatlogger(object):
250 class norepeatlogger(object):
239 def __init__(self):
251 def __init__(self):
240 self._lastseen = None
252 self._lastseen = None
241
253
242 def log(self, fname, lineno, line, msg, blame):
254 def log(self, fname, lineno, line, msg, blame):
243 """print error related a to given line of a given file.
255 """print error related a to given line of a given file.
244
256
245 The faulty line will also be printed but only once in the case
257 The faulty line will also be printed but only once in the case
246 of multiple errors.
258 of multiple errors.
247
259
248 :fname: filename
260 :fname: filename
249 :lineno: line number
261 :lineno: line number
250 :line: actual content of the line
262 :line: actual content of the line
251 :msg: error message
263 :msg: error message
252 """
264 """
253 msgid = fname, lineno, line
265 msgid = fname, lineno, line
254 if msgid != self._lastseen:
266 if msgid != self._lastseen:
255 if blame:
267 if blame:
256 print "%s:%d (%s):" % (fname, lineno, blame)
268 print "%s:%d (%s):" % (fname, lineno, blame)
257 else:
269 else:
258 print "%s:%d:" % (fname, lineno)
270 print "%s:%d:" % (fname, lineno)
259 print " > %s" % line
271 print " > %s" % line
260 self._lastseen = msgid
272 self._lastseen = msgid
261 print " " + msg
273 print " " + msg
262
274
263 _defaultlogger = norepeatlogger()
275 _defaultlogger = norepeatlogger()
264
276
265 def getblame(f):
277 def getblame(f):
266 lines = []
278 lines = []
267 for l in os.popen('hg annotate -un %s' % f):
279 for l in os.popen('hg annotate -un %s' % f):
268 start, line = l.split(':', 1)
280 start, line = l.split(':', 1)
269 user, rev = start.split()
281 user, rev = start.split()
270 lines.append((line[1:-1], user, rev))
282 lines.append((line[1:-1], user, rev))
271 return lines
283 return lines
272
284
273 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
285 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
274 blame=False, debug=False):
286 blame=False, debug=False):
275 """checks style and portability of a given file
287 """checks style and portability of a given file
276
288
277 :f: filepath
289 :f: filepath
278 :logfunc: function used to report error
290 :logfunc: function used to report error
279 logfunc(filename, linenumber, linecontent, errormessage)
291 logfunc(filename, linenumber, linecontent, errormessage)
280 :maxerr: number of error to display before arborting.
292 :maxerr: number of error to display before arborting.
281 Set to None (default) to report all errors
293 Set to None (default) to report all errors
282
294
283 return True if no error is found, False otherwise.
295 return True if no error is found, False otherwise.
284 """
296 """
285 blamecache = None
297 blamecache = None
286 result = True
298 result = True
287 for name, match, filters, pats in checks:
299 for name, match, filters, pats in checks:
288 if debug:
300 if debug:
289 print name, f
301 print name, f
290 fc = 0
302 fc = 0
291 if not re.match(match, f):
303 if not re.match(match, f):
292 if debug:
304 if debug:
293 print "Skipping %s for %s it doesn't match %s" % (
305 print "Skipping %s for %s it doesn't match %s" % (
294 name, match, f)
306 name, match, f)
295 continue
307 continue
296 fp = open(f)
308 fp = open(f)
297 pre = post = fp.read()
309 pre = post = fp.read()
298 fp.close()
310 fp.close()
299 if "no-" + "check-code" in pre:
311 if "no-" + "check-code" in pre:
300 if debug:
312 if debug:
301 print "Skipping %s for %s it has no- and check-code" % (
313 print "Skipping %s for %s it has no- and check-code" % (
302 name, f)
314 name, f)
303 break
315 break
304 for p, r in filters:
316 for p, r in filters:
305 post = re.sub(p, r, post)
317 post = re.sub(p, r, post)
306 if warnings:
318 if warnings:
307 pats = pats[0] + pats[1]
319 pats = pats[0] + pats[1]
308 else:
320 else:
309 pats = pats[0]
321 pats = pats[0]
310 # print post # uncomment to show filtered version
322 # print post # uncomment to show filtered version
311 z = enumerate(zip(pre.splitlines(), post.splitlines(True)))
323 z = enumerate(zip(pre.splitlines(), post.splitlines(True)))
312 if debug:
324 if debug:
313 print "Checking %s for %s" % (name, f)
325 print "Checking %s for %s" % (name, f)
314 for n, l in z:
326 for n, l in z:
315 if "check-code" + "-ignore" in l[0]:
327 if "check-code" + "-ignore" in l[0]:
316 if debug:
328 if debug:
317 print "Skipping %s for %s:%s (check-code -ignore)" % (
329 print "Skipping %s for %s:%s (check-code -ignore)" % (
318 name, f, n)
330 name, f, n)
319 continue
331 continue
320 for p, msg in pats:
332 for p, msg in pats:
321 if re.search(p, l[1]):
333 if re.search(p, l[1]):
322 bd = ""
334 bd = ""
323 if blame:
335 if blame:
324 bd = 'working directory'
336 bd = 'working directory'
325 if not blamecache:
337 if not blamecache:
326 blamecache = getblame(f)
338 blamecache = getblame(f)
327 if n < len(blamecache):
339 if n < len(blamecache):
328 bl, bu, br = blamecache[n]
340 bl, bu, br = blamecache[n]
329 if bl == l[0]:
341 if bl == l[0]:
330 bd = '%s@%s' % (bu, br)
342 bd = '%s@%s' % (bu, br)
331 logfunc(f, n + 1, l[0], msg, bd)
343 logfunc(f, n + 1, l[0], msg, bd)
332 fc += 1
344 fc += 1
333 result = False
345 result = False
334 if maxerr is not None and fc >= maxerr:
346 if maxerr is not None and fc >= maxerr:
335 print " (too many errors, giving up)"
347 print " (too many errors, giving up)"
336 break
348 break
337 return result
349 return result
338
350
339 if __name__ == "__main__":
351 if __name__ == "__main__":
340 parser = optparse.OptionParser("%prog [options] [files]")
352 parser = optparse.OptionParser("%prog [options] [files]")
341 parser.add_option("-w", "--warnings", action="store_true",
353 parser.add_option("-w", "--warnings", action="store_true",
342 help="include warning-level checks")
354 help="include warning-level checks")
343 parser.add_option("-p", "--per-file", type="int",
355 parser.add_option("-p", "--per-file", type="int",
344 help="max warnings per file")
356 help="max warnings per file")
345 parser.add_option("-b", "--blame", action="store_true",
357 parser.add_option("-b", "--blame", action="store_true",
346 help="use annotate to generate blame info")
358 help="use annotate to generate blame info")
347 parser.add_option("", "--debug", action="store_true",
359 parser.add_option("", "--debug", action="store_true",
348 help="show debug information")
360 help="show debug information")
349
361
350 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False)
362 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False)
351 (options, args) = parser.parse_args()
363 (options, args) = parser.parse_args()
352
364
353 if len(args) == 0:
365 if len(args) == 0:
354 check = glob.glob("*")
366 check = glob.glob("*")
355 else:
367 else:
356 check = args
368 check = args
357
369
358 for f in check:
370 for f in check:
359 ret = 0
371 ret = 0
360 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
372 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
361 blame=options.blame, debug=options.debug):
373 blame=options.blame, debug=options.debug):
362 ret = 1
374 ret = 1
363 sys.exit(ret)
375 sys.exit(ret)
General Comments 0
You need to be logged in to leave comments. Login now