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