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