##// END OF EJS Templates
check-code: added a check for calls to the builtin cmp function
Renato Cunha -
r11764:16723af5 default
parent child Browse files
Show More
@@ -1,264 +1,265 b''
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, os
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 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
77 78 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
78 79 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
79 80 (r'^\s*\t', "don't use tabs"),
80 81 (r'\S;\s*\n', "semicolon"),
81 82 (r'\w,\w', "missing whitespace after ,"),
82 83 (r'\w[+/*\-<>]\w', "missing whitespace in expression"),
83 84 (r'^\s+\w+=\w+[^,)]$', "missing whitespace in assignment"),
84 85 (r'.{85}', "line too long"),
85 86 (r'.{81}', "warning: line over 80 characters"),
86 87 (r'[^\n]\Z', "no trailing newline"),
87 88 # (r'^\s+[^_ ][^_. ]+_[^_]+\s*=', "don't use underbars in identifiers"),
88 89 # (r'\w*[a-z][A-Z]\w*\s*=', "don't use camelcase in identifiers"),
89 90 (r'^\s*(if|while|def|class|except|try)\s[^[]*:\s*[^\]#\s]+',
90 91 "linebreak after :"),
91 92 (r'class\s[^(]:', "old-style class, use class foo(object)"),
92 93 (r'^\s+del\(', "del isn't a function"),
93 94 (r'^\s+except\(', "except isn't a function"),
94 95 (r',]', "unneeded trailing ',' in list"),
95 96 # (r'class\s[A-Z][^\(]*\((?!Exception)',
96 97 # "don't capitalize non-exception classes"),
97 98 # (r'in range\(', "use xrange"),
98 99 # (r'^\s*print\s+', "avoid using print in core and extensions"),
99 100 (r'[\x80-\xff]', "non-ASCII character literal"),
100 101 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
101 102 (r'^\s*with\s+', "with not available in Python 2.4"),
102 103 (r'(?<!def)\s+(any|all|format)\(',
103 104 "any/all/format not available in Python 2.4"),
104 105 (r'(?<!def)\s+(callable)\(',
105 106 "callable not available in Python 3, use hasattr(f, '__call__')"),
106 107 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
107 108 (r'([\(\[]\s\S)|(\S\s[\)\]])', "gratuitous whitespace in () or []"),
108 109 # (r'\s\s=', "gratuitous whitespace before ="),
109 110 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
110 111 "missing whitespace around operator"),
111 112 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\s',
112 113 "missing whitespace around operator"),
113 114 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
114 115 "missing whitespace around operator"),
115 116 (r'[^+=*!<>&| -](\s=|=\s)[^= ]',
116 117 "wrong whitespace around ="),
117 118 (r'raise Exception', "don't raise generic exceptions"),
118 119 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
119 120 "warning: unwrapped ui message"),
120 121 ]
121 122
122 123 pyfilters = [
123 124 (r"""(?msx)(?P<comment>\#.*?$)|
124 125 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
125 126 (?P<text>(([^\\]|\\.)*?))
126 127 (?P=quote))""", reppython),
127 128 ]
128 129
129 130 cpats = [
130 131 (r'//', "don't use //-style comments"),
131 132 (r'^ ', "don't use spaces to indent"),
132 133 (r'\S\t', "don't use tabs except for indent"),
133 134 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
134 135 (r'.{85}', "line too long"),
135 136 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
136 137 (r'return\(', "return is not a function"),
137 138 (r' ;', "no space before ;"),
138 139 (r'\w+\* \w+', "use int *foo, not int* foo"),
139 140 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
140 141 (r'\S+ (\+\+|--)', "use foo++, not foo ++"),
141 142 (r'\w,\w', "missing whitespace after ,"),
142 143 (r'\w[+/*]\w', "missing whitespace in expression"),
143 144 (r'^#\s+\w', "use #foo, not # foo"),
144 145 (r'[^\n]\Z', "no trailing newline"),
145 146 ]
146 147
147 148 cfilters = [
148 149 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
149 150 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
150 151 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
151 152 (r'(\()([^)]+\))', repcallspaces),
152 153 ]
153 154
154 155 checks = [
155 156 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
156 157 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
157 158 ('c', r'.*\.c$', cfilters, cpats),
158 159 ]
159 160
160 161 class norepeatlogger(object):
161 162 def __init__(self):
162 163 self._lastseen = None
163 164
164 165 def log(self, fname, lineno, line, msg, blame):
165 166 """print error related a to given line of a given file.
166 167
167 168 The faulty line will also be printed but only once in the case
168 169 of multiple errors.
169 170
170 171 :fname: filename
171 172 :lineno: line number
172 173 :line: actual content of the line
173 174 :msg: error message
174 175 """
175 176 msgid = fname, lineno, line
176 177 if msgid != self._lastseen:
177 178 if blame:
178 179 print "%s:%d (%s):" % (fname, lineno, blame)
179 180 else:
180 181 print "%s:%d:" % (fname, lineno)
181 182 print " > %s" % line
182 183 self._lastseen = msgid
183 184 print " " + msg
184 185
185 186 _defaultlogger = norepeatlogger()
186 187
187 188 def getblame(f):
188 189 lines = []
189 190 for l in os.popen('hg annotate -un %s' % f):
190 191 start, line = l.split(':', 1)
191 192 user, rev = start.split()
192 193 lines.append((line[1:-1], user, rev))
193 194 return lines
194 195
195 196 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
196 197 blame=False):
197 198 """checks style and portability of a given file
198 199
199 200 :f: filepath
200 201 :logfunc: function used to report error
201 202 logfunc(filename, linenumber, linecontent, errormessage)
202 203 :maxerr: number of error to display before arborting.
203 204 Set to None (default) to report all errors
204 205
205 206 return True if no error is found, False otherwise.
206 207 """
207 208 blamecache = None
208 209 result = True
209 210 for name, match, filters, pats in checks:
210 211 fc = 0
211 212 if not re.match(match, f):
212 213 continue
213 214 pre = post = open(f).read()
214 215 if "no-" + "check-code" in pre:
215 216 break
216 217 for p, r in filters:
217 218 post = re.sub(p, r, post)
218 219 # print post # uncomment to show filtered version
219 220 z = enumerate(zip(pre.splitlines(), post.splitlines(True)))
220 221 for n, l in z:
221 222 if "check-code" + "-ignore" in l[0]:
222 223 continue
223 224 for p, msg in pats:
224 225 if not warnings and msg.startswith("warning"):
225 226 continue
226 227 if re.search(p, l[1]):
227 228 bd = ""
228 229 if blame:
229 230 bd = 'working directory'
230 231 if not blamecache:
231 232 blamecache = getblame(f)
232 233 if n < len(blamecache):
233 234 bl, bu, br = blamecache[n]
234 235 if bl == l[0]:
235 236 bd = '%s@%s' % (bu, br)
236 237 logfunc(f, n + 1, l[0], msg, bd)
237 238 fc += 1
238 239 result = False
239 240 if maxerr is not None and fc >= maxerr:
240 241 print " (too many errors, giving up)"
241 242 break
242 243 break
243 244 return result
244 245
245 246 if __name__ == "__main__":
246 247 parser = optparse.OptionParser("%prog [options] [files]")
247 248 parser.add_option("-w", "--warnings", action="store_true",
248 249 help="include warning-level checks")
249 250 parser.add_option("-p", "--per-file", type="int",
250 251 help="max warnings per file")
251 252 parser.add_option("-b", "--blame", action="store_true",
252 253 help="use annotate to generate blame info")
253 254
254 255 parser.set_defaults(per_file=15, warnings=False, blame=False)
255 256 (options, args) = parser.parse_args()
256 257
257 258 if len(args) == 0:
258 259 check = glob.glob("*")
259 260 else:
260 261 check = args
261 262
262 263 for f in check:
263 264 checkfile(f, maxerr=options.per_file, warnings=options.warnings,
264 265 blame=options.blame)
General Comments 0
You need to be logged in to leave comments. Login now