##// END OF EJS Templates
check-code: stop forbidding return code result...
marmoute -
r48293:9b1710c5 default
parent child Browse files
Show More
@@ -1,1109 +1,1108 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 #
2 #
3 # check-code - a style and portability checker for Mercurial
3 # check-code - a style and portability checker for Mercurial
4 #
4 #
5 # Copyright 2010 Olivia Mackall <olivia@selenic.com>
5 # Copyright 2010 Olivia Mackall <olivia@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """style and portability checker for Mercurial
10 """style and portability checker for Mercurial
11
11
12 when a rule triggers wrong, do one of the following (prefer one from top):
12 when a rule triggers wrong, do one of the following (prefer one from top):
13 * do the work-around the rule suggests
13 * do the work-around the rule suggests
14 * doublecheck that it is a false match
14 * doublecheck that it is a false match
15 * improve the rule pattern
15 * improve the rule pattern
16 * add an ignore pattern to the rule (3rd arg) which matches your good line
16 * add an ignore pattern to the rule (3rd arg) which matches your good line
17 (you can append a short comment and match this, like: #re-raises)
17 (you can append a short comment and match this, like: #re-raises)
18 * change the pattern to a warning and list the exception in test-check-code-hg
18 * change the pattern to a warning and list the exception in test-check-code-hg
19 * ONLY use no--check-code for skipping entire files from external sources
19 * ONLY use no--check-code for skipping entire files from external sources
20 """
20 """
21
21
22 from __future__ import absolute_import, print_function
22 from __future__ import absolute_import, print_function
23 import glob
23 import glob
24 import keyword
24 import keyword
25 import optparse
25 import optparse
26 import os
26 import os
27 import re
27 import re
28 import sys
28 import sys
29
29
30 if sys.version_info[0] < 3:
30 if sys.version_info[0] < 3:
31 opentext = open
31 opentext = open
32 else:
32 else:
33
33
34 def opentext(f):
34 def opentext(f):
35 return open(f, encoding='latin1')
35 return open(f, encoding='latin1')
36
36
37
37
38 try:
38 try:
39 xrange
39 xrange
40 except NameError:
40 except NameError:
41 xrange = range
41 xrange = range
42 try:
42 try:
43 import re2
43 import re2
44 except ImportError:
44 except ImportError:
45 re2 = None
45 re2 = None
46
46
47 import testparseutil
47 import testparseutil
48
48
49
49
50 def compilere(pat, multiline=False):
50 def compilere(pat, multiline=False):
51 if multiline:
51 if multiline:
52 pat = '(?m)' + pat
52 pat = '(?m)' + pat
53 if re2:
53 if re2:
54 try:
54 try:
55 return re2.compile(pat)
55 return re2.compile(pat)
56 except re2.error:
56 except re2.error:
57 pass
57 pass
58 return re.compile(pat)
58 return re.compile(pat)
59
59
60
60
61 # check "rules depending on implementation of repquote()" in each
61 # check "rules depending on implementation of repquote()" in each
62 # patterns (especially pypats), before changing around repquote()
62 # patterns (especially pypats), before changing around repquote()
63 _repquotefixedmap = {
63 _repquotefixedmap = {
64 ' ': ' ',
64 ' ': ' ',
65 '\n': '\n',
65 '\n': '\n',
66 '.': 'p',
66 '.': 'p',
67 ':': 'q',
67 ':': 'q',
68 '%': '%',
68 '%': '%',
69 '\\': 'b',
69 '\\': 'b',
70 '*': 'A',
70 '*': 'A',
71 '+': 'P',
71 '+': 'P',
72 '-': 'M',
72 '-': 'M',
73 }
73 }
74
74
75
75
76 def _repquoteencodechr(i):
76 def _repquoteencodechr(i):
77 if i > 255:
77 if i > 255:
78 return 'u'
78 return 'u'
79 c = chr(i)
79 c = chr(i)
80 if c in _repquotefixedmap:
80 if c in _repquotefixedmap:
81 return _repquotefixedmap[c]
81 return _repquotefixedmap[c]
82 if c.isalpha():
82 if c.isalpha():
83 return 'x'
83 return 'x'
84 if c.isdigit():
84 if c.isdigit():
85 return 'n'
85 return 'n'
86 return 'o'
86 return 'o'
87
87
88
88
89 _repquotett = ''.join(_repquoteencodechr(i) for i in xrange(256))
89 _repquotett = ''.join(_repquoteencodechr(i) for i in xrange(256))
90
90
91
91
92 def repquote(m):
92 def repquote(m):
93 t = m.group('text')
93 t = m.group('text')
94 t = t.translate(_repquotett)
94 t = t.translate(_repquotett)
95 return m.group('quote') + t + m.group('quote')
95 return m.group('quote') + t + m.group('quote')
96
96
97
97
98 def reppython(m):
98 def reppython(m):
99 comment = m.group('comment')
99 comment = m.group('comment')
100 if comment:
100 if comment:
101 l = len(comment.rstrip())
101 l = len(comment.rstrip())
102 return "#" * l + comment[l:]
102 return "#" * l + comment[l:]
103 return repquote(m)
103 return repquote(m)
104
104
105
105
106 def repcomment(m):
106 def repcomment(m):
107 return m.group(1) + "#" * len(m.group(2))
107 return m.group(1) + "#" * len(m.group(2))
108
108
109
109
110 def repccomment(m):
110 def repccomment(m):
111 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
111 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
112 return m.group(1) + t + "*/"
112 return m.group(1) + t + "*/"
113
113
114
114
115 def repcallspaces(m):
115 def repcallspaces(m):
116 t = re.sub(r"\n\s+", "\n", m.group(2))
116 t = re.sub(r"\n\s+", "\n", m.group(2))
117 return m.group(1) + t
117 return m.group(1) + t
118
118
119
119
120 def repinclude(m):
120 def repinclude(m):
121 return m.group(1) + "<foo>"
121 return m.group(1) + "<foo>"
122
122
123
123
124 def rephere(m):
124 def rephere(m):
125 t = re.sub(r"\S", "x", m.group(2))
125 t = re.sub(r"\S", "x", m.group(2))
126 return m.group(1) + t
126 return m.group(1) + t
127
127
128
128
129 testpats = [
129 testpats = [
130 [
130 [
131 (r'\b(push|pop)d\b', "don't use 'pushd' or 'popd', use 'cd'"),
131 (r'\b(push|pop)d\b', "don't use 'pushd' or 'popd', use 'cd'"),
132 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
132 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
133 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
133 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
134 (r'(?<!hg )grep.* -a', "don't use 'grep -a', use in-line python"),
134 (r'(?<!hg )grep.* -a', "don't use 'grep -a', use in-line python"),
135 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
135 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
136 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
136 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
137 (r'echo -n', "don't use 'echo -n', use printf"),
137 (r'echo -n', "don't use 'echo -n', use printf"),
138 (r'(^|\|\s*)\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"),
138 (r'(^|\|\s*)\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"),
139 (r'head -c', "don't use 'head -c', use 'dd'"),
139 (r'head -c', "don't use 'head -c', use 'dd'"),
140 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
140 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
141 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
141 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
142 (r'\bls\b.*-\w*R', "don't use 'ls -R', use 'find'"),
142 (r'\bls\b.*-\w*R', "don't use 'ls -R', use 'find'"),
143 (r'printf.*[^\\]\\([1-9]|0\d)', r"don't use 'printf \NNN', use Python"),
143 (r'printf.*[^\\]\\([1-9]|0\d)', r"don't use 'printf \NNN', use Python"),
144 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
144 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
145 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
145 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
146 (
146 (
147 r'\[[^\]]+==',
147 r'\[[^\]]+==',
148 '[ foo == bar ] is a bashism, use [ foo = bar ] instead',
148 '[ foo == bar ] is a bashism, use [ foo = bar ] instead',
149 ),
149 ),
150 (
150 (
151 r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
151 r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
152 "use egrep for extended grep syntax",
152 "use egrep for extended grep syntax",
153 ),
153 ),
154 (r'(^|\|\s*)e?grep .*\\S', "don't use \\S in regular expression"),
154 (r'(^|\|\s*)e?grep .*\\S', "don't use \\S in regular expression"),
155 (r'(?<!!)/bin/', "don't use explicit paths for tools"),
155 (r'(?<!!)/bin/', "don't use explicit paths for tools"),
156 (r'#!.*/bash', "don't use bash in shebang, use sh"),
156 (r'#!.*/bash', "don't use bash in shebang, use sh"),
157 (r'[^\n]\Z', "no trailing newline"),
157 (r'[^\n]\Z', "no trailing newline"),
158 (r'export .*=', "don't export and assign at once"),
158 (r'export .*=', "don't export and assign at once"),
159 (r'^source\b', "don't use 'source', use '.'"),
159 (r'^source\b', "don't use 'source', use '.'"),
160 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
160 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
161 (r'\bls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
161 (r'\bls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
162 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
162 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
163 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
163 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
164 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
164 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
165 (r'\[\[\s+[^\]]*\]\]', "don't use '[[ ]]', use '[ ]'"),
165 (r'\[\[\s+[^\]]*\]\]', "don't use '[[ ]]', use '[ ]'"),
166 (r'^alias\b.*=', "don't use alias, use a function"),
166 (r'^alias\b.*=', "don't use alias, use a function"),
167 (r'if\s*!', "don't use '!' to negate exit status"),
167 (r'if\s*!', "don't use '!' to negate exit status"),
168 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
168 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
169 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
169 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
170 (
170 (
171 r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
171 r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
172 "put a backslash-escaped newline after sed 'i' command",
172 "put a backslash-escaped newline after sed 'i' command",
173 ),
173 ),
174 (r'^diff *-\w*[uU].*$\n(^ \$ |^$)', "prefix diff -u/-U with cmp"),
174 (r'^diff *-\w*[uU].*$\n(^ \$ |^$)', "prefix diff -u/-U with cmp"),
175 (r'^\s+(if)? diff *-\w*[uU]', "prefix diff -u/-U with cmp"),
175 (r'^\s+(if)? diff *-\w*[uU]', "prefix diff -u/-U with cmp"),
176 (r'[\s="`\']python\s(?!bindings)', "don't use 'python', use '$PYTHON'"),
176 (r'[\s="`\']python\s(?!bindings)', "don't use 'python', use '$PYTHON'"),
177 (r'seq ', "don't use 'seq', use $TESTDIR/seq.py"),
177 (r'seq ', "don't use 'seq', use $TESTDIR/seq.py"),
178 (r'\butil\.Abort\b', "directly use error.Abort"),
178 (r'\butil\.Abort\b', "directly use error.Abort"),
179 (r'\|&', "don't use |&, use 2>&1"),
179 (r'\|&', "don't use |&, use 2>&1"),
180 (r'\w = +\w', "only one space after = allowed"),
180 (r'\w = +\w', "only one space after = allowed"),
181 (
181 (
182 r'\bsed\b.*[^\\]\\n',
182 r'\bsed\b.*[^\\]\\n',
183 "don't use 'sed ... \\n', use a \\ and a newline",
183 "don't use 'sed ... \\n', use a \\ and a newline",
184 ),
184 ),
185 (r'env.*-u', "don't use 'env -u VAR', use 'unset VAR'"),
185 (r'env.*-u', "don't use 'env -u VAR', use 'unset VAR'"),
186 (r'cp.* -r ', "don't use 'cp -r', use 'cp -R'"),
186 (r'cp.* -r ', "don't use 'cp -r', use 'cp -R'"),
187 (r'grep.* -[ABC]', "don't use grep's context flags"),
187 (r'grep.* -[ABC]', "don't use grep's context flags"),
188 (
188 (
189 r'find.*-printf',
189 r'find.*-printf',
190 "don't use 'find -printf', it doesn't exist on BSD find(1)",
190 "don't use 'find -printf', it doesn't exist on BSD find(1)",
191 ),
191 ),
192 (r'\$RANDOM ', "don't use bash-only $RANDOM to generate random values"),
192 (r'\$RANDOM ', "don't use bash-only $RANDOM to generate random values"),
193 ],
193 ],
194 # warnings
194 # warnings
195 [
195 [
196 (r'^function', "don't use 'function', use old style"),
196 (r'^function', "don't use 'function', use old style"),
197 (r'^diff.*-\w*N', "don't use 'diff -N'"),
197 (r'^diff.*-\w*N', "don't use 'diff -N'"),
198 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
198 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
199 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
199 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
200 (r'kill (`|\$\()', "don't use kill, use killdaemons.py"),
200 (r'kill (`|\$\()', "don't use kill, use killdaemons.py"),
201 ],
201 ],
202 ]
202 ]
203
203
204 testfilters = [
204 testfilters = [
205 (r"( *)(#([^!][^\n]*\S)?)", repcomment),
205 (r"( *)(#([^!][^\n]*\S)?)", repcomment),
206 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
206 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
207 ]
207 ]
208
208
209 uprefix = r"^ \$ "
209 uprefix = r"^ \$ "
210 utestpats = [
210 utestpats = [
211 [
211 [
212 (r'^(\S.*|| [$>] \S.*)[ \t]\n', "trailing whitespace on non-output"),
212 (r'^(\S.*|| [$>] \S.*)[ \t]\n', "trailing whitespace on non-output"),
213 (
213 (
214 uprefix + r'.*\|\s*sed[^|>\n]*\n',
214 uprefix + r'.*\|\s*sed[^|>\n]*\n',
215 "use regex test output patterns instead of sed",
215 "use regex test output patterns instead of sed",
216 ),
216 ),
217 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
217 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
218 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
219 (
218 (
220 uprefix + r'.*\|\| echo.*(fail|error)',
219 uprefix + r'.*\|\| echo.*(fail|error)',
221 "explicit exit code checks unnecessary",
220 "explicit exit code checks unnecessary",
222 ),
221 ),
223 (uprefix + r'set -e', "don't use set -e"),
222 (uprefix + r'set -e', "don't use set -e"),
224 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
223 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
225 (
224 (
226 uprefix + r'.*:\.\S*/',
225 uprefix + r'.*:\.\S*/',
227 "x:.y in a path does not work on msys, rewrite "
226 "x:.y in a path does not work on msys, rewrite "
228 "as x://.y, or see `hg log -k msys` for alternatives",
227 "as x://.y, or see `hg log -k msys` for alternatives",
229 r'-\S+:\.|' '# no-msys', # -Rxxx
228 r'-\S+:\.|' '# no-msys', # -Rxxx
230 ), # in test-pull.t which is skipped on windows
229 ), # in test-pull.t which is skipped on windows
231 (
230 (
232 r'^ [^$>].*27\.0\.0\.1',
231 r'^ [^$>].*27\.0\.0\.1',
233 'use $LOCALIP not an explicit loopback address',
232 'use $LOCALIP not an explicit loopback address',
234 ),
233 ),
235 (
234 (
236 r'^ (?![>$] ).*\$LOCALIP.*[^)]$',
235 r'^ (?![>$] ).*\$LOCALIP.*[^)]$',
237 'mark $LOCALIP output lines with (glob) to help tests in BSD jails',
236 'mark $LOCALIP output lines with (glob) to help tests in BSD jails',
238 ),
237 ),
239 (
238 (
240 r'^ (cat|find): .*: \$ENOENT\$',
239 r'^ (cat|find): .*: \$ENOENT\$',
241 'use test -f to test for file existence',
240 'use test -f to test for file existence',
242 ),
241 ),
243 (
242 (
244 r'^ diff -[^ -]*p',
243 r'^ diff -[^ -]*p',
245 "don't use (external) diff with -p for portability",
244 "don't use (external) diff with -p for portability",
246 ),
245 ),
247 (r' readlink ', 'use readlink.py instead of readlink'),
246 (r' readlink ', 'use readlink.py instead of readlink'),
248 (
247 (
249 r'^ [-+][-+][-+] .* [-+]0000 \(glob\)',
248 r'^ [-+][-+][-+] .* [-+]0000 \(glob\)',
250 "glob timezone field in diff output for portability",
249 "glob timezone field in diff output for portability",
251 ),
250 ),
252 (
251 (
253 r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@',
252 r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@',
254 "use '@@ -N* +N,n @@ (glob)' style chunk header for portability",
253 "use '@@ -N* +N,n @@ (glob)' style chunk header for portability",
255 ),
254 ),
256 (
255 (
257 r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@',
256 r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@',
258 "use '@@ -N,n +N* @@ (glob)' style chunk header for portability",
257 "use '@@ -N,n +N* @@ (glob)' style chunk header for portability",
259 ),
258 ),
260 (
259 (
261 r'^ @@ -[0-9]+ [+][0-9]+ @@',
260 r'^ @@ -[0-9]+ [+][0-9]+ @@',
262 "use '@@ -N* +N* @@ (glob)' style chunk header for portability",
261 "use '@@ -N* +N* @@ (glob)' style chunk header for portability",
263 ),
262 ),
264 (
263 (
265 uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff'
264 uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff'
266 r'( +(-[^ po-]+|--(?!program|option)[^ ]+|[^-][^ ]*))*$',
265 r'( +(-[^ po-]+|--(?!program|option)[^ ]+|[^-][^ ]*))*$',
267 "use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)",
266 "use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)",
268 ),
267 ),
269 ],
268 ],
270 # warnings
269 # warnings
271 [
270 [
272 (
271 (
273 r'^ (?!.*\$LOCALIP)[^*?/\n]* \(glob\)$',
272 r'^ (?!.*\$LOCALIP)[^*?/\n]* \(glob\)$',
274 "glob match with no glob string (?, *, /, and $LOCALIP)",
273 "glob match with no glob string (?, *, /, and $LOCALIP)",
275 ),
274 ),
276 ],
275 ],
277 ]
276 ]
278
277
279 # transform plain test rules to unified test's
278 # transform plain test rules to unified test's
280 for i in [0, 1]:
279 for i in [0, 1]:
281 for tp in testpats[i]:
280 for tp in testpats[i]:
282 p = tp[0]
281 p = tp[0]
283 m = tp[1]
282 m = tp[1]
284 if p.startswith('^'):
283 if p.startswith('^'):
285 p = "^ [$>] (%s)" % p[1:]
284 p = "^ [$>] (%s)" % p[1:]
286 else:
285 else:
287 p = "^ [$>] .*(%s)" % p
286 p = "^ [$>] .*(%s)" % p
288 utestpats[i].append((p, m) + tp[2:])
287 utestpats[i].append((p, m) + tp[2:])
289
288
290 # don't transform the following rules:
289 # don't transform the following rules:
291 # " > \t" and " \t" should be allowed in unified tests
290 # " > \t" and " \t" should be allowed in unified tests
292 testpats[0].append((r'^( *)\t', "don't use tabs to indent"))
291 testpats[0].append((r'^( *)\t', "don't use tabs to indent"))
293 utestpats[0].append((r'^( ?)\t', "don't use tabs to indent"))
292 utestpats[0].append((r'^( ?)\t', "don't use tabs to indent"))
294
293
295 utestfilters = [
294 utestfilters = [
296 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
295 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
297 (r"( +)(#([^!][^\n]*\S)?)", repcomment),
296 (r"( +)(#([^!][^\n]*\S)?)", repcomment),
298 ]
297 ]
299
298
300 # common patterns to check *.py
299 # common patterns to check *.py
301 commonpypats = [
300 commonpypats = [
302 [
301 [
303 (r'\\$', 'Use () to wrap long lines in Python, not \\'),
302 (r'\\$', 'Use () to wrap long lines in Python, not \\'),
304 (
303 (
305 r'^\s*def\s*\w+\s*\(.*,\s*\(',
304 r'^\s*def\s*\w+\s*\(.*,\s*\(',
306 "tuple parameter unpacking not available in Python 3+",
305 "tuple parameter unpacking not available in Python 3+",
307 ),
306 ),
308 (
307 (
309 r'lambda\s*\(.*,.*\)',
308 r'lambda\s*\(.*,.*\)',
310 "tuple parameter unpacking not available in Python 3+",
309 "tuple parameter unpacking not available in Python 3+",
311 ),
310 ),
312 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
311 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
313 (r'(?<!\.)\breduce\s*\(.*', "reduce is not available in Python 3+"),
312 (r'(?<!\.)\breduce\s*\(.*', "reduce is not available in Python 3+"),
314 (
313 (
315 r'\bdict\(.*=',
314 r'\bdict\(.*=',
316 'dict() is different in Py2 and 3 and is slower than {}',
315 'dict() is different in Py2 and 3 and is slower than {}',
317 'dict-from-generator',
316 'dict-from-generator',
318 ),
317 ),
319 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
318 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
320 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
319 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
321 (r'^\s*\t', "don't use tabs"),
320 (r'^\s*\t', "don't use tabs"),
322 (r'\S;\s*\n', "semicolon"),
321 (r'\S;\s*\n', "semicolon"),
323 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
322 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
324 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
323 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
325 (r'(\w|\)),\w', "missing whitespace after ,"),
324 (r'(\w|\)),\w', "missing whitespace after ,"),
326 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
325 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
327 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
326 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
328 (
327 (
329 (
328 (
330 # a line ending with a colon, potentially with trailing comments
329 # a line ending with a colon, potentially with trailing comments
331 r':([ \t]*#[^\n]*)?\n'
330 r':([ \t]*#[^\n]*)?\n'
332 # one that is not a pass and not only a comment
331 # one that is not a pass and not only a comment
333 r'(?P<indent>[ \t]+)[^#][^\n]+\n'
332 r'(?P<indent>[ \t]+)[^#][^\n]+\n'
334 # more lines at the same indent level
333 # more lines at the same indent level
335 r'((?P=indent)[^\n]+\n)*'
334 r'((?P=indent)[^\n]+\n)*'
336 # a pass at the same indent level, which is bogus
335 # a pass at the same indent level, which is bogus
337 r'(?P=indent)pass[ \t\n#]'
336 r'(?P=indent)pass[ \t\n#]'
338 ),
337 ),
339 'omit superfluous pass',
338 'omit superfluous pass',
340 ),
339 ),
341 (r'[^\n]\Z', "no trailing newline"),
340 (r'[^\n]\Z', "no trailing newline"),
342 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
341 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
343 (
342 (
344 r'^\s+(self\.)?[A-Za-z][a-z0-9]+[A-Z]\w* = ',
343 r'^\s+(self\.)?[A-Za-z][a-z0-9]+[A-Z]\w* = ',
345 "don't use camelcase in identifiers",
344 "don't use camelcase in identifiers",
346 r'#.*camelcase-required',
345 r'#.*camelcase-required',
347 ),
346 ),
348 (
347 (
349 r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
348 r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
350 "linebreak after :",
349 "linebreak after :",
351 ),
350 ),
352 (
351 (
353 r'class\s[^( \n]+:',
352 r'class\s[^( \n]+:',
354 "old-style class, use class foo(object)",
353 "old-style class, use class foo(object)",
355 r'#.*old-style',
354 r'#.*old-style',
356 ),
355 ),
357 (
356 (
358 r'class\s[^( \n]+\(\):',
357 r'class\s[^( \n]+\(\):',
359 "class foo() creates old style object, use class foo(object)",
358 "class foo() creates old style object, use class foo(object)",
360 r'#.*old-style',
359 r'#.*old-style',
361 ),
360 ),
362 (
361 (
363 r'\b(%s)\('
362 r'\b(%s)\('
364 % '|'.join(k for k in keyword.kwlist if k not in ('print', 'exec')),
363 % '|'.join(k for k in keyword.kwlist if k not in ('print', 'exec')),
365 "Python keyword is not a function",
364 "Python keyword is not a function",
366 ),
365 ),
367 # (r'class\s[A-Z][^\(]*\((?!Exception)',
366 # (r'class\s[A-Z][^\(]*\((?!Exception)',
368 # "don't capitalize non-exception classes"),
367 # "don't capitalize non-exception classes"),
369 # (r'in range\(', "use xrange"),
368 # (r'in range\(', "use xrange"),
370 # (r'^\s*print\s+', "avoid using print in core and extensions"),
369 # (r'^\s*print\s+', "avoid using print in core and extensions"),
371 (r'[\x80-\xff]', "non-ASCII character literal"),
370 (r'[\x80-\xff]', "non-ASCII character literal"),
372 (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"),
371 (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"),
373 (
372 (
374 r'([\(\[][ \t]\S)|(\S[ \t][\)\]])',
373 r'([\(\[][ \t]\S)|(\S[ \t][\)\]])',
375 "gratuitous whitespace in () or []",
374 "gratuitous whitespace in () or []",
376 ),
375 ),
377 # (r'\s\s=', "gratuitous whitespace before ="),
376 # (r'\s\s=', "gratuitous whitespace before ="),
378 (
377 (
379 r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
378 r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
380 "missing whitespace around operator",
379 "missing whitespace around operator",
381 ),
380 ),
382 (
381 (
383 r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
382 r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
384 "missing whitespace around operator",
383 "missing whitespace around operator",
385 ),
384 ),
386 (
385 (
387 r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
386 r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
388 "missing whitespace around operator",
387 "missing whitespace around operator",
389 ),
388 ),
390 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]', "wrong whitespace around ="),
389 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]', "wrong whitespace around ="),
391 (
390 (
392 r'\([^()]*( =[^=]|[^<>!=]= )',
391 r'\([^()]*( =[^=]|[^<>!=]= )',
393 "no whitespace around = for named parameters",
392 "no whitespace around = for named parameters",
394 ),
393 ),
395 (
394 (
396 r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
395 r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
397 "don't use old-style two-argument raise, use Exception(message)",
396 "don't use old-style two-argument raise, use Exception(message)",
398 ),
397 ),
399 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
398 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
400 (
399 (
401 r' [=!]=\s+(True|False|None)',
400 r' [=!]=\s+(True|False|None)',
402 "comparison with singleton, use 'is' or 'is not' instead",
401 "comparison with singleton, use 'is' or 'is not' instead",
403 ),
402 ),
404 (
403 (
405 r'^\s*(while|if) [01]:',
404 r'^\s*(while|if) [01]:',
406 "use True/False for constant Boolean expression",
405 "use True/False for constant Boolean expression",
407 ),
406 ),
408 (r'^\s*if False(:| +and)', 'Remove code instead of using `if False`'),
407 (r'^\s*if False(:| +and)', 'Remove code instead of using `if False`'),
409 (
408 (
410 r'(?:(?<!def)\s+|\()hasattr\(',
409 r'(?:(?<!def)\s+|\()hasattr\(',
411 'hasattr(foo, bar) is broken on py2, use util.safehasattr(foo, bar) '
410 'hasattr(foo, bar) is broken on py2, use util.safehasattr(foo, bar) '
412 'instead',
411 'instead',
413 r'#.*hasattr-py3-only',
412 r'#.*hasattr-py3-only',
414 ),
413 ),
415 (r'opener\([^)]*\).read\(', "use opener.read() instead"),
414 (r'opener\([^)]*\).read\(', "use opener.read() instead"),
416 (r'opener\([^)]*\).write\(', "use opener.write() instead"),
415 (r'opener\([^)]*\).write\(', "use opener.write() instead"),
417 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
416 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
418 (r'\.debug\(\_', "don't mark debug messages for translation"),
417 (r'\.debug\(\_', "don't mark debug messages for translation"),
419 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
418 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
420 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
419 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
421 (
420 (
422 r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
421 r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
423 'legacy exception syntax; use "as" instead of ","',
422 'legacy exception syntax; use "as" instead of ","',
424 ),
423 ),
425 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
424 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
426 (r'\bdef\s+__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
425 (r'\bdef\s+__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
427 (
426 (
428 r'os\.path\.join\(.*, *(""|\'\')\)',
427 r'os\.path\.join\(.*, *(""|\'\')\)',
429 "use pathutil.normasprefix(path) instead of os.path.join(path, '')",
428 "use pathutil.normasprefix(path) instead of os.path.join(path, '')",
430 ),
429 ),
431 (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
430 (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
432 # XXX only catch mutable arguments on the first line of the definition
431 # XXX only catch mutable arguments on the first line of the definition
433 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
432 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
434 (r'\butil\.Abort\b', "directly use error.Abort"),
433 (r'\butil\.Abort\b', "directly use error.Abort"),
435 (
434 (
436 r'^@(\w*\.)?cachefunc',
435 r'^@(\w*\.)?cachefunc',
437 "module-level @cachefunc is risky, please avoid",
436 "module-level @cachefunc is risky, please avoid",
438 ),
437 ),
439 (
438 (
440 r'^import Queue',
439 r'^import Queue',
441 "don't use Queue, use pycompat.queue.Queue + "
440 "don't use Queue, use pycompat.queue.Queue + "
442 "pycompat.queue.Empty",
441 "pycompat.queue.Empty",
443 ),
442 ),
444 (
443 (
445 r'^import cStringIO',
444 r'^import cStringIO',
446 "don't use cStringIO.StringIO, use util.stringio",
445 "don't use cStringIO.StringIO, use util.stringio",
447 ),
446 ),
448 (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
447 (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
449 (
448 (
450 r'^import SocketServer',
449 r'^import SocketServer',
451 "don't use SockerServer, use util.socketserver",
450 "don't use SockerServer, use util.socketserver",
452 ),
451 ),
453 (r'^import urlparse', "don't use urlparse, use util.urlreq"),
452 (r'^import urlparse', "don't use urlparse, use util.urlreq"),
454 (r'^import xmlrpclib', "don't use xmlrpclib, use util.xmlrpclib"),
453 (r'^import xmlrpclib', "don't use xmlrpclib, use util.xmlrpclib"),
455 (r'^import cPickle', "don't use cPickle, use util.pickle"),
454 (r'^import cPickle', "don't use cPickle, use util.pickle"),
456 (r'^import pickle', "don't use pickle, use util.pickle"),
455 (r'^import pickle', "don't use pickle, use util.pickle"),
457 (r'^import httplib', "don't use httplib, use util.httplib"),
456 (r'^import httplib', "don't use httplib, use util.httplib"),
458 (r'^import BaseHTTPServer', "use util.httpserver instead"),
457 (r'^import BaseHTTPServer', "use util.httpserver instead"),
459 (
458 (
460 r'^(from|import) mercurial\.(cext|pure|cffi)',
459 r'^(from|import) mercurial\.(cext|pure|cffi)',
461 "use mercurial.policy.importmod instead",
460 "use mercurial.policy.importmod instead",
462 ),
461 ),
463 (r'\.next\(\)', "don't use .next(), use next(...)"),
462 (r'\.next\(\)', "don't use .next(), use next(...)"),
464 (
463 (
465 r'([a-z]*).revision\(\1\.node\(',
464 r'([a-z]*).revision\(\1\.node\(',
466 "don't convert rev to node before passing to revision(nodeorrev)",
465 "don't convert rev to node before passing to revision(nodeorrev)",
467 ),
466 ),
468 (r'platform\.system\(\)', "don't use platform.system(), use pycompat"),
467 (r'platform\.system\(\)', "don't use platform.system(), use pycompat"),
469 ],
468 ],
470 # warnings
469 # warnings
471 [],
470 [],
472 ]
471 ]
473
472
474 # patterns to check normal *.py files
473 # patterns to check normal *.py files
475 pypats = [
474 pypats = [
476 [
475 [
477 # Ideally, these should be placed in "commonpypats" for
476 # Ideally, these should be placed in "commonpypats" for
478 # consistency of coding rules in Mercurial source tree.
477 # consistency of coding rules in Mercurial source tree.
479 # But on the other hand, these are not so seriously required for
478 # But on the other hand, these are not so seriously required for
480 # python code fragments embedded in test scripts. Fixing test
479 # python code fragments embedded in test scripts. Fixing test
481 # scripts for these patterns requires many changes, and has less
480 # scripts for these patterns requires many changes, and has less
482 # profit than effort.
481 # profit than effort.
483 (r'raise Exception', "don't raise generic exceptions"),
482 (r'raise Exception', "don't raise generic exceptions"),
484 (r'[\s\(](open|file)\([^)]*\)\.read\(', "use util.readfile() instead"),
483 (r'[\s\(](open|file)\([^)]*\)\.read\(', "use util.readfile() instead"),
485 (
484 (
486 r'[\s\(](open|file)\([^)]*\)\.write\(',
485 r'[\s\(](open|file)\([^)]*\)\.write\(',
487 "use util.writefile() instead",
486 "use util.writefile() instead",
488 ),
487 ),
489 (
488 (
490 r'^[\s\(]*(open(er)?|file)\([^)]*\)(?!\.close\(\))',
489 r'^[\s\(]*(open(er)?|file)\([^)]*\)(?!\.close\(\))',
491 "always assign an opened file to a variable, and close it afterwards",
490 "always assign an opened file to a variable, and close it afterwards",
492 ),
491 ),
493 (
492 (
494 r'[\s\(](open|file)\([^)]*\)\.(?!close\(\))',
493 r'[\s\(](open|file)\([^)]*\)\.(?!close\(\))',
495 "always assign an opened file to a variable, and close it afterwards",
494 "always assign an opened file to a variable, and close it afterwards",
496 ),
495 ),
497 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
496 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
498 (r'^import atexit', "don't use atexit, use ui.atexit"),
497 (r'^import atexit', "don't use atexit, use ui.atexit"),
499 # rules depending on implementation of repquote()
498 # rules depending on implementation of repquote()
500 (
499 (
501 r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
500 r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
502 'string join across lines with no space',
501 'string join across lines with no space',
503 ),
502 ),
504 (
503 (
505 r'''(?x)ui\.(status|progress|write|note|warn)\(
504 r'''(?x)ui\.(status|progress|write|note|warn)\(
506 [ \t\n#]*
505 [ \t\n#]*
507 (?# any strings/comments might precede a string, which
506 (?# any strings/comments might precede a string, which
508 # contains translatable message)
507 # contains translatable message)
509 b?((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
508 b?((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
510 (?# sequence consisting of below might precede translatable message
509 (?# sequence consisting of below might precede translatable message
511 # - formatting string: "% 10s", "%05d", "% -3.2f", "%*s", "%%" ...
510 # - formatting string: "% 10s", "%05d", "% -3.2f", "%*s", "%%" ...
512 # - escaped character: "\\", "\n", "\0" ...
511 # - escaped character: "\\", "\n", "\0" ...
513 # - character other than '%', 'b' as '\', and 'x' as alphabet)
512 # - character other than '%', 'b' as '\', and 'x' as alphabet)
514 (['"]|\'\'\'|""")
513 (['"]|\'\'\'|""")
515 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
514 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
516 (?# this regexp can't use [^...] style,
515 (?# this regexp can't use [^...] style,
517 # because _preparepats forcibly adds "\n" into [^...],
516 # because _preparepats forcibly adds "\n" into [^...],
518 # even though this regexp wants match it against "\n")''',
517 # even though this regexp wants match it against "\n")''',
519 "missing _() in ui message (use () to hide false-positives)",
518 "missing _() in ui message (use () to hide false-positives)",
520 ),
519 ),
521 ]
520 ]
522 + commonpypats[0],
521 + commonpypats[0],
523 # warnings
522 # warnings
524 [
523 [
525 # rules depending on implementation of repquote()
524 # rules depending on implementation of repquote()
526 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
525 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
527 ]
526 ]
528 + commonpypats[1],
527 + commonpypats[1],
529 ]
528 ]
530
529
531 # patterns to check *.py for embedded ones in test script
530 # patterns to check *.py for embedded ones in test script
532 embeddedpypats = [
531 embeddedpypats = [
533 [] + commonpypats[0],
532 [] + commonpypats[0],
534 # warnings
533 # warnings
535 [] + commonpypats[1],
534 [] + commonpypats[1],
536 ]
535 ]
537
536
538 # common filters to convert *.py
537 # common filters to convert *.py
539 commonpyfilters = [
538 commonpyfilters = [
540 (
539 (
541 r"""(?msx)(?P<comment>\#.*?$)|
540 r"""(?msx)(?P<comment>\#.*?$)|
542 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
541 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
543 (?P<text>(([^\\]|\\.)*?))
542 (?P<text>(([^\\]|\\.)*?))
544 (?P=quote))""",
543 (?P=quote))""",
545 reppython,
544 reppython,
546 ),
545 ),
547 ]
546 ]
548
547
549 # filters to convert normal *.py files
548 # filters to convert normal *.py files
550 pyfilters = [] + commonpyfilters
549 pyfilters = [] + commonpyfilters
551
550
552 # non-filter patterns
551 # non-filter patterns
553 pynfpats = [
552 pynfpats = [
554 [
553 [
555 (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"),
554 (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"),
556 (r'pycompat\.osname\s*[=!]=\s*[\'"]posix[\'"]', "use pycompat.isposix"),
555 (r'pycompat\.osname\s*[=!]=\s*[\'"]posix[\'"]', "use pycompat.isposix"),
557 (
556 (
558 r'pycompat\.sysplatform\s*[!=]=\s*[\'"]darwin[\'"]',
557 r'pycompat\.sysplatform\s*[!=]=\s*[\'"]darwin[\'"]',
559 "use pycompat.isdarwin",
558 "use pycompat.isdarwin",
560 ),
559 ),
561 ],
560 ],
562 # warnings
561 # warnings
563 [],
562 [],
564 ]
563 ]
565
564
566 # filters to convert *.py for embedded ones in test script
565 # filters to convert *.py for embedded ones in test script
567 embeddedpyfilters = [] + commonpyfilters
566 embeddedpyfilters = [] + commonpyfilters
568
567
569 # extension non-filter patterns
568 # extension non-filter patterns
570 pyextnfpats = [
569 pyextnfpats = [
571 [(r'^"""\n?[A-Z]', "don't capitalize docstring title")],
570 [(r'^"""\n?[A-Z]', "don't capitalize docstring title")],
572 # warnings
571 # warnings
573 [],
572 [],
574 ]
573 ]
575
574
576 txtfilters = []
575 txtfilters = []
577
576
578 txtpats = [
577 txtpats = [
579 [
578 [
580 (r'\s$', 'trailing whitespace'),
579 (r'\s$', 'trailing whitespace'),
581 ('.. note::[ \n][^\n]', 'add two newlines after note::'),
580 ('.. note::[ \n][^\n]', 'add two newlines after note::'),
582 ],
581 ],
583 [],
582 [],
584 ]
583 ]
585
584
586 cpats = [
585 cpats = [
587 [
586 [
588 (r'//', "don't use //-style comments"),
587 (r'//', "don't use //-style comments"),
589 (r'\S\t', "don't use tabs except for indent"),
588 (r'\S\t', "don't use tabs except for indent"),
590 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
589 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
591 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
590 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
592 (r'return\(', "return is not a function"),
591 (r'return\(', "return is not a function"),
593 (r' ;', "no space before ;"),
592 (r' ;', "no space before ;"),
594 (r'[^;] \)', "no space before )"),
593 (r'[^;] \)', "no space before )"),
595 (r'[)][{]', "space between ) and {"),
594 (r'[)][{]', "space between ) and {"),
596 (r'\w+\* \w+', "use int *foo, not int* foo"),
595 (r'\w+\* \w+', "use int *foo, not int* foo"),
597 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
596 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
598 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
597 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
599 (r'\w,\w', "missing whitespace after ,"),
598 (r'\w,\w', "missing whitespace after ,"),
600 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
599 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
601 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
600 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
602 (r'^#\s+\w', "use #foo, not # foo"),
601 (r'^#\s+\w', "use #foo, not # foo"),
603 (r'[^\n]\Z', "no trailing newline"),
602 (r'[^\n]\Z', "no trailing newline"),
604 (r'^\s*#import\b', "use only #include in standard C code"),
603 (r'^\s*#import\b', "use only #include in standard C code"),
605 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
604 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
606 (r'strcat\(', "don't use strcat"),
605 (r'strcat\(', "don't use strcat"),
607 # rules depending on implementation of repquote()
606 # rules depending on implementation of repquote()
608 ],
607 ],
609 # warnings
608 # warnings
610 [
609 [
611 # rules depending on implementation of repquote()
610 # rules depending on implementation of repquote()
612 ],
611 ],
613 ]
612 ]
614
613
615 cfilters = [
614 cfilters = [
616 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
615 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
617 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
616 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
618 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
617 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
619 (r'(\()([^)]+\))', repcallspaces),
618 (r'(\()([^)]+\))', repcallspaces),
620 ]
619 ]
621
620
622 inutilpats = [
621 inutilpats = [
623 [
622 [
624 (r'\bui\.', "don't use ui in util"),
623 (r'\bui\.', "don't use ui in util"),
625 ],
624 ],
626 # warnings
625 # warnings
627 [],
626 [],
628 ]
627 ]
629
628
630 inrevlogpats = [
629 inrevlogpats = [
631 [
630 [
632 (r'\brepo\.', "don't use repo in revlog"),
631 (r'\brepo\.', "don't use repo in revlog"),
633 ],
632 ],
634 # warnings
633 # warnings
635 [],
634 [],
636 ]
635 ]
637
636
638 webtemplatefilters = []
637 webtemplatefilters = []
639
638
640 webtemplatepats = [
639 webtemplatepats = [
641 [],
640 [],
642 [
641 [
643 (
642 (
644 r'{desc(\|(?!websub|firstline)[^\|]*)+}',
643 r'{desc(\|(?!websub|firstline)[^\|]*)+}',
645 'follow desc keyword with either firstline or websub',
644 'follow desc keyword with either firstline or websub',
646 ),
645 ),
647 ],
646 ],
648 ]
647 ]
649
648
650 allfilesfilters = []
649 allfilesfilters = []
651
650
652 allfilespats = [
651 allfilespats = [
653 [
652 [
654 (
653 (
655 r'(http|https)://[a-zA-Z0-9./]*selenic.com/',
654 r'(http|https)://[a-zA-Z0-9./]*selenic.com/',
656 'use mercurial-scm.org domain URL',
655 'use mercurial-scm.org domain URL',
657 ),
656 ),
658 (
657 (
659 r'mercurial@selenic\.com',
658 r'mercurial@selenic\.com',
660 'use mercurial-scm.org domain for mercurial ML address',
659 'use mercurial-scm.org domain for mercurial ML address',
661 ),
660 ),
662 (
661 (
663 r'mercurial-devel@selenic\.com',
662 r'mercurial-devel@selenic\.com',
664 'use mercurial-scm.org domain for mercurial-devel ML address',
663 'use mercurial-scm.org domain for mercurial-devel ML address',
665 ),
664 ),
666 ],
665 ],
667 # warnings
666 # warnings
668 [],
667 [],
669 ]
668 ]
670
669
671 py3pats = [
670 py3pats = [
672 [
671 [
673 (
672 (
674 r'os\.environ',
673 r'os\.environ',
675 "use encoding.environ instead (py3)",
674 "use encoding.environ instead (py3)",
676 r'#.*re-exports',
675 r'#.*re-exports',
677 ),
676 ),
678 (r'os\.name', "use pycompat.osname instead (py3)"),
677 (r'os\.name', "use pycompat.osname instead (py3)"),
679 (r'os\.getcwd', "use encoding.getcwd instead (py3)", r'#.*re-exports'),
678 (r'os\.getcwd', "use encoding.getcwd instead (py3)", r'#.*re-exports'),
680 (r'os\.sep', "use pycompat.ossep instead (py3)"),
679 (r'os\.sep', "use pycompat.ossep instead (py3)"),
681 (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"),
680 (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"),
682 (r'os\.altsep', "use pycompat.osaltsep instead (py3)"),
681 (r'os\.altsep', "use pycompat.osaltsep instead (py3)"),
683 (r'sys\.platform', "use pycompat.sysplatform instead (py3)"),
682 (r'sys\.platform', "use pycompat.sysplatform instead (py3)"),
684 (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"),
683 (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"),
685 (r'os\.getenv', "use encoding.environ.get instead"),
684 (r'os\.getenv', "use encoding.environ.get instead"),
686 (r'os\.setenv', "modifying the environ dict is not preferred"),
685 (r'os\.setenv', "modifying the environ dict is not preferred"),
687 (r'(?<!pycompat\.)xrange', "use pycompat.xrange instead (py3)"),
686 (r'(?<!pycompat\.)xrange', "use pycompat.xrange instead (py3)"),
688 ],
687 ],
689 # warnings
688 # warnings
690 [],
689 [],
691 ]
690 ]
692
691
693 checks = [
692 checks = [
694 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
693 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
695 ('python', r'.*\.(py|cgi)$', r'^#!.*python', [], pynfpats),
694 ('python', r'.*\.(py|cgi)$', r'^#!.*python', [], pynfpats),
696 ('python', r'.*hgext.*\.py$', '', [], pyextnfpats),
695 ('python', r'.*hgext.*\.py$', '', [], pyextnfpats),
697 (
696 (
698 'python 3',
697 'python 3',
699 r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
698 r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
700 '',
699 '',
701 pyfilters,
700 pyfilters,
702 py3pats,
701 py3pats,
703 ),
702 ),
704 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
703 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
705 ('c', r'.*\.[ch]$', '', cfilters, cpats),
704 ('c', r'.*\.[ch]$', '', cfilters, cpats),
706 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
705 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
707 (
706 (
708 'layering violation repo in revlog',
707 'layering violation repo in revlog',
709 r'mercurial/revlog\.py',
708 r'mercurial/revlog\.py',
710 '',
709 '',
711 pyfilters,
710 pyfilters,
712 inrevlogpats,
711 inrevlogpats,
713 ),
712 ),
714 (
713 (
715 'layering violation ui in util',
714 'layering violation ui in util',
716 r'mercurial/util\.py',
715 r'mercurial/util\.py',
717 '',
716 '',
718 pyfilters,
717 pyfilters,
719 inutilpats,
718 inutilpats,
720 ),
719 ),
721 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
720 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
722 (
721 (
723 'web template',
722 'web template',
724 r'mercurial/templates/.*\.tmpl',
723 r'mercurial/templates/.*\.tmpl',
725 '',
724 '',
726 webtemplatefilters,
725 webtemplatefilters,
727 webtemplatepats,
726 webtemplatepats,
728 ),
727 ),
729 ('all except for .po', r'.*(?<!\.po)$', '', allfilesfilters, allfilespats),
728 ('all except for .po', r'.*(?<!\.po)$', '', allfilesfilters, allfilespats),
730 ]
729 ]
731
730
732 # (desc,
731 # (desc,
733 # func to pick up embedded code fragments,
732 # func to pick up embedded code fragments,
734 # list of patterns to convert target files
733 # list of patterns to convert target files
735 # list of patterns to detect errors/warnings)
734 # list of patterns to detect errors/warnings)
736 embeddedchecks = [
735 embeddedchecks = [
737 (
736 (
738 'embedded python',
737 'embedded python',
739 testparseutil.pyembedded,
738 testparseutil.pyembedded,
740 embeddedpyfilters,
739 embeddedpyfilters,
741 embeddedpypats,
740 embeddedpypats,
742 )
741 )
743 ]
742 ]
744
743
745
744
746 def _preparepats():
745 def _preparepats():
747 def preparefailandwarn(failandwarn):
746 def preparefailandwarn(failandwarn):
748 for pats in failandwarn:
747 for pats in failandwarn:
749 for i, pseq in enumerate(pats):
748 for i, pseq in enumerate(pats):
750 # fix-up regexes for multi-line searches
749 # fix-up regexes for multi-line searches
751 p = pseq[0]
750 p = pseq[0]
752 # \s doesn't match \n (done in two steps)
751 # \s doesn't match \n (done in two steps)
753 # first, we replace \s that appears in a set already
752 # first, we replace \s that appears in a set already
754 p = re.sub(r'\[\\s', r'[ \\t', p)
753 p = re.sub(r'\[\\s', r'[ \\t', p)
755 # now we replace other \s instances.
754 # now we replace other \s instances.
756 p = re.sub(r'(?<!(\\|\[))\\s', r'[ \\t]', p)
755 p = re.sub(r'(?<!(\\|\[))\\s', r'[ \\t]', p)
757 # [^...] doesn't match newline
756 # [^...] doesn't match newline
758 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
757 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
759
758
760 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
759 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
761
760
762 def preparefilters(filters):
761 def preparefilters(filters):
763 for i, flt in enumerate(filters):
762 for i, flt in enumerate(filters):
764 filters[i] = re.compile(flt[0]), flt[1]
763 filters[i] = re.compile(flt[0]), flt[1]
765
764
766 for cs in (checks, embeddedchecks):
765 for cs in (checks, embeddedchecks):
767 for c in cs:
766 for c in cs:
768 failandwarn = c[-1]
767 failandwarn = c[-1]
769 preparefailandwarn(failandwarn)
768 preparefailandwarn(failandwarn)
770
769
771 filters = c[-2]
770 filters = c[-2]
772 preparefilters(filters)
771 preparefilters(filters)
773
772
774
773
775 class norepeatlogger(object):
774 class norepeatlogger(object):
776 def __init__(self):
775 def __init__(self):
777 self._lastseen = None
776 self._lastseen = None
778
777
779 def log(self, fname, lineno, line, msg, blame):
778 def log(self, fname, lineno, line, msg, blame):
780 """print error related a to given line of a given file.
779 """print error related a to given line of a given file.
781
780
782 The faulty line will also be printed but only once in the case
781 The faulty line will also be printed but only once in the case
783 of multiple errors.
782 of multiple errors.
784
783
785 :fname: filename
784 :fname: filename
786 :lineno: line number
785 :lineno: line number
787 :line: actual content of the line
786 :line: actual content of the line
788 :msg: error message
787 :msg: error message
789 """
788 """
790 msgid = fname, lineno, line
789 msgid = fname, lineno, line
791 if msgid != self._lastseen:
790 if msgid != self._lastseen:
792 if blame:
791 if blame:
793 print("%s:%d (%s):" % (fname, lineno, blame))
792 print("%s:%d (%s):" % (fname, lineno, blame))
794 else:
793 else:
795 print("%s:%d:" % (fname, lineno))
794 print("%s:%d:" % (fname, lineno))
796 print(" > %s" % line)
795 print(" > %s" % line)
797 self._lastseen = msgid
796 self._lastseen = msgid
798 print(" " + msg)
797 print(" " + msg)
799
798
800
799
801 _defaultlogger = norepeatlogger()
800 _defaultlogger = norepeatlogger()
802
801
803
802
804 def getblame(f):
803 def getblame(f):
805 lines = []
804 lines = []
806 for l in os.popen('hg annotate -un %s' % f):
805 for l in os.popen('hg annotate -un %s' % f):
807 start, line = l.split(':', 1)
806 start, line = l.split(':', 1)
808 user, rev = start.split()
807 user, rev = start.split()
809 lines.append((line[1:-1], user, rev))
808 lines.append((line[1:-1], user, rev))
810 return lines
809 return lines
811
810
812
811
813 def checkfile(
812 def checkfile(
814 f,
813 f,
815 logfunc=_defaultlogger.log,
814 logfunc=_defaultlogger.log,
816 maxerr=None,
815 maxerr=None,
817 warnings=False,
816 warnings=False,
818 blame=False,
817 blame=False,
819 debug=False,
818 debug=False,
820 lineno=True,
819 lineno=True,
821 ):
820 ):
822 """checks style and portability of a given file
821 """checks style and portability of a given file
823
822
824 :f: filepath
823 :f: filepath
825 :logfunc: function used to report error
824 :logfunc: function used to report error
826 logfunc(filename, linenumber, linecontent, errormessage)
825 logfunc(filename, linenumber, linecontent, errormessage)
827 :maxerr: number of error to display before aborting.
826 :maxerr: number of error to display before aborting.
828 Set to false (default) to report all errors
827 Set to false (default) to report all errors
829
828
830 return True if no error is found, False otherwise.
829 return True if no error is found, False otherwise.
831 """
830 """
832 result = True
831 result = True
833
832
834 try:
833 try:
835 with opentext(f) as fp:
834 with opentext(f) as fp:
836 try:
835 try:
837 pre = fp.read()
836 pre = fp.read()
838 except UnicodeDecodeError as e:
837 except UnicodeDecodeError as e:
839 print("%s while reading %s" % (e, f))
838 print("%s while reading %s" % (e, f))
840 return result
839 return result
841 except IOError as e:
840 except IOError as e:
842 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
841 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
843 return result
842 return result
844
843
845 # context information shared while single checkfile() invocation
844 # context information shared while single checkfile() invocation
846 context = {'blamecache': None}
845 context = {'blamecache': None}
847
846
848 for name, match, magic, filters, pats in checks:
847 for name, match, magic, filters, pats in checks:
849 if debug:
848 if debug:
850 print(name, f)
849 print(name, f)
851 if not (re.match(match, f) or (magic and re.search(magic, pre))):
850 if not (re.match(match, f) or (magic and re.search(magic, pre))):
852 if debug:
851 if debug:
853 print(
852 print(
854 "Skipping %s for %s it doesn't match %s" % (name, match, f)
853 "Skipping %s for %s it doesn't match %s" % (name, match, f)
855 )
854 )
856 continue
855 continue
857 if "no-" "check-code" in pre:
856 if "no-" "check-code" in pre:
858 # If you're looking at this line, it's because a file has:
857 # If you're looking at this line, it's because a file has:
859 # no- check- code
858 # no- check- code
860 # but the reason to output skipping is to make life for
859 # but the reason to output skipping is to make life for
861 # tests easier. So, instead of writing it with a normal
860 # tests easier. So, instead of writing it with a normal
862 # spelling, we write it with the expected spelling from
861 # spelling, we write it with the expected spelling from
863 # tests/test-check-code.t
862 # tests/test-check-code.t
864 print("Skipping %s it has no-che?k-code (glob)" % f)
863 print("Skipping %s it has no-che?k-code (glob)" % f)
865 return "Skip" # skip checking this file
864 return "Skip" # skip checking this file
866
865
867 fc = _checkfiledata(
866 fc = _checkfiledata(
868 name,
867 name,
869 f,
868 f,
870 pre,
869 pre,
871 filters,
870 filters,
872 pats,
871 pats,
873 context,
872 context,
874 logfunc,
873 logfunc,
875 maxerr,
874 maxerr,
876 warnings,
875 warnings,
877 blame,
876 blame,
878 debug,
877 debug,
879 lineno,
878 lineno,
880 )
879 )
881 if fc:
880 if fc:
882 result = False
881 result = False
883
882
884 if f.endswith('.t') and "no-" "check-code" not in pre:
883 if f.endswith('.t') and "no-" "check-code" not in pre:
885 if debug:
884 if debug:
886 print("Checking embedded code in %s" % f)
885 print("Checking embedded code in %s" % f)
887
886
888 prelines = pre.splitlines()
887 prelines = pre.splitlines()
889 embeddederros = []
888 embeddederros = []
890 for name, embedded, filters, pats in embeddedchecks:
889 for name, embedded, filters, pats in embeddedchecks:
891 # "reset curmax at each repetition" treats maxerr as "max
890 # "reset curmax at each repetition" treats maxerr as "max
892 # nubmer of errors in an actual file per entry of
891 # nubmer of errors in an actual file per entry of
893 # (embedded)checks"
892 # (embedded)checks"
894 curmaxerr = maxerr
893 curmaxerr = maxerr
895
894
896 for found in embedded(f, prelines, embeddederros):
895 for found in embedded(f, prelines, embeddederros):
897 filename, starts, ends, code = found
896 filename, starts, ends, code = found
898 fc = _checkfiledata(
897 fc = _checkfiledata(
899 name,
898 name,
900 f,
899 f,
901 code,
900 code,
902 filters,
901 filters,
903 pats,
902 pats,
904 context,
903 context,
905 logfunc,
904 logfunc,
906 curmaxerr,
905 curmaxerr,
907 warnings,
906 warnings,
908 blame,
907 blame,
909 debug,
908 debug,
910 lineno,
909 lineno,
911 offset=starts - 1,
910 offset=starts - 1,
912 )
911 )
913 if fc:
912 if fc:
914 result = False
913 result = False
915 if curmaxerr:
914 if curmaxerr:
916 if fc >= curmaxerr:
915 if fc >= curmaxerr:
917 break
916 break
918 curmaxerr -= fc
917 curmaxerr -= fc
919
918
920 return result
919 return result
921
920
922
921
923 def _checkfiledata(
922 def _checkfiledata(
924 name,
923 name,
925 f,
924 f,
926 filedata,
925 filedata,
927 filters,
926 filters,
928 pats,
927 pats,
929 context,
928 context,
930 logfunc,
929 logfunc,
931 maxerr,
930 maxerr,
932 warnings,
931 warnings,
933 blame,
932 blame,
934 debug,
933 debug,
935 lineno,
934 lineno,
936 offset=None,
935 offset=None,
937 ):
936 ):
938 """Execute actual error check for file data
937 """Execute actual error check for file data
939
938
940 :name: of the checking category
939 :name: of the checking category
941 :f: filepath
940 :f: filepath
942 :filedata: content of a file
941 :filedata: content of a file
943 :filters: to be applied before checking
942 :filters: to be applied before checking
944 :pats: to detect errors
943 :pats: to detect errors
945 :context: a dict of information shared while single checkfile() invocation
944 :context: a dict of information shared while single checkfile() invocation
946 Valid keys: 'blamecache'.
945 Valid keys: 'blamecache'.
947 :logfunc: function used to report error
946 :logfunc: function used to report error
948 logfunc(filename, linenumber, linecontent, errormessage)
947 logfunc(filename, linenumber, linecontent, errormessage)
949 :maxerr: number of error to display before aborting, or False to
948 :maxerr: number of error to display before aborting, or False to
950 report all errors
949 report all errors
951 :warnings: whether warning level checks should be applied
950 :warnings: whether warning level checks should be applied
952 :blame: whether blame information should be displayed at error reporting
951 :blame: whether blame information should be displayed at error reporting
953 :debug: whether debug information should be displayed
952 :debug: whether debug information should be displayed
954 :lineno: whether lineno should be displayed at error reporting
953 :lineno: whether lineno should be displayed at error reporting
955 :offset: line number offset of 'filedata' in 'f' for checking
954 :offset: line number offset of 'filedata' in 'f' for checking
956 an embedded code fragment, or None (offset=0 is different
955 an embedded code fragment, or None (offset=0 is different
957 from offset=None)
956 from offset=None)
958
957
959 returns number of detected errors.
958 returns number of detected errors.
960 """
959 """
961 blamecache = context['blamecache']
960 blamecache = context['blamecache']
962 if offset is None:
961 if offset is None:
963 lineoffset = 0
962 lineoffset = 0
964 else:
963 else:
965 lineoffset = offset
964 lineoffset = offset
966
965
967 fc = 0
966 fc = 0
968 pre = post = filedata
967 pre = post = filedata
969
968
970 if True: # TODO: get rid of this redundant 'if' block
969 if True: # TODO: get rid of this redundant 'if' block
971 for p, r in filters:
970 for p, r in filters:
972 post = re.sub(p, r, post)
971 post = re.sub(p, r, post)
973 nerrs = len(pats[0]) # nerr elements are errors
972 nerrs = len(pats[0]) # nerr elements are errors
974 if warnings:
973 if warnings:
975 pats = pats[0] + pats[1]
974 pats = pats[0] + pats[1]
976 else:
975 else:
977 pats = pats[0]
976 pats = pats[0]
978 # print post # uncomment to show filtered version
977 # print post # uncomment to show filtered version
979
978
980 if debug:
979 if debug:
981 print("Checking %s for %s" % (name, f))
980 print("Checking %s for %s" % (name, f))
982
981
983 prelines = None
982 prelines = None
984 errors = []
983 errors = []
985 for i, pat in enumerate(pats):
984 for i, pat in enumerate(pats):
986 if len(pat) == 3:
985 if len(pat) == 3:
987 p, msg, ignore = pat
986 p, msg, ignore = pat
988 else:
987 else:
989 p, msg = pat
988 p, msg = pat
990 ignore = None
989 ignore = None
991 if i >= nerrs:
990 if i >= nerrs:
992 msg = "warning: " + msg
991 msg = "warning: " + msg
993
992
994 pos = 0
993 pos = 0
995 n = 0
994 n = 0
996 for m in p.finditer(post):
995 for m in p.finditer(post):
997 if prelines is None:
996 if prelines is None:
998 prelines = pre.splitlines()
997 prelines = pre.splitlines()
999 postlines = post.splitlines(True)
998 postlines = post.splitlines(True)
1000
999
1001 start = m.start()
1000 start = m.start()
1002 while n < len(postlines):
1001 while n < len(postlines):
1003 step = len(postlines[n])
1002 step = len(postlines[n])
1004 if pos + step > start:
1003 if pos + step > start:
1005 break
1004 break
1006 pos += step
1005 pos += step
1007 n += 1
1006 n += 1
1008 l = prelines[n]
1007 l = prelines[n]
1009
1008
1010 if ignore and re.search(ignore, l, re.MULTILINE):
1009 if ignore and re.search(ignore, l, re.MULTILINE):
1011 if debug:
1010 if debug:
1012 print(
1011 print(
1013 "Skipping %s for %s:%s (ignore pattern)"
1012 "Skipping %s for %s:%s (ignore pattern)"
1014 % (name, f, (n + lineoffset))
1013 % (name, f, (n + lineoffset))
1015 )
1014 )
1016 continue
1015 continue
1017 bd = ""
1016 bd = ""
1018 if blame:
1017 if blame:
1019 bd = 'working directory'
1018 bd = 'working directory'
1020 if blamecache is None:
1019 if blamecache is None:
1021 blamecache = getblame(f)
1020 blamecache = getblame(f)
1022 context['blamecache'] = blamecache
1021 context['blamecache'] = blamecache
1023 if (n + lineoffset) < len(blamecache):
1022 if (n + lineoffset) < len(blamecache):
1024 bl, bu, br = blamecache[(n + lineoffset)]
1023 bl, bu, br = blamecache[(n + lineoffset)]
1025 if offset is None and bl == l:
1024 if offset is None and bl == l:
1026 bd = '%s@%s' % (bu, br)
1025 bd = '%s@%s' % (bu, br)
1027 elif offset is not None and bl.endswith(l):
1026 elif offset is not None and bl.endswith(l):
1028 # "offset is not None" means "checking
1027 # "offset is not None" means "checking
1029 # embedded code fragment". In this case,
1028 # embedded code fragment". In this case,
1030 # "l" does not have information about the
1029 # "l" does not have information about the
1031 # beginning of an *original* line in the
1030 # beginning of an *original* line in the
1032 # file (e.g. ' > ').
1031 # file (e.g. ' > ').
1033 # Therefore, use "str.endswith()", and
1032 # Therefore, use "str.endswith()", and
1034 # show "maybe" for a little loose
1033 # show "maybe" for a little loose
1035 # examination.
1034 # examination.
1036 bd = '%s@%s, maybe' % (bu, br)
1035 bd = '%s@%s, maybe' % (bu, br)
1037
1036
1038 errors.append((f, lineno and (n + lineoffset + 1), l, msg, bd))
1037 errors.append((f, lineno and (n + lineoffset + 1), l, msg, bd))
1039
1038
1040 errors.sort()
1039 errors.sort()
1041 for e in errors:
1040 for e in errors:
1042 logfunc(*e)
1041 logfunc(*e)
1043 fc += 1
1042 fc += 1
1044 if maxerr and fc >= maxerr:
1043 if maxerr and fc >= maxerr:
1045 print(" (too many errors, giving up)")
1044 print(" (too many errors, giving up)")
1046 break
1045 break
1047
1046
1048 return fc
1047 return fc
1049
1048
1050
1049
1051 def main():
1050 def main():
1052 parser = optparse.OptionParser("%prog [options] [files | -]")
1051 parser = optparse.OptionParser("%prog [options] [files | -]")
1053 parser.add_option(
1052 parser.add_option(
1054 "-w",
1053 "-w",
1055 "--warnings",
1054 "--warnings",
1056 action="store_true",
1055 action="store_true",
1057 help="include warning-level checks",
1056 help="include warning-level checks",
1058 )
1057 )
1059 parser.add_option(
1058 parser.add_option(
1060 "-p", "--per-file", type="int", help="max warnings per file"
1059 "-p", "--per-file", type="int", help="max warnings per file"
1061 )
1060 )
1062 parser.add_option(
1061 parser.add_option(
1063 "-b",
1062 "-b",
1064 "--blame",
1063 "--blame",
1065 action="store_true",
1064 action="store_true",
1066 help="use annotate to generate blame info",
1065 help="use annotate to generate blame info",
1067 )
1066 )
1068 parser.add_option(
1067 parser.add_option(
1069 "", "--debug", action="store_true", help="show debug information"
1068 "", "--debug", action="store_true", help="show debug information"
1070 )
1069 )
1071 parser.add_option(
1070 parser.add_option(
1072 "",
1071 "",
1073 "--nolineno",
1072 "--nolineno",
1074 action="store_false",
1073 action="store_false",
1075 dest='lineno',
1074 dest='lineno',
1076 help="don't show line numbers",
1075 help="don't show line numbers",
1077 )
1076 )
1078
1077
1079 parser.set_defaults(
1078 parser.set_defaults(
1080 per_file=15, warnings=False, blame=False, debug=False, lineno=True
1079 per_file=15, warnings=False, blame=False, debug=False, lineno=True
1081 )
1080 )
1082 (options, args) = parser.parse_args()
1081 (options, args) = parser.parse_args()
1083
1082
1084 if len(args) == 0:
1083 if len(args) == 0:
1085 check = glob.glob("*")
1084 check = glob.glob("*")
1086 elif args == ['-']:
1085 elif args == ['-']:
1087 # read file list from stdin
1086 # read file list from stdin
1088 check = sys.stdin.read().splitlines()
1087 check = sys.stdin.read().splitlines()
1089 else:
1088 else:
1090 check = args
1089 check = args
1091
1090
1092 _preparepats()
1091 _preparepats()
1093
1092
1094 ret = 0
1093 ret = 0
1095 for f in check:
1094 for f in check:
1096 if not checkfile(
1095 if not checkfile(
1097 f,
1096 f,
1098 maxerr=options.per_file,
1097 maxerr=options.per_file,
1099 warnings=options.warnings,
1098 warnings=options.warnings,
1100 blame=options.blame,
1099 blame=options.blame,
1101 debug=options.debug,
1100 debug=options.debug,
1102 lineno=options.lineno,
1101 lineno=options.lineno,
1103 ):
1102 ):
1104 ret = 1
1103 ret = 1
1105 return ret
1104 return ret
1106
1105
1107
1106
1108 if __name__ == "__main__":
1107 if __name__ == "__main__":
1109 sys.exit(main())
1108 sys.exit(main())
General Comments 0
You need to be logged in to leave comments. Login now