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