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