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