##// END OF EJS Templates
check-code: add a rules to catch os.path.abspath...
marmoute -
r48435:752109dc default
parent child Browse files
Show More
@@ -1,1108 +1,1131 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 (
218 (
219 uprefix + r'.*\|\| echo.*(fail|error)',
219 uprefix + r'.*\|\| echo.*(fail|error)',
220 "explicit exit code checks unnecessary",
220 "explicit exit code checks unnecessary",
221 ),
221 ),
222 (uprefix + r'set -e', "don't use set -e"),
222 (uprefix + r'set -e', "don't use set -e"),
223 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
223 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
224 (
224 (
225 uprefix + r'.*:\.\S*/',
225 uprefix + r'.*:\.\S*/',
226 "x:.y in a path does not work on msys, rewrite "
226 "x:.y in a path does not work on msys, rewrite "
227 "as x://.y, or see `hg log -k msys` for alternatives",
227 "as x://.y, or see `hg log -k msys` for alternatives",
228 r'-\S+:\.|' '# no-msys', # -Rxxx
228 r'-\S+:\.|' '# no-msys', # -Rxxx
229 ), # in test-pull.t which is skipped on windows
229 ), # in test-pull.t which is skipped on windows
230 (
230 (
231 r'^ [^$>].*27\.0\.0\.1',
231 r'^ [^$>].*27\.0\.0\.1',
232 'use $LOCALIP not an explicit loopback address',
232 'use $LOCALIP not an explicit loopback address',
233 ),
233 ),
234 (
234 (
235 r'^ (?![>$] ).*\$LOCALIP.*[^)]$',
235 r'^ (?![>$] ).*\$LOCALIP.*[^)]$',
236 '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',
237 ),
237 ),
238 (
238 (
239 r'^ (cat|find): .*: \$ENOENT\$',
239 r'^ (cat|find): .*: \$ENOENT\$',
240 'use test -f to test for file existence',
240 'use test -f to test for file existence',
241 ),
241 ),
242 (
242 (
243 r'^ diff -[^ -]*p',
243 r'^ diff -[^ -]*p',
244 "don't use (external) diff with -p for portability",
244 "don't use (external) diff with -p for portability",
245 ),
245 ),
246 (r' readlink ', 'use readlink.py instead of readlink'),
246 (r' readlink ', 'use readlink.py instead of readlink'),
247 (
247 (
248 r'^ [-+][-+][-+] .* [-+]0000 \(glob\)',
248 r'^ [-+][-+][-+] .* [-+]0000 \(glob\)',
249 "glob timezone field in diff output for portability",
249 "glob timezone field in diff output for portability",
250 ),
250 ),
251 (
251 (
252 r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@',
252 r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@',
253 "use '@@ -N* +N,n @@ (glob)' style chunk header for portability",
253 "use '@@ -N* +N,n @@ (glob)' style chunk header for portability",
254 ),
254 ),
255 (
255 (
256 r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@',
256 r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@',
257 "use '@@ -N,n +N* @@ (glob)' style chunk header for portability",
257 "use '@@ -N,n +N* @@ (glob)' style chunk header for portability",
258 ),
258 ),
259 (
259 (
260 r'^ @@ -[0-9]+ [+][0-9]+ @@',
260 r'^ @@ -[0-9]+ [+][0-9]+ @@',
261 "use '@@ -N* +N* @@ (glob)' style chunk header for portability",
261 "use '@@ -N* +N* @@ (glob)' style chunk header for portability",
262 ),
262 ),
263 (
263 (
264 uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff'
264 uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff'
265 r'( +(-[^ po-]+|--(?!program|option)[^ ]+|[^-][^ ]*))*$',
265 r'( +(-[^ po-]+|--(?!program|option)[^ ]+|[^-][^ ]*))*$',
266 "use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)",
266 "use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)",
267 ),
267 ),
268 ],
268 ],
269 # warnings
269 # warnings
270 [
270 [
271 (
271 (
272 r'^ (?!.*\$LOCALIP)[^*?/\n]* \(glob\)$',
272 r'^ (?!.*\$LOCALIP)[^*?/\n]* \(glob\)$',
273 "glob match with no glob string (?, *, /, and $LOCALIP)",
273 "glob match with no glob string (?, *, /, and $LOCALIP)",
274 ),
274 ),
275 ],
275 ],
276 ]
276 ]
277
277
278 # transform plain test rules to unified test's
278 # transform plain test rules to unified test's
279 for i in [0, 1]:
279 for i in [0, 1]:
280 for tp in testpats[i]:
280 for tp in testpats[i]:
281 p = tp[0]
281 p = tp[0]
282 m = tp[1]
282 m = tp[1]
283 if p.startswith('^'):
283 if p.startswith('^'):
284 p = "^ [$>] (%s)" % p[1:]
284 p = "^ [$>] (%s)" % p[1:]
285 else:
285 else:
286 p = "^ [$>] .*(%s)" % p
286 p = "^ [$>] .*(%s)" % p
287 utestpats[i].append((p, m) + tp[2:])
287 utestpats[i].append((p, m) + tp[2:])
288
288
289 # don't transform the following rules:
289 # don't transform the following rules:
290 # " > \t" and " \t" should be allowed in unified tests
290 # " > \t" and " \t" should be allowed in unified tests
291 testpats[0].append((r'^( *)\t', "don't use tabs to indent"))
291 testpats[0].append((r'^( *)\t', "don't use tabs to indent"))
292 utestpats[0].append((r'^( ?)\t', "don't use tabs to indent"))
292 utestpats[0].append((r'^( ?)\t', "don't use tabs to indent"))
293
293
294 utestfilters = [
294 utestfilters = [
295 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
295 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
296 (r"( +)(#([^!][^\n]*\S)?)", repcomment),
296 (r"( +)(#([^!][^\n]*\S)?)", repcomment),
297 ]
297 ]
298
298
299 # common patterns to check *.py
299 # common patterns to check *.py
300 commonpypats = [
300 commonpypats = [
301 [
301 [
302 (r'\\$', 'Use () to wrap long lines in Python, not \\'),
302 (r'\\$', 'Use () to wrap long lines in Python, not \\'),
303 (
303 (
304 r'^\s*def\s*\w+\s*\(.*,\s*\(',
304 r'^\s*def\s*\w+\s*\(.*,\s*\(',
305 "tuple parameter unpacking not available in Python 3+",
305 "tuple parameter unpacking not available in Python 3+",
306 ),
306 ),
307 (
307 (
308 r'lambda\s*\(.*,.*\)',
308 r'lambda\s*\(.*,.*\)',
309 "tuple parameter unpacking not available in Python 3+",
309 "tuple parameter unpacking not available in Python 3+",
310 ),
310 ),
311 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
311 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
312 (r'(?<!\.)\breduce\s*\(.*', "reduce is not available in Python 3+"),
312 (r'(?<!\.)\breduce\s*\(.*', "reduce is not available in Python 3+"),
313 (
313 (
314 r'\bdict\(.*=',
314 r'\bdict\(.*=',
315 'dict() is different in Py2 and 3 and is slower than {}',
315 'dict() is different in Py2 and 3 and is slower than {}',
316 'dict-from-generator',
316 'dict-from-generator',
317 ),
317 ),
318 (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+"),
319 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
319 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
320 (r'^\s*\t', "don't use tabs"),
320 (r'^\s*\t', "don't use tabs"),
321 (r'\S;\s*\n', "semicolon"),
321 (r'\S;\s*\n', "semicolon"),
322 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
322 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
323 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
323 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
324 (r'(\w|\)),\w', "missing whitespace after ,"),
324 (r'(\w|\)),\w', "missing whitespace after ,"),
325 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
325 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
326 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
326 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
327 (
327 (
328 (
328 (
329 # a line ending with a colon, potentially with trailing comments
329 # a line ending with a colon, potentially with trailing comments
330 r':([ \t]*#[^\n]*)?\n'
330 r':([ \t]*#[^\n]*)?\n'
331 # one that is not a pass and not only a comment
331 # one that is not a pass and not only a comment
332 r'(?P<indent>[ \t]+)[^#][^\n]+\n'
332 r'(?P<indent>[ \t]+)[^#][^\n]+\n'
333 # more lines at the same indent level
333 # more lines at the same indent level
334 r'((?P=indent)[^\n]+\n)*'
334 r'((?P=indent)[^\n]+\n)*'
335 # a pass at the same indent level, which is bogus
335 # a pass at the same indent level, which is bogus
336 r'(?P=indent)pass[ \t\n#]'
336 r'(?P=indent)pass[ \t\n#]'
337 ),
337 ),
338 'omit superfluous pass',
338 'omit superfluous pass',
339 ),
339 ),
340 (r'[^\n]\Z', "no trailing newline"),
340 (r'[^\n]\Z', "no trailing newline"),
341 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
341 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
342 (
342 (
343 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* = ',
344 "don't use camelcase in identifiers",
344 "don't use camelcase in identifiers",
345 r'#.*camelcase-required',
345 r'#.*camelcase-required',
346 ),
346 ),
347 (
347 (
348 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]+',
349 "linebreak after :",
349 "linebreak after :",
350 ),
350 ),
351 (
351 (
352 r'class\s[^( \n]+:',
352 r'class\s[^( \n]+:',
353 "old-style class, use class foo(object)",
353 "old-style class, use class foo(object)",
354 r'#.*old-style',
354 r'#.*old-style',
355 ),
355 ),
356 (
356 (
357 r'class\s[^( \n]+\(\):',
357 r'class\s[^( \n]+\(\):',
358 "class foo() creates old style object, use class foo(object)",
358 "class foo() creates old style object, use class foo(object)",
359 r'#.*old-style',
359 r'#.*old-style',
360 ),
360 ),
361 (
361 (
362 r'\b(%s)\('
362 r'\b(%s)\('
363 % '|'.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')),
364 "Python keyword is not a function",
364 "Python keyword is not a function",
365 ),
365 ),
366 # (r'class\s[A-Z][^\(]*\((?!Exception)',
366 # (r'class\s[A-Z][^\(]*\((?!Exception)',
367 # "don't capitalize non-exception classes"),
367 # "don't capitalize non-exception classes"),
368 # (r'in range\(', "use xrange"),
368 # (r'in range\(', "use xrange"),
369 # (r'^\s*print\s+', "avoid using print in core and extensions"),
369 # (r'^\s*print\s+', "avoid using print in core and extensions"),
370 (r'[\x80-\xff]', "non-ASCII character literal"),
370 (r'[\x80-\xff]', "non-ASCII character literal"),
371 (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"),
371 (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"),
372 (
372 (
373 r'([\(\[][ \t]\S)|(\S[ \t][\)\]])',
373 r'([\(\[][ \t]\S)|(\S[ \t][\)\]])',
374 "gratuitous whitespace in () or []",
374 "gratuitous whitespace in () or []",
375 ),
375 ),
376 # (r'\s\s=', "gratuitous whitespace before ="),
376 # (r'\s\s=', "gratuitous whitespace before ="),
377 (
377 (
378 r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
378 r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
379 "missing whitespace around operator",
379 "missing whitespace around operator",
380 ),
380 ),
381 (
381 (
382 r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
382 r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
383 "missing whitespace around operator",
383 "missing whitespace around operator",
384 ),
384 ),
385 (
385 (
386 r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
386 r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
387 "missing whitespace around operator",
387 "missing whitespace around operator",
388 ),
388 ),
389 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]', "wrong whitespace around ="),
389 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]', "wrong whitespace around ="),
390 (
390 (
391 r'\([^()]*( =[^=]|[^<>!=]= )',
391 r'\([^()]*( =[^=]|[^<>!=]= )',
392 "no whitespace around = for named parameters",
392 "no whitespace around = for named parameters",
393 ),
393 ),
394 (
394 (
395 r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
395 r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
396 "don't use old-style two-argument raise, use Exception(message)",
396 "don't use old-style two-argument raise, use Exception(message)",
397 ),
397 ),
398 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
398 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
399 (
399 (
400 r' [=!]=\s+(True|False|None)',
400 r' [=!]=\s+(True|False|None)',
401 "comparison with singleton, use 'is' or 'is not' instead",
401 "comparison with singleton, use 'is' or 'is not' instead",
402 ),
402 ),
403 (
403 (
404 r'^\s*(while|if) [01]:',
404 r'^\s*(while|if) [01]:',
405 "use True/False for constant Boolean expression",
405 "use True/False for constant Boolean expression",
406 ),
406 ),
407 (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`'),
408 (
408 (
409 r'(?:(?<!def)\s+|\()hasattr\(',
409 r'(?:(?<!def)\s+|\()hasattr\(',
410 '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) '
411 'instead',
411 'instead',
412 r'#.*hasattr-py3-only',
412 r'#.*hasattr-py3-only',
413 ),
413 ),
414 (r'opener\([^)]*\).read\(', "use opener.read() instead"),
414 (r'opener\([^)]*\).read\(', "use opener.read() instead"),
415 (r'opener\([^)]*\).write\(', "use opener.write() instead"),
415 (r'opener\([^)]*\).write\(', "use opener.write() instead"),
416 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
416 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
417 (r'\.debug\(\_', "don't mark debug messages for translation"),
417 (r'\.debug\(\_', "don't mark debug messages for translation"),
418 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
418 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
419 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
419 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
420 (
420 (
421 r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
421 r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
422 'legacy exception syntax; use "as" instead of ","',
422 'legacy exception syntax; use "as" instead of ","',
423 ),
423 ),
424 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
424 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
425 (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"),
426 (
426 (
427 r'os\.path\.join\(.*, *(""|\'\')\)',
427 r'os\.path\.join\(.*, *(""|\'\')\)',
428 "use pathutil.normasprefix(path) instead of os.path.join(path, '')",
428 "use pathutil.normasprefix(path) instead of os.path.join(path, '')",
429 ),
429 ),
430 (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"'),
431 # 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
432 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
432 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
433 (r'\butil\.Abort\b', "directly use error.Abort"),
433 (r'\butil\.Abort\b', "directly use error.Abort"),
434 (
434 (
435 r'^@(\w*\.)?cachefunc',
435 r'^@(\w*\.)?cachefunc',
436 "module-level @cachefunc is risky, please avoid",
436 "module-level @cachefunc is risky, please avoid",
437 ),
437 ),
438 (
438 (
439 r'^import Queue',
439 r'^import Queue',
440 "don't use Queue, use pycompat.queue.Queue + "
440 "don't use Queue, use pycompat.queue.Queue + "
441 "pycompat.queue.Empty",
441 "pycompat.queue.Empty",
442 ),
442 ),
443 (
443 (
444 r'^import cStringIO',
444 r'^import cStringIO',
445 "don't use cStringIO.StringIO, use util.stringio",
445 "don't use cStringIO.StringIO, use util.stringio",
446 ),
446 ),
447 (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"),
448 (
448 (
449 r'^import SocketServer',
449 r'^import SocketServer',
450 "don't use SockerServer, use util.socketserver",
450 "don't use SockerServer, use util.socketserver",
451 ),
451 ),
452 (r'^import urlparse', "don't use urlparse, use util.urlreq"),
452 (r'^import urlparse', "don't use urlparse, use util.urlreq"),
453 (r'^import xmlrpclib', "don't use xmlrpclib, use util.xmlrpclib"),
453 (r'^import xmlrpclib', "don't use xmlrpclib, use util.xmlrpclib"),
454 (r'^import cPickle', "don't use cPickle, use util.pickle"),
454 (r'^import cPickle', "don't use cPickle, use util.pickle"),
455 (r'^import pickle', "don't use pickle, use util.pickle"),
455 (r'^import pickle', "don't use pickle, use util.pickle"),
456 (r'^import httplib', "don't use httplib, use util.httplib"),
456 (r'^import httplib', "don't use httplib, use util.httplib"),
457 (r'^import BaseHTTPServer', "use util.httpserver instead"),
457 (r'^import BaseHTTPServer', "use util.httpserver instead"),
458 (
458 (
459 r'^(from|import) mercurial\.(cext|pure|cffi)',
459 r'^(from|import) mercurial\.(cext|pure|cffi)',
460 "use mercurial.policy.importmod instead",
460 "use mercurial.policy.importmod instead",
461 ),
461 ),
462 (r'\.next\(\)', "don't use .next(), use next(...)"),
462 (r'\.next\(\)', "don't use .next(), use next(...)"),
463 (
463 (
464 r'([a-z]*).revision\(\1\.node\(',
464 r'([a-z]*).revision\(\1\.node\(',
465 "don't convert rev to node before passing to revision(nodeorrev)",
465 "don't convert rev to node before passing to revision(nodeorrev)",
466 ),
466 ),
467 (r'platform\.system\(\)', "don't use platform.system(), use pycompat"),
467 (r'platform\.system\(\)', "don't use platform.system(), use pycompat"),
468 ],
468 ],
469 # warnings
469 # warnings
470 [],
470 [],
471 ]
471 ]
472
472
473 # patterns to check normal *.py files
473 # patterns to check normal *.py files
474 pypats = [
474 pypats = [
475 [
475 [
476 # Ideally, these should be placed in "commonpypats" for
476 # Ideally, these should be placed in "commonpypats" for
477 # consistency of coding rules in Mercurial source tree.
477 # consistency of coding rules in Mercurial source tree.
478 # 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
479 # python code fragments embedded in test scripts. Fixing test
479 # python code fragments embedded in test scripts. Fixing test
480 # scripts for these patterns requires many changes, and has less
480 # scripts for these patterns requires many changes, and has less
481 # profit than effort.
481 # profit than effort.
482 (r'raise Exception', "don't raise generic exceptions"),
482 (r'raise Exception', "don't raise generic exceptions"),
483 (r'[\s\(](open|file)\([^)]*\)\.read\(', "use util.readfile() instead"),
483 (r'[\s\(](open|file)\([^)]*\)\.read\(', "use util.readfile() instead"),
484 (
484 (
485 r'[\s\(](open|file)\([^)]*\)\.write\(',
485 r'[\s\(](open|file)\([^)]*\)\.write\(',
486 "use util.writefile() instead",
486 "use util.writefile() instead",
487 ),
487 ),
488 (
488 (
489 r'^[\s\(]*(open(er)?|file)\([^)]*\)(?!\.close\(\))',
489 r'^[\s\(]*(open(er)?|file)\([^)]*\)(?!\.close\(\))',
490 "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",
491 ),
491 ),
492 (
492 (
493 r'[\s\(](open|file)\([^)]*\)\.(?!close\(\))',
493 r'[\s\(](open|file)\([^)]*\)\.(?!close\(\))',
494 "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",
495 ),
495 ),
496 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
496 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
497 (r'^import atexit', "don't use atexit, use ui.atexit"),
497 (r'^import atexit', "don't use atexit, use ui.atexit"),
498 # rules depending on implementation of repquote()
498 # rules depending on implementation of repquote()
499 (
499 (
500 r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
500 r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
501 'string join across lines with no space',
501 'string join across lines with no space',
502 ),
502 ),
503 (
503 (
504 r'''(?x)ui\.(status|progress|write|note|warn)\(
504 r'''(?x)ui\.(status|progress|write|note|warn)\(
505 [ \t\n#]*
505 [ \t\n#]*
506 (?# any strings/comments might precede a string, which
506 (?# any strings/comments might precede a string, which
507 # contains translatable message)
507 # contains translatable message)
508 b?((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
508 b?((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
509 (?# sequence consisting of below might precede translatable message
509 (?# sequence consisting of below might precede translatable message
510 # - formatting string: "% 10s", "%05d", "% -3.2f", "%*s", "%%" ...
510 # - formatting string: "% 10s", "%05d", "% -3.2f", "%*s", "%%" ...
511 # - escaped character: "\\", "\n", "\0" ...
511 # - escaped character: "\\", "\n", "\0" ...
512 # - character other than '%', 'b' as '\', and 'x' as alphabet)
512 # - character other than '%', 'b' as '\', and 'x' as alphabet)
513 (['"]|\'\'\'|""")
513 (['"]|\'\'\'|""")
514 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
514 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
515 (?# this regexp can't use [^...] style,
515 (?# this regexp can't use [^...] style,
516 # because _preparepats forcibly adds "\n" into [^...],
516 # because _preparepats forcibly adds "\n" into [^...],
517 # even though this regexp wants match it against "\n")''',
517 # even though this regexp wants match it against "\n")''',
518 "missing _() in ui message (use () to hide false-positives)",
518 "missing _() in ui message (use () to hide false-positives)",
519 ),
519 ),
520 ]
520 ]
521 + commonpypats[0],
521 + commonpypats[0],
522 # warnings
522 # warnings
523 [
523 [
524 # rules depending on implementation of repquote()
524 # rules depending on implementation of repquote()
525 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
525 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
526 ]
526 ]
527 + commonpypats[1],
527 + commonpypats[1],
528 ]
528 ]
529
529
530 # patterns to check *.py for embedded ones in test script
530 # patterns to check *.py for embedded ones in test script
531 embeddedpypats = [
531 embeddedpypats = [
532 [] + commonpypats[0],
532 [] + commonpypats[0],
533 # warnings
533 # warnings
534 [] + commonpypats[1],
534 [] + commonpypats[1],
535 ]
535 ]
536
536
537 # common filters to convert *.py
537 # common filters to convert *.py
538 commonpyfilters = [
538 commonpyfilters = [
539 (
539 (
540 r"""(?msx)(?P<comment>\#.*?$)|
540 r"""(?msx)(?P<comment>\#.*?$)|
541 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
541 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
542 (?P<text>(([^\\]|\\.)*?))
542 (?P<text>(([^\\]|\\.)*?))
543 (?P=quote))""",
543 (?P=quote))""",
544 reppython,
544 reppython,
545 ),
545 ),
546 ]
546 ]
547
547
548 # pattern only for mercurial and extensions
549 core_py_pats = [
550 [
551 # Windows tend to get confused about capitalization of the drive letter
552 #
553 # see mercurial.windows.abspath for details
554 (
555 r'os\.path\.abspath',
556 "use util.abspath instead (windows)",
557 r'#.*re-exports',
558 ),
559 ],
560 # warnings
561 [],
562 ]
563
548 # filters to convert normal *.py files
564 # filters to convert normal *.py files
549 pyfilters = [] + commonpyfilters
565 pyfilters = [] + commonpyfilters
550
566
551 # non-filter patterns
567 # non-filter patterns
552 pynfpats = [
568 pynfpats = [
553 [
569 [
554 (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"),
570 (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"),
555 (r'pycompat\.osname\s*[=!]=\s*[\'"]posix[\'"]', "use pycompat.isposix"),
571 (r'pycompat\.osname\s*[=!]=\s*[\'"]posix[\'"]', "use pycompat.isposix"),
556 (
572 (
557 r'pycompat\.sysplatform\s*[!=]=\s*[\'"]darwin[\'"]',
573 r'pycompat\.sysplatform\s*[!=]=\s*[\'"]darwin[\'"]',
558 "use pycompat.isdarwin",
574 "use pycompat.isdarwin",
559 ),
575 ),
560 ],
576 ],
561 # warnings
577 # warnings
562 [],
578 [],
563 ]
579 ]
564
580
565 # filters to convert *.py for embedded ones in test script
581 # filters to convert *.py for embedded ones in test script
566 embeddedpyfilters = [] + commonpyfilters
582 embeddedpyfilters = [] + commonpyfilters
567
583
568 # extension non-filter patterns
584 # extension non-filter patterns
569 pyextnfpats = [
585 pyextnfpats = [
570 [(r'^"""\n?[A-Z]', "don't capitalize docstring title")],
586 [(r'^"""\n?[A-Z]', "don't capitalize docstring title")],
571 # warnings
587 # warnings
572 [],
588 [],
573 ]
589 ]
574
590
575 txtfilters = []
591 txtfilters = []
576
592
577 txtpats = [
593 txtpats = [
578 [
594 [
579 (r'\s$', 'trailing whitespace'),
595 (r'\s$', 'trailing whitespace'),
580 ('.. note::[ \n][^\n]', 'add two newlines after note::'),
596 ('.. note::[ \n][^\n]', 'add two newlines after note::'),
581 ],
597 ],
582 [],
598 [],
583 ]
599 ]
584
600
585 cpats = [
601 cpats = [
586 [
602 [
587 (r'//', "don't use //-style comments"),
603 (r'//', "don't use //-style comments"),
588 (r'\S\t', "don't use tabs except for indent"),
604 (r'\S\t', "don't use tabs except for indent"),
589 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
605 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
590 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
606 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
591 (r'return\(', "return is not a function"),
607 (r'return\(', "return is not a function"),
592 (r' ;', "no space before ;"),
608 (r' ;', "no space before ;"),
593 (r'[^;] \)', "no space before )"),
609 (r'[^;] \)', "no space before )"),
594 (r'[)][{]', "space between ) and {"),
610 (r'[)][{]', "space between ) and {"),
595 (r'\w+\* \w+', "use int *foo, not int* foo"),
611 (r'\w+\* \w+', "use int *foo, not int* foo"),
596 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
612 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
597 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
613 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
598 (r'\w,\w', "missing whitespace after ,"),
614 (r'\w,\w', "missing whitespace after ,"),
599 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
615 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
600 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
616 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
601 (r'^#\s+\w', "use #foo, not # foo"),
617 (r'^#\s+\w', "use #foo, not # foo"),
602 (r'[^\n]\Z', "no trailing newline"),
618 (r'[^\n]\Z', "no trailing newline"),
603 (r'^\s*#import\b', "use only #include in standard C code"),
619 (r'^\s*#import\b', "use only #include in standard C code"),
604 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
620 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
605 (r'strcat\(', "don't use strcat"),
621 (r'strcat\(', "don't use strcat"),
606 # rules depending on implementation of repquote()
622 # rules depending on implementation of repquote()
607 ],
623 ],
608 # warnings
624 # warnings
609 [
625 [
610 # rules depending on implementation of repquote()
626 # rules depending on implementation of repquote()
611 ],
627 ],
612 ]
628 ]
613
629
614 cfilters = [
630 cfilters = [
615 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
631 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
616 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
632 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
617 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
633 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
618 (r'(\()([^)]+\))', repcallspaces),
634 (r'(\()([^)]+\))', repcallspaces),
619 ]
635 ]
620
636
621 inutilpats = [
637 inutilpats = [
622 [
638 [
623 (r'\bui\.', "don't use ui in util"),
639 (r'\bui\.', "don't use ui in util"),
624 ],
640 ],
625 # warnings
641 # warnings
626 [],
642 [],
627 ]
643 ]
628
644
629 inrevlogpats = [
645 inrevlogpats = [
630 [
646 [
631 (r'\brepo\.', "don't use repo in revlog"),
647 (r'\brepo\.', "don't use repo in revlog"),
632 ],
648 ],
633 # warnings
649 # warnings
634 [],
650 [],
635 ]
651 ]
636
652
637 webtemplatefilters = []
653 webtemplatefilters = []
638
654
639 webtemplatepats = [
655 webtemplatepats = [
640 [],
656 [],
641 [
657 [
642 (
658 (
643 r'{desc(\|(?!websub|firstline)[^\|]*)+}',
659 r'{desc(\|(?!websub|firstline)[^\|]*)+}',
644 'follow desc keyword with either firstline or websub',
660 'follow desc keyword with either firstline or websub',
645 ),
661 ),
646 ],
662 ],
647 ]
663 ]
648
664
649 allfilesfilters = []
665 allfilesfilters = []
650
666
651 allfilespats = [
667 allfilespats = [
652 [
668 [
653 (
669 (
654 r'(http|https)://[a-zA-Z0-9./]*selenic.com/',
670 r'(http|https)://[a-zA-Z0-9./]*selenic.com/',
655 'use mercurial-scm.org domain URL',
671 'use mercurial-scm.org domain URL',
656 ),
672 ),
657 (
673 (
658 r'mercurial@selenic\.com',
674 r'mercurial@selenic\.com',
659 'use mercurial-scm.org domain for mercurial ML address',
675 'use mercurial-scm.org domain for mercurial ML address',
660 ),
676 ),
661 (
677 (
662 r'mercurial-devel@selenic\.com',
678 r'mercurial-devel@selenic\.com',
663 'use mercurial-scm.org domain for mercurial-devel ML address',
679 'use mercurial-scm.org domain for mercurial-devel ML address',
664 ),
680 ),
665 ],
681 ],
666 # warnings
682 # warnings
667 [],
683 [],
668 ]
684 ]
669
685
670 py3pats = [
686 py3pats = [
671 [
687 [
672 (
688 (
673 r'os\.environ',
689 r'os\.environ',
674 "use encoding.environ instead (py3)",
690 "use encoding.environ instead (py3)",
675 r'#.*re-exports',
691 r'#.*re-exports',
676 ),
692 ),
677 (r'os\.name', "use pycompat.osname instead (py3)"),
693 (r'os\.name', "use pycompat.osname instead (py3)"),
678 (r'os\.getcwd', "use encoding.getcwd instead (py3)", r'#.*re-exports'),
694 (r'os\.getcwd', "use encoding.getcwd instead (py3)", r'#.*re-exports'),
679 (r'os\.sep', "use pycompat.ossep instead (py3)"),
695 (r'os\.sep', "use pycompat.ossep instead (py3)"),
680 (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"),
696 (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"),
681 (r'os\.altsep', "use pycompat.osaltsep instead (py3)"),
697 (r'os\.altsep', "use pycompat.osaltsep instead (py3)"),
682 (r'sys\.platform', "use pycompat.sysplatform instead (py3)"),
698 (r'sys\.platform', "use pycompat.sysplatform instead (py3)"),
683 (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"),
699 (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"),
684 (r'os\.getenv', "use encoding.environ.get instead"),
700 (r'os\.getenv', "use encoding.environ.get instead"),
685 (r'os\.setenv', "modifying the environ dict is not preferred"),
701 (r'os\.setenv', "modifying the environ dict is not preferred"),
686 (r'(?<!pycompat\.)xrange', "use pycompat.xrange instead (py3)"),
702 (r'(?<!pycompat\.)xrange', "use pycompat.xrange instead (py3)"),
687 ],
703 ],
688 # warnings
704 # warnings
689 [],
705 [],
690 ]
706 ]
691
707
692 checks = [
708 checks = [
693 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
709 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
694 ('python', r'.*\.(py|cgi)$', r'^#!.*python', [], pynfpats),
710 ('python', r'.*\.(py|cgi)$', r'^#!.*python', [], pynfpats),
695 ('python', r'.*hgext.*\.py$', '', [], pyextnfpats),
711 ('python', r'.*hgext.*\.py$', '', [], pyextnfpats),
696 (
712 (
697 'python 3',
713 'python 3',
698 r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
714 r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
699 '',
715 '',
700 pyfilters,
716 pyfilters,
701 py3pats,
717 py3pats,
702 ),
718 ),
719 (
720 'core files',
721 r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
722 '',
723 pyfilters,
724 core_py_pats,
725 ),
703 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
726 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
704 ('c', r'.*\.[ch]$', '', cfilters, cpats),
727 ('c', r'.*\.[ch]$', '', cfilters, cpats),
705 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
728 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
706 (
729 (
707 'layering violation repo in revlog',
730 'layering violation repo in revlog',
708 r'mercurial/revlog\.py',
731 r'mercurial/revlog\.py',
709 '',
732 '',
710 pyfilters,
733 pyfilters,
711 inrevlogpats,
734 inrevlogpats,
712 ),
735 ),
713 (
736 (
714 'layering violation ui in util',
737 'layering violation ui in util',
715 r'mercurial/util\.py',
738 r'mercurial/util\.py',
716 '',
739 '',
717 pyfilters,
740 pyfilters,
718 inutilpats,
741 inutilpats,
719 ),
742 ),
720 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
743 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
721 (
744 (
722 'web template',
745 'web template',
723 r'mercurial/templates/.*\.tmpl',
746 r'mercurial/templates/.*\.tmpl',
724 '',
747 '',
725 webtemplatefilters,
748 webtemplatefilters,
726 webtemplatepats,
749 webtemplatepats,
727 ),
750 ),
728 ('all except for .po', r'.*(?<!\.po)$', '', allfilesfilters, allfilespats),
751 ('all except for .po', r'.*(?<!\.po)$', '', allfilesfilters, allfilespats),
729 ]
752 ]
730
753
731 # (desc,
754 # (desc,
732 # func to pick up embedded code fragments,
755 # func to pick up embedded code fragments,
733 # list of patterns to convert target files
756 # list of patterns to convert target files
734 # list of patterns to detect errors/warnings)
757 # list of patterns to detect errors/warnings)
735 embeddedchecks = [
758 embeddedchecks = [
736 (
759 (
737 'embedded python',
760 'embedded python',
738 testparseutil.pyembedded,
761 testparseutil.pyembedded,
739 embeddedpyfilters,
762 embeddedpyfilters,
740 embeddedpypats,
763 embeddedpypats,
741 )
764 )
742 ]
765 ]
743
766
744
767
745 def _preparepats():
768 def _preparepats():
746 def preparefailandwarn(failandwarn):
769 def preparefailandwarn(failandwarn):
747 for pats in failandwarn:
770 for pats in failandwarn:
748 for i, pseq in enumerate(pats):
771 for i, pseq in enumerate(pats):
749 # fix-up regexes for multi-line searches
772 # fix-up regexes for multi-line searches
750 p = pseq[0]
773 p = pseq[0]
751 # \s doesn't match \n (done in two steps)
774 # \s doesn't match \n (done in two steps)
752 # first, we replace \s that appears in a set already
775 # first, we replace \s that appears in a set already
753 p = re.sub(r'\[\\s', r'[ \\t', p)
776 p = re.sub(r'\[\\s', r'[ \\t', p)
754 # now we replace other \s instances.
777 # now we replace other \s instances.
755 p = re.sub(r'(?<!(\\|\[))\\s', r'[ \\t]', p)
778 p = re.sub(r'(?<!(\\|\[))\\s', r'[ \\t]', p)
756 # [^...] doesn't match newline
779 # [^...] doesn't match newline
757 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
780 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
758
781
759 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
782 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
760
783
761 def preparefilters(filters):
784 def preparefilters(filters):
762 for i, flt in enumerate(filters):
785 for i, flt in enumerate(filters):
763 filters[i] = re.compile(flt[0]), flt[1]
786 filters[i] = re.compile(flt[0]), flt[1]
764
787
765 for cs in (checks, embeddedchecks):
788 for cs in (checks, embeddedchecks):
766 for c in cs:
789 for c in cs:
767 failandwarn = c[-1]
790 failandwarn = c[-1]
768 preparefailandwarn(failandwarn)
791 preparefailandwarn(failandwarn)
769
792
770 filters = c[-2]
793 filters = c[-2]
771 preparefilters(filters)
794 preparefilters(filters)
772
795
773
796
774 class norepeatlogger(object):
797 class norepeatlogger(object):
775 def __init__(self):
798 def __init__(self):
776 self._lastseen = None
799 self._lastseen = None
777
800
778 def log(self, fname, lineno, line, msg, blame):
801 def log(self, fname, lineno, line, msg, blame):
779 """print error related a to given line of a given file.
802 """print error related a to given line of a given file.
780
803
781 The faulty line will also be printed but only once in the case
804 The faulty line will also be printed but only once in the case
782 of multiple errors.
805 of multiple errors.
783
806
784 :fname: filename
807 :fname: filename
785 :lineno: line number
808 :lineno: line number
786 :line: actual content of the line
809 :line: actual content of the line
787 :msg: error message
810 :msg: error message
788 """
811 """
789 msgid = fname, lineno, line
812 msgid = fname, lineno, line
790 if msgid != self._lastseen:
813 if msgid != self._lastseen:
791 if blame:
814 if blame:
792 print("%s:%d (%s):" % (fname, lineno, blame))
815 print("%s:%d (%s):" % (fname, lineno, blame))
793 else:
816 else:
794 print("%s:%d:" % (fname, lineno))
817 print("%s:%d:" % (fname, lineno))
795 print(" > %s" % line)
818 print(" > %s" % line)
796 self._lastseen = msgid
819 self._lastseen = msgid
797 print(" " + msg)
820 print(" " + msg)
798
821
799
822
800 _defaultlogger = norepeatlogger()
823 _defaultlogger = norepeatlogger()
801
824
802
825
803 def getblame(f):
826 def getblame(f):
804 lines = []
827 lines = []
805 for l in os.popen('hg annotate -un %s' % f):
828 for l in os.popen('hg annotate -un %s' % f):
806 start, line = l.split(':', 1)
829 start, line = l.split(':', 1)
807 user, rev = start.split()
830 user, rev = start.split()
808 lines.append((line[1:-1], user, rev))
831 lines.append((line[1:-1], user, rev))
809 return lines
832 return lines
810
833
811
834
812 def checkfile(
835 def checkfile(
813 f,
836 f,
814 logfunc=_defaultlogger.log,
837 logfunc=_defaultlogger.log,
815 maxerr=None,
838 maxerr=None,
816 warnings=False,
839 warnings=False,
817 blame=False,
840 blame=False,
818 debug=False,
841 debug=False,
819 lineno=True,
842 lineno=True,
820 ):
843 ):
821 """checks style and portability of a given file
844 """checks style and portability of a given file
822
845
823 :f: filepath
846 :f: filepath
824 :logfunc: function used to report error
847 :logfunc: function used to report error
825 logfunc(filename, linenumber, linecontent, errormessage)
848 logfunc(filename, linenumber, linecontent, errormessage)
826 :maxerr: number of error to display before aborting.
849 :maxerr: number of error to display before aborting.
827 Set to false (default) to report all errors
850 Set to false (default) to report all errors
828
851
829 return True if no error is found, False otherwise.
852 return True if no error is found, False otherwise.
830 """
853 """
831 result = True
854 result = True
832
855
833 try:
856 try:
834 with opentext(f) as fp:
857 with opentext(f) as fp:
835 try:
858 try:
836 pre = fp.read()
859 pre = fp.read()
837 except UnicodeDecodeError as e:
860 except UnicodeDecodeError as e:
838 print("%s while reading %s" % (e, f))
861 print("%s while reading %s" % (e, f))
839 return result
862 return result
840 except IOError as e:
863 except IOError as e:
841 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
864 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
842 return result
865 return result
843
866
844 # context information shared while single checkfile() invocation
867 # context information shared while single checkfile() invocation
845 context = {'blamecache': None}
868 context = {'blamecache': None}
846
869
847 for name, match, magic, filters, pats in checks:
870 for name, match, magic, filters, pats in checks:
848 if debug:
871 if debug:
849 print(name, f)
872 print(name, f)
850 if not (re.match(match, f) or (magic and re.search(magic, pre))):
873 if not (re.match(match, f) or (magic and re.search(magic, pre))):
851 if debug:
874 if debug:
852 print(
875 print(
853 "Skipping %s for %s it doesn't match %s" % (name, match, f)
876 "Skipping %s for %s it doesn't match %s" % (name, match, f)
854 )
877 )
855 continue
878 continue
856 if "no-" "check-code" in pre:
879 if "no-" "check-code" in pre:
857 # If you're looking at this line, it's because a file has:
880 # If you're looking at this line, it's because a file has:
858 # no- check- code
881 # no- check- code
859 # but the reason to output skipping is to make life for
882 # but the reason to output skipping is to make life for
860 # tests easier. So, instead of writing it with a normal
883 # tests easier. So, instead of writing it with a normal
861 # spelling, we write it with the expected spelling from
884 # spelling, we write it with the expected spelling from
862 # tests/test-check-code.t
885 # tests/test-check-code.t
863 print("Skipping %s it has no-che?k-code (glob)" % f)
886 print("Skipping %s it has no-che?k-code (glob)" % f)
864 return "Skip" # skip checking this file
887 return "Skip" # skip checking this file
865
888
866 fc = _checkfiledata(
889 fc = _checkfiledata(
867 name,
890 name,
868 f,
891 f,
869 pre,
892 pre,
870 filters,
893 filters,
871 pats,
894 pats,
872 context,
895 context,
873 logfunc,
896 logfunc,
874 maxerr,
897 maxerr,
875 warnings,
898 warnings,
876 blame,
899 blame,
877 debug,
900 debug,
878 lineno,
901 lineno,
879 )
902 )
880 if fc:
903 if fc:
881 result = False
904 result = False
882
905
883 if f.endswith('.t') and "no-" "check-code" not in pre:
906 if f.endswith('.t') and "no-" "check-code" not in pre:
884 if debug:
907 if debug:
885 print("Checking embedded code in %s" % f)
908 print("Checking embedded code in %s" % f)
886
909
887 prelines = pre.splitlines()
910 prelines = pre.splitlines()
888 embeddederros = []
911 embeddederros = []
889 for name, embedded, filters, pats in embeddedchecks:
912 for name, embedded, filters, pats in embeddedchecks:
890 # "reset curmax at each repetition" treats maxerr as "max
913 # "reset curmax at each repetition" treats maxerr as "max
891 # nubmer of errors in an actual file per entry of
914 # nubmer of errors in an actual file per entry of
892 # (embedded)checks"
915 # (embedded)checks"
893 curmaxerr = maxerr
916 curmaxerr = maxerr
894
917
895 for found in embedded(f, prelines, embeddederros):
918 for found in embedded(f, prelines, embeddederros):
896 filename, starts, ends, code = found
919 filename, starts, ends, code = found
897 fc = _checkfiledata(
920 fc = _checkfiledata(
898 name,
921 name,
899 f,
922 f,
900 code,
923 code,
901 filters,
924 filters,
902 pats,
925 pats,
903 context,
926 context,
904 logfunc,
927 logfunc,
905 curmaxerr,
928 curmaxerr,
906 warnings,
929 warnings,
907 blame,
930 blame,
908 debug,
931 debug,
909 lineno,
932 lineno,
910 offset=starts - 1,
933 offset=starts - 1,
911 )
934 )
912 if fc:
935 if fc:
913 result = False
936 result = False
914 if curmaxerr:
937 if curmaxerr:
915 if fc >= curmaxerr:
938 if fc >= curmaxerr:
916 break
939 break
917 curmaxerr -= fc
940 curmaxerr -= fc
918
941
919 return result
942 return result
920
943
921
944
922 def _checkfiledata(
945 def _checkfiledata(
923 name,
946 name,
924 f,
947 f,
925 filedata,
948 filedata,
926 filters,
949 filters,
927 pats,
950 pats,
928 context,
951 context,
929 logfunc,
952 logfunc,
930 maxerr,
953 maxerr,
931 warnings,
954 warnings,
932 blame,
955 blame,
933 debug,
956 debug,
934 lineno,
957 lineno,
935 offset=None,
958 offset=None,
936 ):
959 ):
937 """Execute actual error check for file data
960 """Execute actual error check for file data
938
961
939 :name: of the checking category
962 :name: of the checking category
940 :f: filepath
963 :f: filepath
941 :filedata: content of a file
964 :filedata: content of a file
942 :filters: to be applied before checking
965 :filters: to be applied before checking
943 :pats: to detect errors
966 :pats: to detect errors
944 :context: a dict of information shared while single checkfile() invocation
967 :context: a dict of information shared while single checkfile() invocation
945 Valid keys: 'blamecache'.
968 Valid keys: 'blamecache'.
946 :logfunc: function used to report error
969 :logfunc: function used to report error
947 logfunc(filename, linenumber, linecontent, errormessage)
970 logfunc(filename, linenumber, linecontent, errormessage)
948 :maxerr: number of error to display before aborting, or False to
971 :maxerr: number of error to display before aborting, or False to
949 report all errors
972 report all errors
950 :warnings: whether warning level checks should be applied
973 :warnings: whether warning level checks should be applied
951 :blame: whether blame information should be displayed at error reporting
974 :blame: whether blame information should be displayed at error reporting
952 :debug: whether debug information should be displayed
975 :debug: whether debug information should be displayed
953 :lineno: whether lineno should be displayed at error reporting
976 :lineno: whether lineno should be displayed at error reporting
954 :offset: line number offset of 'filedata' in 'f' for checking
977 :offset: line number offset of 'filedata' in 'f' for checking
955 an embedded code fragment, or None (offset=0 is different
978 an embedded code fragment, or None (offset=0 is different
956 from offset=None)
979 from offset=None)
957
980
958 returns number of detected errors.
981 returns number of detected errors.
959 """
982 """
960 blamecache = context['blamecache']
983 blamecache = context['blamecache']
961 if offset is None:
984 if offset is None:
962 lineoffset = 0
985 lineoffset = 0
963 else:
986 else:
964 lineoffset = offset
987 lineoffset = offset
965
988
966 fc = 0
989 fc = 0
967 pre = post = filedata
990 pre = post = filedata
968
991
969 if True: # TODO: get rid of this redundant 'if' block
992 if True: # TODO: get rid of this redundant 'if' block
970 for p, r in filters:
993 for p, r in filters:
971 post = re.sub(p, r, post)
994 post = re.sub(p, r, post)
972 nerrs = len(pats[0]) # nerr elements are errors
995 nerrs = len(pats[0]) # nerr elements are errors
973 if warnings:
996 if warnings:
974 pats = pats[0] + pats[1]
997 pats = pats[0] + pats[1]
975 else:
998 else:
976 pats = pats[0]
999 pats = pats[0]
977 # print post # uncomment to show filtered version
1000 # print post # uncomment to show filtered version
978
1001
979 if debug:
1002 if debug:
980 print("Checking %s for %s" % (name, f))
1003 print("Checking %s for %s" % (name, f))
981
1004
982 prelines = None
1005 prelines = None
983 errors = []
1006 errors = []
984 for i, pat in enumerate(pats):
1007 for i, pat in enumerate(pats):
985 if len(pat) == 3:
1008 if len(pat) == 3:
986 p, msg, ignore = pat
1009 p, msg, ignore = pat
987 else:
1010 else:
988 p, msg = pat
1011 p, msg = pat
989 ignore = None
1012 ignore = None
990 if i >= nerrs:
1013 if i >= nerrs:
991 msg = "warning: " + msg
1014 msg = "warning: " + msg
992
1015
993 pos = 0
1016 pos = 0
994 n = 0
1017 n = 0
995 for m in p.finditer(post):
1018 for m in p.finditer(post):
996 if prelines is None:
1019 if prelines is None:
997 prelines = pre.splitlines()
1020 prelines = pre.splitlines()
998 postlines = post.splitlines(True)
1021 postlines = post.splitlines(True)
999
1022
1000 start = m.start()
1023 start = m.start()
1001 while n < len(postlines):
1024 while n < len(postlines):
1002 step = len(postlines[n])
1025 step = len(postlines[n])
1003 if pos + step > start:
1026 if pos + step > start:
1004 break
1027 break
1005 pos += step
1028 pos += step
1006 n += 1
1029 n += 1
1007 l = prelines[n]
1030 l = prelines[n]
1008
1031
1009 if ignore and re.search(ignore, l, re.MULTILINE):
1032 if ignore and re.search(ignore, l, re.MULTILINE):
1010 if debug:
1033 if debug:
1011 print(
1034 print(
1012 "Skipping %s for %s:%s (ignore pattern)"
1035 "Skipping %s for %s:%s (ignore pattern)"
1013 % (name, f, (n + lineoffset))
1036 % (name, f, (n + lineoffset))
1014 )
1037 )
1015 continue
1038 continue
1016 bd = ""
1039 bd = ""
1017 if blame:
1040 if blame:
1018 bd = 'working directory'
1041 bd = 'working directory'
1019 if blamecache is None:
1042 if blamecache is None:
1020 blamecache = getblame(f)
1043 blamecache = getblame(f)
1021 context['blamecache'] = blamecache
1044 context['blamecache'] = blamecache
1022 if (n + lineoffset) < len(blamecache):
1045 if (n + lineoffset) < len(blamecache):
1023 bl, bu, br = blamecache[(n + lineoffset)]
1046 bl, bu, br = blamecache[(n + lineoffset)]
1024 if offset is None and bl == l:
1047 if offset is None and bl == l:
1025 bd = '%s@%s' % (bu, br)
1048 bd = '%s@%s' % (bu, br)
1026 elif offset is not None and bl.endswith(l):
1049 elif offset is not None and bl.endswith(l):
1027 # "offset is not None" means "checking
1050 # "offset is not None" means "checking
1028 # embedded code fragment". In this case,
1051 # embedded code fragment". In this case,
1029 # "l" does not have information about the
1052 # "l" does not have information about the
1030 # beginning of an *original* line in the
1053 # beginning of an *original* line in the
1031 # file (e.g. ' > ').
1054 # file (e.g. ' > ').
1032 # Therefore, use "str.endswith()", and
1055 # Therefore, use "str.endswith()", and
1033 # show "maybe" for a little loose
1056 # show "maybe" for a little loose
1034 # examination.
1057 # examination.
1035 bd = '%s@%s, maybe' % (bu, br)
1058 bd = '%s@%s, maybe' % (bu, br)
1036
1059
1037 errors.append((f, lineno and (n + lineoffset + 1), l, msg, bd))
1060 errors.append((f, lineno and (n + lineoffset + 1), l, msg, bd))
1038
1061
1039 errors.sort()
1062 errors.sort()
1040 for e in errors:
1063 for e in errors:
1041 logfunc(*e)
1064 logfunc(*e)
1042 fc += 1
1065 fc += 1
1043 if maxerr and fc >= maxerr:
1066 if maxerr and fc >= maxerr:
1044 print(" (too many errors, giving up)")
1067 print(" (too many errors, giving up)")
1045 break
1068 break
1046
1069
1047 return fc
1070 return fc
1048
1071
1049
1072
1050 def main():
1073 def main():
1051 parser = optparse.OptionParser("%prog [options] [files | -]")
1074 parser = optparse.OptionParser("%prog [options] [files | -]")
1052 parser.add_option(
1075 parser.add_option(
1053 "-w",
1076 "-w",
1054 "--warnings",
1077 "--warnings",
1055 action="store_true",
1078 action="store_true",
1056 help="include warning-level checks",
1079 help="include warning-level checks",
1057 )
1080 )
1058 parser.add_option(
1081 parser.add_option(
1059 "-p", "--per-file", type="int", help="max warnings per file"
1082 "-p", "--per-file", type="int", help="max warnings per file"
1060 )
1083 )
1061 parser.add_option(
1084 parser.add_option(
1062 "-b",
1085 "-b",
1063 "--blame",
1086 "--blame",
1064 action="store_true",
1087 action="store_true",
1065 help="use annotate to generate blame info",
1088 help="use annotate to generate blame info",
1066 )
1089 )
1067 parser.add_option(
1090 parser.add_option(
1068 "", "--debug", action="store_true", help="show debug information"
1091 "", "--debug", action="store_true", help="show debug information"
1069 )
1092 )
1070 parser.add_option(
1093 parser.add_option(
1071 "",
1094 "",
1072 "--nolineno",
1095 "--nolineno",
1073 action="store_false",
1096 action="store_false",
1074 dest='lineno',
1097 dest='lineno',
1075 help="don't show line numbers",
1098 help="don't show line numbers",
1076 )
1099 )
1077
1100
1078 parser.set_defaults(
1101 parser.set_defaults(
1079 per_file=15, warnings=False, blame=False, debug=False, lineno=True
1102 per_file=15, warnings=False, blame=False, debug=False, lineno=True
1080 )
1103 )
1081 (options, args) = parser.parse_args()
1104 (options, args) = parser.parse_args()
1082
1105
1083 if len(args) == 0:
1106 if len(args) == 0:
1084 check = glob.glob("*")
1107 check = glob.glob("*")
1085 elif args == ['-']:
1108 elif args == ['-']:
1086 # read file list from stdin
1109 # read file list from stdin
1087 check = sys.stdin.read().splitlines()
1110 check = sys.stdin.read().splitlines()
1088 else:
1111 else:
1089 check = args
1112 check = args
1090
1113
1091 _preparepats()
1114 _preparepats()
1092
1115
1093 ret = 0
1116 ret = 0
1094 for f in check:
1117 for f in check:
1095 if not checkfile(
1118 if not checkfile(
1096 f,
1119 f,
1097 maxerr=options.per_file,
1120 maxerr=options.per_file,
1098 warnings=options.warnings,
1121 warnings=options.warnings,
1099 blame=options.blame,
1122 blame=options.blame,
1100 debug=options.debug,
1123 debug=options.debug,
1101 lineno=options.lineno,
1124 lineno=options.lineno,
1102 ):
1125 ):
1103 ret = 1
1126 ret = 1
1104 return ret
1127 return ret
1105
1128
1106
1129
1107 if __name__ == "__main__":
1130 if __name__ == "__main__":
1108 sys.exit(main())
1131 sys.exit(main())
General Comments 0
You need to be logged in to leave comments. Login now