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