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