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