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