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