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