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