##// END OF EJS Templates
check-code: catch dict.has_key
Martin Geisler -
r11602:ba2520dd default
parent child Browse files
Show More
@@ -1,238 +1,239
1 1 #!/usr/bin/env python
2 2 #
3 3 # check-code - a style and portability checker for Mercurial
4 4 #
5 5 # Copyright 2010 Matt Mackall <mpm@selenic.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 import re, glob
11 11 import optparse
12 12
13 13 def repquote(m):
14 14 t = re.sub(r"\w", "x", m.group('text'))
15 15 t = re.sub(r"[^\sx]", "o", t)
16 16 return m.group('quote') + t + m.group('quote')
17 17
18 18 def reppython(m):
19 19 comment = m.group('comment')
20 20 if comment:
21 21 return "#" * len(comment)
22 22 return repquote(m)
23 23
24 24 def repcomment(m):
25 25 return m.group(1) + "#" * len(m.group(2))
26 26
27 27 def repccomment(m):
28 28 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
29 29 return m.group(1) + t + "*/"
30 30
31 31 def repcallspaces(m):
32 32 t = re.sub(r"\n\s+", "\n", m.group(2))
33 33 return m.group(1) + t
34 34
35 35 def repinclude(m):
36 36 return m.group(1) + "<foo>"
37 37
38 38 def rephere(m):
39 39 t = re.sub(r"\S", "x", m.group(2))
40 40 return m.group(1) + t
41 41
42 42
43 43 testpats = [
44 44 (r'(pushd|popd)', "don't use 'pushd' or 'popd', use 'cd'"),
45 45 (r'\W\$?\(\([^\)]*\)\)', "don't use (()) or $(()), use 'expr'"),
46 46 (r'^function', "don't use 'function', use old style"),
47 47 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
48 48 (r'echo.*\\n', "don't use 'echo \\n', use printf"),
49 49 (r'^diff.*-\w*N', "don't use 'diff -N'"),
50 50 (r'(^| )wc[^|]*$', "filter wc output"),
51 51 (r'head -c', "don't use 'head -c', use 'dd'"),
52 52 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
53 53 (r'printf.*\\\d\d\d', "don't use 'printf \NNN', use Python"),
54 54 (r'printf.*\\x', "don't use printf \\x, use Python"),
55 55 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
56 56 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
57 57 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
58 58 "use egrep for extended grep syntax"),
59 59 (r'/bin/', "don't use explicit paths for tools"),
60 60 (r'\$PWD', "don't use $PWD, use `pwd`"),
61 61 (r'[^\n]\Z', "no trailing newline"),
62 62 (r'export.*=', "don't export and assign at once"),
63 63 ('^([^"\']|("[^"]*")|(\'[^\']*\'))*\\^', "^ must be quoted"),
64 64 (r'^source\b', "don't use 'source', use '.'"),
65 65 ]
66 66
67 67 testfilters = [
68 68 (r"( *)(#([^\n]*\S)?)", repcomment),
69 69 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
70 70 ]
71 71
72 72 pypats = [
73 73 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
74 74 "tuple parameter unpacking not available in Python 3+"),
75 75 (r'lambda\s*\(.*,.*\)',
76 76 "tuple parameter unpacking not available in Python 3+"),
77 77 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
78 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
78 79 (r'^\s*\t', "don't use tabs"),
79 80 (r'\S;\s*\n', "semicolon"),
80 81 (r'\w,\w', "missing whitespace after ,"),
81 82 (r'\w[+/*\-<>]\w', "missing whitespace in expression"),
82 83 (r'^\s+\w+=\w+[^,)]$', "missing whitespace in assignment"),
83 84 (r'.{85}', "line too long"),
84 85 (r'[^\n]\Z', "no trailing newline"),
85 86 # (r'^\s+[^_ ][^_. ]+_[^_]+\s*=', "don't use underbars in identifiers"),
86 87 # (r'\w*[a-z][A-Z]\w*\s*=', "don't use camelcase in identifiers"),
87 88 (r'^\s*(if|while|def|class|except|try)\s[^[]*:\s*[^\]#\s]+',
88 89 "linebreak after :"),
89 90 (r'class\s[^(]:', "old-style class, use class foo(object)"),
90 91 (r'^\s+del\(', "del isn't a function"),
91 92 (r'^\s+except\(', "except isn't a function"),
92 93 (r',]', "unneeded trailing ',' in list"),
93 94 # (r'class\s[A-Z][^\(]*\((?!Exception)',
94 95 # "don't capitalize non-exception classes"),
95 96 # (r'in range\(', "use xrange"),
96 97 # (r'^\s*print\s+', "avoid using print in core and extensions"),
97 98 (r'[\x80-\xff]', "non-ASCII character literal"),
98 99 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
99 100 (r'^\s*with\s+', "with not available in Python 2.4"),
100 101 (r'(?<!def)\s+(any|all|format)\(',
101 102 "any/all/format not available in Python 2.4"),
102 103 (r'(?<!def)\s+(callable)\(',
103 104 "callable not available in Python 3, use hasattr(f, '__call__')"),
104 105 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
105 106 (r'([\(\[]\s\S)|(\S\s[\)\]])', "gratuitous whitespace in () or []"),
106 107 # (r'\s\s=', "gratuitous whitespace before ="),
107 108 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
108 109 "missing whitespace around operator"),
109 110 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\s',
110 111 "missing whitespace around operator"),
111 112 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
112 113 "missing whitespace around operator"),
113 114 (r'[^+=*!<>&| -](\s=|=\s)[^= ]',
114 115 "wrong whitespace around ="),
115 116 (r'raise Exception', "don't raise generic exceptions"),
116 117 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
117 118 "warning: unwrapped ui message"),
118 119 ]
119 120
120 121 pyfilters = [
121 122 (r"""(?msx)(?P<comment>\#.*?$)|
122 123 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
123 124 (?P<text>(([^\\]|\\.)*?))
124 125 (?P=quote))""", reppython),
125 126 ]
126 127
127 128 cpats = [
128 129 (r'//', "don't use //-style comments"),
129 130 (r'^ ', "don't use spaces to indent"),
130 131 (r'\S\t', "don't use tabs except for indent"),
131 132 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
132 133 (r'.{85}', "line too long"),
133 134 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
134 135 (r'return\(', "return is not a function"),
135 136 (r' ;', "no space before ;"),
136 137 (r'\w+\* \w+', "use int *foo, not int* foo"),
137 138 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
138 139 (r'\S+ (\+\+|--)', "use foo++, not foo ++"),
139 140 (r'\w,\w', "missing whitespace after ,"),
140 141 (r'\w[+/*]\w', "missing whitespace in expression"),
141 142 (r'^#\s+\w', "use #foo, not # foo"),
142 143 (r'[^\n]\Z', "no trailing newline"),
143 144 ]
144 145
145 146 cfilters = [
146 147 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
147 148 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
148 149 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
149 150 (r'(\()([^)]+\))', repcallspaces),
150 151 ]
151 152
152 153 checks = [
153 154 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
154 155 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
155 156 ('c', r'.*\.c$', cfilters, cpats),
156 157 ]
157 158
158 159 class norepeatlogger(object):
159 160 def __init__(self):
160 161 self._lastseen = None
161 162
162 163 def log(self, fname, lineno, line, msg):
163 164 """print error related a to given line of a given file.
164 165
165 166 The faulty line will also be printed but only once in the case
166 167 of multiple errors.
167 168
168 169 :fname: filename
169 170 :lineno: line number
170 171 :line: actual content of the line
171 172 :msg: error message
172 173 """
173 174 msgid = fname, lineno, line
174 175 if msgid != self._lastseen:
175 176 print "%s:%d:" % (fname, lineno)
176 177 print " > %s" % line
177 178 self._lastseen = msgid
178 179 print " " + msg
179 180
180 181 _defaultlogger = norepeatlogger()
181 182
182 183 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False):
183 184 """checks style and portability of a given file
184 185
185 186 :f: filepath
186 187 :logfunc: function used to report error
187 188 logfunc(filename, linenumber, linecontent, errormessage)
188 189 :maxerr: number of error to display before arborting.
189 190 Set to None (default) to report all errors
190 191
191 192 return True if no error is found, False otherwise.
192 193 """
193 194 result = True
194 195 for name, match, filters, pats in checks:
195 196 fc = 0
196 197 if not re.match(match, f):
197 198 continue
198 199 pre = post = open(f).read()
199 200 if "no-" + "check-code" in pre:
200 201 break
201 202 for p, r in filters:
202 203 post = re.sub(p, r, post)
203 204 # print post # uncomment to show filtered version
204 205 z = enumerate(zip(pre.splitlines(), post.splitlines(True)))
205 206 for n, l in z:
206 207 if "check-code" + "-ignore" in l[0]:
207 208 continue
208 209 for p, msg in pats:
209 210 if not warnings and msg.startswith("warning"):
210 211 continue
211 212 if re.search(p, l[1]):
212 213 logfunc(f, n + 1, l[0], msg)
213 214 fc += 1
214 215 result = False
215 216 if maxerr is not None and fc >= maxerr:
216 217 print " (too many errors, giving up)"
217 218 break
218 219 break
219 220 return result
220 221
221 222
222 223 if __name__ == "__main__":
223 224 parser = optparse.OptionParser("%prog [options] [files]")
224 225 parser.add_option("-w", "--warnings", action="store_true",
225 226 help="include warning-level checks")
226 227 parser.add_option("-p", "--per-file", type="int",
227 228 help="max warnings per file")
228 229
229 230 parser.set_defaults(per_file=15, warnings=False)
230 231 (options, args) = parser.parse_args()
231 232
232 233 if len(args) == 0:
233 234 check = glob.glob("*")
234 235 else:
235 236 check = args
236 237
237 238 for f in check:
238 239 checkfile(f, maxerr=options.per_file, warnings=options.warnings)
General Comments 0
You need to be logged in to leave comments. Login now