##// END OF EJS Templates
contrib: widen "direct use of `python`" net again...
Augie Fackler -
r33288:f08a178a default
parent child Browse files
Show More
@@ -1,717 +1,717 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 """style and portability checker for Mercurial
11 11
12 12 when a rule triggers wrong, do one of the following (prefer one from top):
13 13 * do the work-around the rule suggests
14 14 * doublecheck that it is a false match
15 15 * improve the rule pattern
16 16 * add an ignore pattern to the rule (3rd arg) which matches your good line
17 17 (you can append a short comment and match this, like: #re-raises)
18 18 * change the pattern to a warning and list the exception in test-check-code-hg
19 19 * ONLY use no--check-code for skipping entire files from external sources
20 20 """
21 21
22 22 from __future__ import absolute_import, print_function
23 23 import glob
24 24 import keyword
25 25 import optparse
26 26 import os
27 27 import re
28 28 import sys
29 29 if sys.version_info[0] < 3:
30 30 opentext = open
31 31 else:
32 32 def opentext(f):
33 33 return open(f, encoding='ascii')
34 34 try:
35 35 xrange
36 36 except NameError:
37 37 xrange = range
38 38 try:
39 39 import re2
40 40 except ImportError:
41 41 re2 = None
42 42
43 43 def compilere(pat, multiline=False):
44 44 if multiline:
45 45 pat = '(?m)' + pat
46 46 if re2:
47 47 try:
48 48 return re2.compile(pat)
49 49 except re2.error:
50 50 pass
51 51 return re.compile(pat)
52 52
53 53 # check "rules depending on implementation of repquote()" in each
54 54 # patterns (especially pypats), before changing around repquote()
55 55 _repquotefixedmap = {' ': ' ', '\n': '\n', '.': 'p', ':': 'q',
56 56 '%': '%', '\\': 'b', '*': 'A', '+': 'P', '-': 'M'}
57 57 def _repquoteencodechr(i):
58 58 if i > 255:
59 59 return 'u'
60 60 c = chr(i)
61 61 if c in _repquotefixedmap:
62 62 return _repquotefixedmap[c]
63 63 if c.isalpha():
64 64 return 'x'
65 65 if c.isdigit():
66 66 return 'n'
67 67 return 'o'
68 68 _repquotett = ''.join(_repquoteencodechr(i) for i in xrange(256))
69 69
70 70 def repquote(m):
71 71 t = m.group('text')
72 72 t = t.translate(_repquotett)
73 73 return m.group('quote') + t + m.group('quote')
74 74
75 75 def reppython(m):
76 76 comment = m.group('comment')
77 77 if comment:
78 78 l = len(comment.rstrip())
79 79 return "#" * l + comment[l:]
80 80 return repquote(m)
81 81
82 82 def repcomment(m):
83 83 return m.group(1) + "#" * len(m.group(2))
84 84
85 85 def repccomment(m):
86 86 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
87 87 return m.group(1) + t + "*/"
88 88
89 89 def repcallspaces(m):
90 90 t = re.sub(r"\n\s+", "\n", m.group(2))
91 91 return m.group(1) + t
92 92
93 93 def repinclude(m):
94 94 return m.group(1) + "<foo>"
95 95
96 96 def rephere(m):
97 97 t = re.sub(r"\S", "x", m.group(2))
98 98 return m.group(1) + t
99 99
100 100
101 101 testpats = [
102 102 [
103 103 (r'\b(push|pop)d\b', "don't use 'pushd' or 'popd', use 'cd'"),
104 104 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
105 105 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
106 106 (r'(?<!hg )grep.* -a', "don't use 'grep -a', use in-line python"),
107 107 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
108 108 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
109 109 (r'echo -n', "don't use 'echo -n', use printf"),
110 110 (r'(^|\|\s*)\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"),
111 111 (r'head -c', "don't use 'head -c', use 'dd'"),
112 112 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
113 113 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
114 114 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
115 115 (r'printf.*[^\\]\\([1-9]|0\d)', r"don't use 'printf \NNN', use Python"),
116 116 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
117 117 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
118 118 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
119 119 (r'\[[^\]]+==', '[ foo == bar ] is a bashism, use [ foo = bar ] instead'),
120 120 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
121 121 "use egrep for extended grep syntax"),
122 122 (r'/bin/', "don't use explicit paths for tools"),
123 123 (r'[^\n]\Z', "no trailing newline"),
124 124 (r'export .*=', "don't export and assign at once"),
125 125 (r'^source\b', "don't use 'source', use '.'"),
126 126 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
127 127 (r'\bls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
128 128 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
129 129 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
130 130 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
131 131 (r'\[\[\s+[^\]]*\]\]', "don't use '[[ ]]', use '[ ]'"),
132 132 (r'^alias\b.*=', "don't use alias, use a function"),
133 133 (r'if\s*!', "don't use '!' to negate exit status"),
134 134 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
135 135 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
136 136 (r'^( *)\t', "don't use tabs to indent"),
137 137 (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
138 138 "put a backslash-escaped newline after sed 'i' command"),
139 139 (r'^diff *-\w*[uU].*$\n(^ \$ |^$)', "prefix diff -u/-U with cmp"),
140 140 (r'^\s+(if)? diff *-\w*[uU]', "prefix diff -u/-U with cmp"),
141 (r'\spython\s(?!bindings)', "don't use 'python', use '$PYTHON'"),
141 (r'[\s="`\']python\s(?!bindings)', "don't use 'python', use '$PYTHON'"),
142 142 (r'seq ', "don't use 'seq', use $TESTDIR/seq.py"),
143 143 (r'\butil\.Abort\b', "directly use error.Abort"),
144 144 (r'\|&', "don't use |&, use 2>&1"),
145 145 (r'\w = +\w', "only one space after = allowed"),
146 146 (r'\bsed\b.*[^\\]\\n', "don't use 'sed ... \\n', use a \\ and a newline"),
147 147 (r'env.*-u', "don't use 'env -u VAR', use 'unset VAR'"),
148 148 (r'cp.* -r ', "don't use 'cp -r', use 'cp -R'"),
149 149 (r'grep.* -[ABC] ', "don't use grep's context flags"),
150 150 ],
151 151 # warnings
152 152 [
153 153 (r'^function', "don't use 'function', use old style"),
154 154 (r'^diff.*-\w*N', "don't use 'diff -N'"),
155 155 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
156 156 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
157 157 (r'kill (`|\$\()', "don't use kill, use killdaemons.py")
158 158 ]
159 159 ]
160 160
161 161 testfilters = [
162 162 (r"( *)(#([^\n]*\S)?)", repcomment),
163 163 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
164 164 ]
165 165
166 166 winglobmsg = "use (glob) to match Windows paths too"
167 167 uprefix = r"^ \$ "
168 168 utestpats = [
169 169 [
170 170 (r'^(\S.*|| [$>] \S.*)[ \t]\n', "trailing whitespace on non-output"),
171 171 (uprefix + r'.*\|\s*sed[^|>\n]*\n',
172 172 "use regex test output patterns instead of sed"),
173 173 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
174 174 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
175 175 (uprefix + r'.*\|\| echo.*(fail|error)',
176 176 "explicit exit code checks unnecessary"),
177 177 (uprefix + r'set -e', "don't use set -e"),
178 178 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
179 179 (uprefix + r'.*:\.\S*/', "x:.y in a path does not work on msys, rewrite "
180 180 "as x://.y, or see `hg log -k msys` for alternatives", r'-\S+:\.|' #-Rxxx
181 181 '# no-msys'), # in test-pull.t which is skipped on windows
182 182 (r'^ saved backup bundle to \$TESTTMP.*\.hg$', winglobmsg),
183 183 (r'^ changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
184 184 winglobmsg),
185 185 (r'^ pulling from \$TESTTMP/.*[^)]$', winglobmsg,
186 186 '\$TESTTMP/unix-repo$'), # in test-issue1802.t which skipped on windows
187 187 (r'^ reverting (?!subrepo ).*/.*[^)]$', winglobmsg),
188 188 (r'^ cloning subrepo \S+/.*[^)]$', winglobmsg),
189 189 (r'^ pushing to \$TESTTMP/.*[^)]$', winglobmsg),
190 190 (r'^ pushing subrepo \S+/\S+ to.*[^)]$', winglobmsg),
191 191 (r'^ moving \S+/.*[^)]$', winglobmsg),
192 192 (r'^ no changes made to subrepo since.*/.*[^)]$', winglobmsg),
193 193 (r'^ .*: largefile \S+ not available from file:.*/.*[^)]$', winglobmsg),
194 194 (r'^ .*file://\$TESTTMP',
195 195 'write "file:/*/$TESTTMP" + (glob) to match on windows too'),
196 196 (r'^ [^$>].*27\.0\.0\.1',
197 197 'use $LOCALIP not an explicit loopback address'),
198 198 (r'^ [^$>].*\$LOCALIP.*[^)]$',
199 199 'mark $LOCALIP output lines with (glob) to help tests in BSD jails'),
200 200 (r'^ (cat|find): .*: No such file or directory',
201 201 'use test -f to test for file existence'),
202 202 (r'^ diff -[^ -]*p',
203 203 "don't use (external) diff with -p for portability"),
204 204 (r'^ [-+][-+][-+] .* [-+]0000 \(glob\)',
205 205 "glob timezone field in diff output for portability"),
206 206 (r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@',
207 207 "use '@@ -N* +N,n @@ (glob)' style chunk header for portability"),
208 208 (r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@',
209 209 "use '@@ -N,n +N* @@ (glob)' style chunk header for portability"),
210 210 (r'^ @@ -[0-9]+ [+][0-9]+ @@',
211 211 "use '@@ -N* +N* @@ (glob)' style chunk header for portability"),
212 212 (uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff'
213 213 r'( +(-[^ po-]+|--(?!program|option)[^ ]+|[^-][^ ]*))*$',
214 214 "use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)"),
215 215 ],
216 216 # warnings
217 217 [
218 218 (r'^ (?!.*\$LOCALIP)[^*?/\n]* \(glob\)$',
219 219 "glob match with no glob string (?, *, /, and $LOCALIP)"),
220 220 ]
221 221 ]
222 222
223 223 for i in [0, 1]:
224 224 for tp in testpats[i]:
225 225 p = tp[0]
226 226 m = tp[1]
227 227 if p.startswith(r'^'):
228 228 p = r"^ [$>] (%s)" % p[1:]
229 229 else:
230 230 p = r"^ [$>] .*(%s)" % p
231 231 utestpats[i].append((p, m) + tp[2:])
232 232
233 233 utestfilters = [
234 234 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
235 235 (r"( +)(#([^\n]*\S)?)", repcomment),
236 236 ]
237 237
238 238 pypats = [
239 239 [
240 240 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
241 241 "tuple parameter unpacking not available in Python 3+"),
242 242 (r'lambda\s*\(.*,.*\)',
243 243 "tuple parameter unpacking not available in Python 3+"),
244 244 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
245 245 (r'(?<!\.)\breduce\s*\(.*', "reduce is not available in Python 3+"),
246 246 (r'\bdict\(.*=', 'dict() is different in Py2 and 3 and is slower than {}',
247 247 'dict-from-generator'),
248 248 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
249 249 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
250 250 (r'^\s*\t', "don't use tabs"),
251 251 (r'\S;\s*\n', "semicolon"),
252 252 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
253 253 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
254 254 (r'(\w|\)),\w', "missing whitespace after ,"),
255 255 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
256 256 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
257 257 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
258 258 (r'.{81}', "line too long"),
259 259 (r'[^\n]\Z', "no trailing newline"),
260 260 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
261 261 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
262 262 # "don't use underbars in identifiers"),
263 263 (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
264 264 "don't use camelcase in identifiers"),
265 265 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
266 266 "linebreak after :"),
267 267 (r'class\s[^( \n]+:', "old-style class, use class foo(object)",
268 268 r'#.*old-style'),
269 269 (r'class\s[^( \n]+\(\):',
270 270 "class foo() creates old style object, use class foo(object)",
271 271 r'#.*old-style'),
272 272 (r'\b(%s)\(' % '|'.join(k for k in keyword.kwlist
273 273 if k not in ('print', 'exec')),
274 274 "Python keyword is not a function"),
275 275 (r',]', "unneeded trailing ',' in list"),
276 276 # (r'class\s[A-Z][^\(]*\((?!Exception)',
277 277 # "don't capitalize non-exception classes"),
278 278 # (r'in range\(', "use xrange"),
279 279 # (r'^\s*print\s+', "avoid using print in core and extensions"),
280 280 (r'[\x80-\xff]', "non-ASCII character literal"),
281 281 (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"),
282 282 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
283 283 "gratuitous whitespace after Python keyword"),
284 284 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
285 285 # (r'\s\s=', "gratuitous whitespace before ="),
286 286 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
287 287 "missing whitespace around operator"),
288 288 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
289 289 "missing whitespace around operator"),
290 290 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
291 291 "missing whitespace around operator"),
292 292 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
293 293 "wrong whitespace around ="),
294 294 (r'\([^()]*( =[^=]|[^<>!=]= )',
295 295 "no whitespace around = for named parameters"),
296 296 (r'raise Exception', "don't raise generic exceptions"),
297 297 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
298 298 "don't use old-style two-argument raise, use Exception(message)"),
299 299 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
300 300 (r' [=!]=\s+(True|False|None)',
301 301 "comparison with singleton, use 'is' or 'is not' instead"),
302 302 (r'^\s*(while|if) [01]:',
303 303 "use True/False for constant Boolean expression"),
304 304 (r'(?:(?<!def)\s+|\()hasattr\(',
305 305 'hasattr(foo, bar) is broken on py2, use util.safehasattr(foo, bar) '
306 306 'instead', r'#.*hasattr-py3-only'),
307 307 (r'opener\([^)]*\).read\(',
308 308 "use opener.read() instead"),
309 309 (r'opener\([^)]*\).write\(',
310 310 "use opener.write() instead"),
311 311 (r'[\s\(](open|file)\([^)]*\)\.read\(',
312 312 "use util.readfile() instead"),
313 313 (r'[\s\(](open|file)\([^)]*\)\.write\(',
314 314 "use util.writefile() instead"),
315 315 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
316 316 "always assign an opened file to a variable, and close it afterwards"),
317 317 (r'[\s\(](open|file)\([^)]*\)\.',
318 318 "always assign an opened file to a variable, and close it afterwards"),
319 319 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
320 320 (r'\.debug\(\_', "don't mark debug messages for translation"),
321 321 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
322 322 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
323 323 (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
324 324 'legacy exception syntax; use "as" instead of ","'),
325 325 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
326 326 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
327 327 (r'\bdef\s+__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
328 328 (r'os\.path\.join\(.*, *(""|\'\')\)',
329 329 "use pathutil.normasprefix(path) instead of os.path.join(path, '')"),
330 330 (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
331 331 # XXX only catch mutable arguments on the first line of the definition
332 332 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
333 333 (r'\butil\.Abort\b', "directly use error.Abort"),
334 334 (r'^@(\w*\.)?cachefunc', "module-level @cachefunc is risky, please avoid"),
335 335 (r'^import Queue', "don't use Queue, use util.queue + util.empty"),
336 336 (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"),
337 337 (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
338 338 (r'^import SocketServer', "don't use SockerServer, use util.socketserver"),
339 339 (r'^import urlparse', "don't use urlparse, use util.urlreq"),
340 340 (r'^import xmlrpclib', "don't use xmlrpclib, use util.xmlrpclib"),
341 341 (r'^import cPickle', "don't use cPickle, use util.pickle"),
342 342 (r'^import pickle', "don't use pickle, use util.pickle"),
343 343 (r'^import httplib', "don't use httplib, use util.httplib"),
344 344 (r'^import BaseHTTPServer', "use util.httpserver instead"),
345 345 (r'^(from|import) mercurial\.(cext|pure|cffi)',
346 346 "use mercurial.policy.importmod instead"),
347 347 (r'\.next\(\)', "don't use .next(), use next(...)"),
348 348 (r'([a-z]*).revision\(\1\.node\(',
349 349 "don't convert rev to node before passing to revision(nodeorrev)"),
350 350
351 351 # rules depending on implementation of repquote()
352 352 (r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
353 353 'string join across lines with no space'),
354 354 (r'''(?x)ui\.(status|progress|write|note|warn)\(
355 355 [ \t\n#]*
356 356 (?# any strings/comments might precede a string, which
357 357 # contains translatable message)
358 358 ((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
359 359 (?# sequence consisting of below might precede translatable message
360 360 # - formatting string: "% 10s", "%05d", "% -3.2f", "%*s", "%%" ...
361 361 # - escaped character: "\\", "\n", "\0" ...
362 362 # - character other than '%', 'b' as '\', and 'x' as alphabet)
363 363 (['"]|\'\'\'|""")
364 364 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
365 365 (?# this regexp can't use [^...] style,
366 366 # because _preparepats forcibly adds "\n" into [^...],
367 367 # even though this regexp wants match it against "\n")''',
368 368 "missing _() in ui message (use () to hide false-positives)"),
369 369 ],
370 370 # warnings
371 371 [
372 372 # rules depending on implementation of repquote()
373 373 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
374 374 ]
375 375 ]
376 376
377 377 pyfilters = [
378 378 (r"""(?msx)(?P<comment>\#.*?$)|
379 379 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
380 380 (?P<text>(([^\\]|\\.)*?))
381 381 (?P=quote))""", reppython),
382 382 ]
383 383
384 384 # extension non-filter patterns
385 385 pyextnfpats = [
386 386 [(r'^"""\n?[A-Z]', "don't capitalize docstring title")],
387 387 # warnings
388 388 [],
389 389 ]
390 390
391 391 txtfilters = []
392 392
393 393 txtpats = [
394 394 [
395 395 ('\s$', 'trailing whitespace'),
396 396 ('.. note::[ \n][^\n]', 'add two newlines after note::')
397 397 ],
398 398 []
399 399 ]
400 400
401 401 cpats = [
402 402 [
403 403 (r'//', "don't use //-style comments"),
404 404 (r'^ ', "don't use spaces to indent"),
405 405 (r'\S\t', "don't use tabs except for indent"),
406 406 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
407 407 (r'.{81}', "line too long"),
408 408 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
409 409 (r'return\(', "return is not a function"),
410 410 (r' ;', "no space before ;"),
411 411 (r'[^;] \)', "no space before )"),
412 412 (r'[)][{]', "space between ) and {"),
413 413 (r'\w+\* \w+', "use int *foo, not int* foo"),
414 414 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
415 415 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
416 416 (r'\w,\w', "missing whitespace after ,"),
417 417 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
418 418 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
419 419 (r'^#\s+\w', "use #foo, not # foo"),
420 420 (r'[^\n]\Z', "no trailing newline"),
421 421 (r'^\s*#import\b', "use only #include in standard C code"),
422 422 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
423 423 (r'strcat\(', "don't use strcat"),
424 424
425 425 # rules depending on implementation of repquote()
426 426 ],
427 427 # warnings
428 428 [
429 429 # rules depending on implementation of repquote()
430 430 ]
431 431 ]
432 432
433 433 cfilters = [
434 434 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
435 435 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
436 436 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
437 437 (r'(\()([^)]+\))', repcallspaces),
438 438 ]
439 439
440 440 inutilpats = [
441 441 [
442 442 (r'\bui\.', "don't use ui in util"),
443 443 ],
444 444 # warnings
445 445 []
446 446 ]
447 447
448 448 inrevlogpats = [
449 449 [
450 450 (r'\brepo\.', "don't use repo in revlog"),
451 451 ],
452 452 # warnings
453 453 []
454 454 ]
455 455
456 456 webtemplatefilters = []
457 457
458 458 webtemplatepats = [
459 459 [],
460 460 [
461 461 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
462 462 'follow desc keyword with either firstline or websub'),
463 463 ]
464 464 ]
465 465
466 466 allfilesfilters = []
467 467
468 468 allfilespats = [
469 469 [
470 470 (r'(http|https)://[a-zA-Z0-9./]*selenic.com/',
471 471 'use mercurial-scm.org domain URL'),
472 472 (r'mercurial@selenic\.com',
473 473 'use mercurial-scm.org domain for mercurial ML address'),
474 474 (r'mercurial-devel@selenic\.com',
475 475 'use mercurial-scm.org domain for mercurial-devel ML address'),
476 476 ],
477 477 # warnings
478 478 [],
479 479 ]
480 480
481 481 py3pats = [
482 482 [
483 483 (r'os\.environ', "use encoding.environ instead (py3)", r'#.*re-exports'),
484 484 (r'os\.name', "use pycompat.osname instead (py3)"),
485 485 (r'os\.getcwd', "use pycompat.getcwd instead (py3)"),
486 486 (r'os\.sep', "use pycompat.ossep instead (py3)"),
487 487 (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"),
488 488 (r'os\.altsep', "use pycompat.osaltsep instead (py3)"),
489 489 (r'sys\.platform', "use pycompat.sysplatform instead (py3)"),
490 490 (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"),
491 491 (r'os\.getenv', "use encoding.environ.get instead"),
492 492 (r'os\.setenv', "modifying the environ dict is not preferred"),
493 493 ],
494 494 # warnings
495 495 [],
496 496 ]
497 497
498 498 checks = [
499 499 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
500 500 ('python', r'.*hgext.*\.py$', '', [], pyextnfpats),
501 501 ('python 3', r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
502 502 '', pyfilters, py3pats),
503 503 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
504 504 ('c', r'.*\.[ch]$', '', cfilters, cpats),
505 505 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
506 506 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
507 507 pyfilters, inrevlogpats),
508 508 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
509 509 inutilpats),
510 510 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
511 511 ('web template', r'mercurial/templates/.*\.tmpl', '',
512 512 webtemplatefilters, webtemplatepats),
513 513 ('all except for .po', r'.*(?<!\.po)$', '',
514 514 allfilesfilters, allfilespats),
515 515 ]
516 516
517 517 def _preparepats():
518 518 for c in checks:
519 519 failandwarn = c[-1]
520 520 for pats in failandwarn:
521 521 for i, pseq in enumerate(pats):
522 522 # fix-up regexes for multi-line searches
523 523 p = pseq[0]
524 524 # \s doesn't match \n
525 525 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
526 526 # [^...] doesn't match newline
527 527 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
528 528
529 529 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
530 530 filters = c[3]
531 531 for i, flt in enumerate(filters):
532 532 filters[i] = re.compile(flt[0]), flt[1]
533 533
534 534 class norepeatlogger(object):
535 535 def __init__(self):
536 536 self._lastseen = None
537 537
538 538 def log(self, fname, lineno, line, msg, blame):
539 539 """print error related a to given line of a given file.
540 540
541 541 The faulty line will also be printed but only once in the case
542 542 of multiple errors.
543 543
544 544 :fname: filename
545 545 :lineno: line number
546 546 :line: actual content of the line
547 547 :msg: error message
548 548 """
549 549 msgid = fname, lineno, line
550 550 if msgid != self._lastseen:
551 551 if blame:
552 552 print("%s:%d (%s):" % (fname, lineno, blame))
553 553 else:
554 554 print("%s:%d:" % (fname, lineno))
555 555 print(" > %s" % line)
556 556 self._lastseen = msgid
557 557 print(" " + msg)
558 558
559 559 _defaultlogger = norepeatlogger()
560 560
561 561 def getblame(f):
562 562 lines = []
563 563 for l in os.popen('hg annotate -un %s' % f):
564 564 start, line = l.split(':', 1)
565 565 user, rev = start.split()
566 566 lines.append((line[1:-1], user, rev))
567 567 return lines
568 568
569 569 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
570 570 blame=False, debug=False, lineno=True):
571 571 """checks style and portability of a given file
572 572
573 573 :f: filepath
574 574 :logfunc: function used to report error
575 575 logfunc(filename, linenumber, linecontent, errormessage)
576 576 :maxerr: number of error to display before aborting.
577 577 Set to false (default) to report all errors
578 578
579 579 return True if no error is found, False otherwise.
580 580 """
581 581 blamecache = None
582 582 result = True
583 583
584 584 try:
585 585 with opentext(f) as fp:
586 586 try:
587 587 pre = post = fp.read()
588 588 except UnicodeDecodeError as e:
589 589 print("%s while reading %s" % (e, f))
590 590 return result
591 591 except IOError as e:
592 592 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
593 593 return result
594 594
595 595 for name, match, magic, filters, pats in checks:
596 596 post = pre # discard filtering result of previous check
597 597 if debug:
598 598 print(name, f)
599 599 fc = 0
600 600 if not (re.match(match, f) or (magic and re.search(magic, pre))):
601 601 if debug:
602 602 print("Skipping %s for %s it doesn't match %s" % (
603 603 name, match, f))
604 604 continue
605 605 if "no-" "check-code" in pre:
606 606 # If you're looking at this line, it's because a file has:
607 607 # no- check- code
608 608 # but the reason to output skipping is to make life for
609 609 # tests easier. So, instead of writing it with a normal
610 610 # spelling, we write it with the expected spelling from
611 611 # tests/test-check-code.t
612 612 print("Skipping %s it has no-che?k-code (glob)" % f)
613 613 return "Skip" # skip checking this file
614 614 for p, r in filters:
615 615 post = re.sub(p, r, post)
616 616 nerrs = len(pats[0]) # nerr elements are errors
617 617 if warnings:
618 618 pats = pats[0] + pats[1]
619 619 else:
620 620 pats = pats[0]
621 621 # print post # uncomment to show filtered version
622 622
623 623 if debug:
624 624 print("Checking %s for %s" % (name, f))
625 625
626 626 prelines = None
627 627 errors = []
628 628 for i, pat in enumerate(pats):
629 629 if len(pat) == 3:
630 630 p, msg, ignore = pat
631 631 else:
632 632 p, msg = pat
633 633 ignore = None
634 634 if i >= nerrs:
635 635 msg = "warning: " + msg
636 636
637 637 pos = 0
638 638 n = 0
639 639 for m in p.finditer(post):
640 640 if prelines is None:
641 641 prelines = pre.splitlines()
642 642 postlines = post.splitlines(True)
643 643
644 644 start = m.start()
645 645 while n < len(postlines):
646 646 step = len(postlines[n])
647 647 if pos + step > start:
648 648 break
649 649 pos += step
650 650 n += 1
651 651 l = prelines[n]
652 652
653 653 if ignore and re.search(ignore, l, re.MULTILINE):
654 654 if debug:
655 655 print("Skipping %s for %s:%s (ignore pattern)" % (
656 656 name, f, n))
657 657 continue
658 658 bd = ""
659 659 if blame:
660 660 bd = 'working directory'
661 661 if not blamecache:
662 662 blamecache = getblame(f)
663 663 if n < len(blamecache):
664 664 bl, bu, br = blamecache[n]
665 665 if bl == l:
666 666 bd = '%s@%s' % (bu, br)
667 667
668 668 errors.append((f, lineno and n + 1, l, msg, bd))
669 669 result = False
670 670
671 671 errors.sort()
672 672 for e in errors:
673 673 logfunc(*e)
674 674 fc += 1
675 675 if maxerr and fc >= maxerr:
676 676 print(" (too many errors, giving up)")
677 677 break
678 678
679 679 return result
680 680
681 681 def main():
682 682 parser = optparse.OptionParser("%prog [options] [files | -]")
683 683 parser.add_option("-w", "--warnings", action="store_true",
684 684 help="include warning-level checks")
685 685 parser.add_option("-p", "--per-file", type="int",
686 686 help="max warnings per file")
687 687 parser.add_option("-b", "--blame", action="store_true",
688 688 help="use annotate to generate blame info")
689 689 parser.add_option("", "--debug", action="store_true",
690 690 help="show debug information")
691 691 parser.add_option("", "--nolineno", action="store_false",
692 692 dest='lineno', help="don't show line numbers")
693 693
694 694 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
695 695 lineno=True)
696 696 (options, args) = parser.parse_args()
697 697
698 698 if len(args) == 0:
699 699 check = glob.glob("*")
700 700 elif args == ['-']:
701 701 # read file list from stdin
702 702 check = sys.stdin.read().splitlines()
703 703 else:
704 704 check = args
705 705
706 706 _preparepats()
707 707
708 708 ret = 0
709 709 for f in check:
710 710 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
711 711 blame=options.blame, debug=options.debug,
712 712 lineno=options.lineno):
713 713 ret = 1
714 714 return ret
715 715
716 716 if __name__ == "__main__":
717 717 sys.exit(main())
General Comments 0
You need to be logged in to leave comments. Login now