##// END OF EJS Templates
check-code: prohibit `if False` antipattern...
Augie Fackler -
r33369:d36bcba9 default
parent child Browse files
Show More
@@ -1,717 +1,718 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'[\s="`\']python\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'^\s*if False(:| +and)', 'Remove code instead of using `if False`'),
304 (r'(?:(?<!def)\s+|\()hasattr\(',
305 (r'(?:(?<!def)\s+|\()hasattr\(',
305 'hasattr(foo, bar) is broken on py2, use util.safehasattr(foo, bar) '
306 'hasattr(foo, bar) is broken on py2, use util.safehasattr(foo, bar) '
306 'instead', r'#.*hasattr-py3-only'),
307 'instead', r'#.*hasattr-py3-only'),
307 (r'opener\([^)]*\).read\(',
308 (r'opener\([^)]*\).read\(',
308 "use opener.read() instead"),
309 "use opener.read() instead"),
309 (r'opener\([^)]*\).write\(',
310 (r'opener\([^)]*\).write\(',
310 "use opener.write() instead"),
311 "use opener.write() instead"),
311 (r'[\s\(](open|file)\([^)]*\)\.read\(',
312 (r'[\s\(](open|file)\([^)]*\)\.read\(',
312 "use util.readfile() instead"),
313 "use util.readfile() instead"),
313 (r'[\s\(](open|file)\([^)]*\)\.write\(',
314 (r'[\s\(](open|file)\([^)]*\)\.write\(',
314 "use util.writefile() instead"),
315 "use util.writefile() instead"),
315 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
316 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
316 "always assign an opened file to a variable, and close it afterwards"),
317 "always assign an opened file to a variable, and close it afterwards"),
317 (r'[\s\(](open|file)\([^)]*\)\.',
318 (r'[\s\(](open|file)\([^)]*\)\.',
318 "always assign an opened file to a variable, and close it afterwards"),
319 "always assign an opened file to a variable, and close it afterwards"),
319 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
320 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
320 (r'\.debug\(\_', "don't mark debug messages for translation"),
321 (r'\.debug\(\_', "don't mark debug messages for translation"),
321 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
322 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
322 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
323 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
323 (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
324 (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
324 'legacy exception syntax; use "as" instead of ","'),
325 'legacy exception syntax; use "as" instead of ","'),
325 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
326 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
326 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
327 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
327 (r'\bdef\s+__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
328 (r'\bdef\s+__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
328 (r'os\.path\.join\(.*, *(""|\'\')\)',
329 (r'os\.path\.join\(.*, *(""|\'\')\)',
329 "use pathutil.normasprefix(path) instead of os.path.join(path, '')"),
330 "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"'),
331 (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
332 # XXX only catch mutable arguments on the first line of the definition
332 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
333 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
333 (r'\butil\.Abort\b', "directly use error.Abort"),
334 (r'\butil\.Abort\b', "directly use error.Abort"),
334 (r'^@(\w*\.)?cachefunc', "module-level @cachefunc is risky, please avoid"),
335 (r'^@(\w*\.)?cachefunc', "module-level @cachefunc is risky, please avoid"),
335 (r'^import Queue', "don't use Queue, use util.queue + util.empty"),
336 (r'^import Queue', "don't use Queue, use util.queue + util.empty"),
336 (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"),
337 (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"),
337 (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
338 (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
338 (r'^import SocketServer', "don't use SockerServer, use util.socketserver"),
339 (r'^import SocketServer', "don't use SockerServer, use util.socketserver"),
339 (r'^import urlparse', "don't use urlparse, use util.urlreq"),
340 (r'^import urlparse', "don't use urlparse, use util.urlreq"),
340 (r'^import xmlrpclib', "don't use xmlrpclib, use util.xmlrpclib"),
341 (r'^import xmlrpclib', "don't use xmlrpclib, use util.xmlrpclib"),
341 (r'^import cPickle', "don't use cPickle, use util.pickle"),
342 (r'^import cPickle', "don't use cPickle, use util.pickle"),
342 (r'^import pickle', "don't use pickle, use util.pickle"),
343 (r'^import pickle', "don't use pickle, use util.pickle"),
343 (r'^import httplib', "don't use httplib, use util.httplib"),
344 (r'^import httplib', "don't use httplib, use util.httplib"),
344 (r'^import BaseHTTPServer', "use util.httpserver instead"),
345 (r'^import BaseHTTPServer', "use util.httpserver instead"),
345 (r'^(from|import) mercurial\.(cext|pure|cffi)',
346 (r'^(from|import) mercurial\.(cext|pure|cffi)',
346 "use mercurial.policy.importmod instead"),
347 "use mercurial.policy.importmod instead"),
347 (r'\.next\(\)', "don't use .next(), use next(...)"),
348 (r'\.next\(\)', "don't use .next(), use next(...)"),
348 (r'([a-z]*).revision\(\1\.node\(',
349 (r'([a-z]*).revision\(\1\.node\(',
349 "don't convert rev to node before passing to revision(nodeorrev)"),
350 "don't convert rev to node before passing to revision(nodeorrev)"),
350
351
351 # rules depending on implementation of repquote()
352 # rules depending on implementation of repquote()
352 (r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
353 (r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
353 'string join across lines with no space'),
354 'string join across lines with no space'),
354 (r'''(?x)ui\.(status|progress|write|note|warn)\(
355 (r'''(?x)ui\.(status|progress|write|note|warn)\(
355 [ \t\n#]*
356 [ \t\n#]*
356 (?# any strings/comments might precede a string, which
357 (?# any strings/comments might precede a string, which
357 # contains translatable message)
358 # contains translatable message)
358 ((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
359 ((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
359 (?# sequence consisting of below might precede translatable message
360 (?# sequence consisting of below might precede translatable message
360 # - formatting string: "% 10s", "%05d", "% -3.2f", "%*s", "%%" ...
361 # - formatting string: "% 10s", "%05d", "% -3.2f", "%*s", "%%" ...
361 # - escaped character: "\\", "\n", "\0" ...
362 # - escaped character: "\\", "\n", "\0" ...
362 # - character other than '%', 'b' as '\', and 'x' as alphabet)
363 # - character other than '%', 'b' as '\', and 'x' as alphabet)
363 (['"]|\'\'\'|""")
364 (['"]|\'\'\'|""")
364 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
365 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
365 (?# this regexp can't use [^...] style,
366 (?# this regexp can't use [^...] style,
366 # because _preparepats forcibly adds "\n" into [^...],
367 # because _preparepats forcibly adds "\n" into [^...],
367 # even though this regexp wants match it against "\n")''',
368 # even though this regexp wants match it against "\n")''',
368 "missing _() in ui message (use () to hide false-positives)"),
369 "missing _() in ui message (use () to hide false-positives)"),
369 ],
370 ],
370 # warnings
371 # warnings
371 [
372 [
372 # rules depending on implementation of repquote()
373 # rules depending on implementation of repquote()
373 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
374 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
374 ]
375 ]
375 ]
376 ]
376
377
377 pyfilters = [
378 pyfilters = [
378 (r"""(?msx)(?P<comment>\#.*?$)|
379 (r"""(?msx)(?P<comment>\#.*?$)|
379 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
380 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
380 (?P<text>(([^\\]|\\.)*?))
381 (?P<text>(([^\\]|\\.)*?))
381 (?P=quote))""", reppython),
382 (?P=quote))""", reppython),
382 ]
383 ]
383
384
384 # extension non-filter patterns
385 # extension non-filter patterns
385 pyextnfpats = [
386 pyextnfpats = [
386 [(r'^"""\n?[A-Z]', "don't capitalize docstring title")],
387 [(r'^"""\n?[A-Z]', "don't capitalize docstring title")],
387 # warnings
388 # warnings
388 [],
389 [],
389 ]
390 ]
390
391
391 txtfilters = []
392 txtfilters = []
392
393
393 txtpats = [
394 txtpats = [
394 [
395 [
395 ('\s$', 'trailing whitespace'),
396 ('\s$', 'trailing whitespace'),
396 ('.. note::[ \n][^\n]', 'add two newlines after note::')
397 ('.. note::[ \n][^\n]', 'add two newlines after note::')
397 ],
398 ],
398 []
399 []
399 ]
400 ]
400
401
401 cpats = [
402 cpats = [
402 [
403 [
403 (r'//', "don't use //-style comments"),
404 (r'//', "don't use //-style comments"),
404 (r'^ ', "don't use spaces to indent"),
405 (r'^ ', "don't use spaces to indent"),
405 (r'\S\t', "don't use tabs except for indent"),
406 (r'\S\t', "don't use tabs except for indent"),
406 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
407 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
407 (r'.{81}', "line too long"),
408 (r'.{81}', "line too long"),
408 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
409 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
409 (r'return\(', "return is not a function"),
410 (r'return\(', "return is not a function"),
410 (r' ;', "no space before ;"),
411 (r' ;', "no space before ;"),
411 (r'[^;] \)', "no space before )"),
412 (r'[^;] \)', "no space before )"),
412 (r'[)][{]', "space between ) and {"),
413 (r'[)][{]', "space between ) and {"),
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\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
415 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
416 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
416 (r'\w,\w', "missing whitespace after ,"),
417 (r'\w,\w', "missing whitespace after ,"),
417 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
418 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
418 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
419 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
419 (r'^#\s+\w', "use #foo, not # foo"),
420 (r'^#\s+\w', "use #foo, not # foo"),
420 (r'[^\n]\Z', "no trailing newline"),
421 (r'[^\n]\Z', "no trailing newline"),
421 (r'^\s*#import\b', "use only #include in standard C code"),
422 (r'^\s*#import\b', "use only #include in standard C code"),
422 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
423 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
423 (r'strcat\(', "don't use strcat"),
424 (r'strcat\(', "don't use strcat"),
424
425
425 # rules depending on implementation of repquote()
426 # rules depending on implementation of repquote()
426 ],
427 ],
427 # warnings
428 # warnings
428 [
429 [
429 # rules depending on implementation of repquote()
430 # rules depending on implementation of repquote()
430 ]
431 ]
431 ]
432 ]
432
433
433 cfilters = [
434 cfilters = [
434 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
435 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
435 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
436 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
436 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
437 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
437 (r'(\()([^)]+\))', repcallspaces),
438 (r'(\()([^)]+\))', repcallspaces),
438 ]
439 ]
439
440
440 inutilpats = [
441 inutilpats = [
441 [
442 [
442 (r'\bui\.', "don't use ui in util"),
443 (r'\bui\.', "don't use ui in util"),
443 ],
444 ],
444 # warnings
445 # warnings
445 []
446 []
446 ]
447 ]
447
448
448 inrevlogpats = [
449 inrevlogpats = [
449 [
450 [
450 (r'\brepo\.', "don't use repo in revlog"),
451 (r'\brepo\.', "don't use repo in revlog"),
451 ],
452 ],
452 # warnings
453 # warnings
453 []
454 []
454 ]
455 ]
455
456
456 webtemplatefilters = []
457 webtemplatefilters = []
457
458
458 webtemplatepats = [
459 webtemplatepats = [
459 [],
460 [],
460 [
461 [
461 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
462 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
462 'follow desc keyword with either firstline or websub'),
463 'follow desc keyword with either firstline or websub'),
463 ]
464 ]
464 ]
465 ]
465
466
466 allfilesfilters = []
467 allfilesfilters = []
467
468
468 allfilespats = [
469 allfilespats = [
469 [
470 [
470 (r'(http|https)://[a-zA-Z0-9./]*selenic.com/',
471 (r'(http|https)://[a-zA-Z0-9./]*selenic.com/',
471 'use mercurial-scm.org domain URL'),
472 'use mercurial-scm.org domain URL'),
472 (r'mercurial@selenic\.com',
473 (r'mercurial@selenic\.com',
473 'use mercurial-scm.org domain for mercurial ML address'),
474 'use mercurial-scm.org domain for mercurial ML address'),
474 (r'mercurial-devel@selenic\.com',
475 (r'mercurial-devel@selenic\.com',
475 'use mercurial-scm.org domain for mercurial-devel ML address'),
476 'use mercurial-scm.org domain for mercurial-devel ML address'),
476 ],
477 ],
477 # warnings
478 # warnings
478 [],
479 [],
479 ]
480 ]
480
481
481 py3pats = [
482 py3pats = [
482 [
483 [
483 (r'os\.environ', "use encoding.environ instead (py3)", r'#.*re-exports'),
484 (r'os\.environ', "use encoding.environ instead (py3)", r'#.*re-exports'),
484 (r'os\.name', "use pycompat.osname instead (py3)"),
485 (r'os\.name', "use pycompat.osname instead (py3)"),
485 (r'os\.getcwd', "use pycompat.getcwd instead (py3)"),
486 (r'os\.getcwd', "use pycompat.getcwd instead (py3)"),
486 (r'os\.sep', "use pycompat.ossep instead (py3)"),
487 (r'os\.sep', "use pycompat.ossep instead (py3)"),
487 (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"),
488 (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"),
488 (r'os\.altsep', "use pycompat.osaltsep instead (py3)"),
489 (r'os\.altsep', "use pycompat.osaltsep instead (py3)"),
489 (r'sys\.platform', "use pycompat.sysplatform instead (py3)"),
490 (r'sys\.platform', "use pycompat.sysplatform instead (py3)"),
490 (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"),
491 (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"),
491 (r'os\.getenv', "use encoding.environ.get instead"),
492 (r'os\.getenv', "use encoding.environ.get instead"),
492 (r'os\.setenv', "modifying the environ dict is not preferred"),
493 (r'os\.setenv', "modifying the environ dict is not preferred"),
493 ],
494 ],
494 # warnings
495 # warnings
495 [],
496 [],
496 ]
497 ]
497
498
498 checks = [
499 checks = [
499 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
500 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
500 ('python', r'.*hgext.*\.py$', '', [], pyextnfpats),
501 ('python', r'.*hgext.*\.py$', '', [], pyextnfpats),
501 ('python 3', r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
502 ('python 3', r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
502 '', pyfilters, py3pats),
503 '', pyfilters, py3pats),
503 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
504 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
504 ('c', r'.*\.[ch]$', '', cfilters, cpats),
505 ('c', r'.*\.[ch]$', '', cfilters, cpats),
505 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
506 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
506 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
507 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
507 pyfilters, inrevlogpats),
508 pyfilters, inrevlogpats),
508 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
509 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
509 inutilpats),
510 inutilpats),
510 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
511 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
511 ('web template', r'mercurial/templates/.*\.tmpl', '',
512 ('web template', r'mercurial/templates/.*\.tmpl', '',
512 webtemplatefilters, webtemplatepats),
513 webtemplatefilters, webtemplatepats),
513 ('all except for .po', r'.*(?<!\.po)$', '',
514 ('all except for .po', r'.*(?<!\.po)$', '',
514 allfilesfilters, allfilespats),
515 allfilesfilters, allfilespats),
515 ]
516 ]
516
517
517 def _preparepats():
518 def _preparepats():
518 for c in checks:
519 for c in checks:
519 failandwarn = c[-1]
520 failandwarn = c[-1]
520 for pats in failandwarn:
521 for pats in failandwarn:
521 for i, pseq in enumerate(pats):
522 for i, pseq in enumerate(pats):
522 # fix-up regexes for multi-line searches
523 # fix-up regexes for multi-line searches
523 p = pseq[0]
524 p = pseq[0]
524 # \s doesn't match \n
525 # \s doesn't match \n
525 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
526 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
526 # [^...] doesn't match newline
527 # [^...] doesn't match newline
527 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
528 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
528
529
529 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
530 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
530 filters = c[3]
531 filters = c[3]
531 for i, flt in enumerate(filters):
532 for i, flt in enumerate(filters):
532 filters[i] = re.compile(flt[0]), flt[1]
533 filters[i] = re.compile(flt[0]), flt[1]
533
534
534 class norepeatlogger(object):
535 class norepeatlogger(object):
535 def __init__(self):
536 def __init__(self):
536 self._lastseen = None
537 self._lastseen = None
537
538
538 def log(self, fname, lineno, line, msg, blame):
539 def log(self, fname, lineno, line, msg, blame):
539 """print error related a to given line of a given file.
540 """print error related a to given line of a given file.
540
541
541 The faulty line will also be printed but only once in the case
542 The faulty line will also be printed but only once in the case
542 of multiple errors.
543 of multiple errors.
543
544
544 :fname: filename
545 :fname: filename
545 :lineno: line number
546 :lineno: line number
546 :line: actual content of the line
547 :line: actual content of the line
547 :msg: error message
548 :msg: error message
548 """
549 """
549 msgid = fname, lineno, line
550 msgid = fname, lineno, line
550 if msgid != self._lastseen:
551 if msgid != self._lastseen:
551 if blame:
552 if blame:
552 print("%s:%d (%s):" % (fname, lineno, blame))
553 print("%s:%d (%s):" % (fname, lineno, blame))
553 else:
554 else:
554 print("%s:%d:" % (fname, lineno))
555 print("%s:%d:" % (fname, lineno))
555 print(" > %s" % line)
556 print(" > %s" % line)
556 self._lastseen = msgid
557 self._lastseen = msgid
557 print(" " + msg)
558 print(" " + msg)
558
559
559 _defaultlogger = norepeatlogger()
560 _defaultlogger = norepeatlogger()
560
561
561 def getblame(f):
562 def getblame(f):
562 lines = []
563 lines = []
563 for l in os.popen('hg annotate -un %s' % f):
564 for l in os.popen('hg annotate -un %s' % f):
564 start, line = l.split(':', 1)
565 start, line = l.split(':', 1)
565 user, rev = start.split()
566 user, rev = start.split()
566 lines.append((line[1:-1], user, rev))
567 lines.append((line[1:-1], user, rev))
567 return lines
568 return lines
568
569
569 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
570 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
570 blame=False, debug=False, lineno=True):
571 blame=False, debug=False, lineno=True):
571 """checks style and portability of a given file
572 """checks style and portability of a given file
572
573
573 :f: filepath
574 :f: filepath
574 :logfunc: function used to report error
575 :logfunc: function used to report error
575 logfunc(filename, linenumber, linecontent, errormessage)
576 logfunc(filename, linenumber, linecontent, errormessage)
576 :maxerr: number of error to display before aborting.
577 :maxerr: number of error to display before aborting.
577 Set to false (default) to report all errors
578 Set to false (default) to report all errors
578
579
579 return True if no error is found, False otherwise.
580 return True if no error is found, False otherwise.
580 """
581 """
581 blamecache = None
582 blamecache = None
582 result = True
583 result = True
583
584
584 try:
585 try:
585 with opentext(f) as fp:
586 with opentext(f) as fp:
586 try:
587 try:
587 pre = post = fp.read()
588 pre = post = fp.read()
588 except UnicodeDecodeError as e:
589 except UnicodeDecodeError as e:
589 print("%s while reading %s" % (e, f))
590 print("%s while reading %s" % (e, f))
590 return result
591 return result
591 except IOError as e:
592 except IOError as e:
592 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
593 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
593 return result
594 return result
594
595
595 for name, match, magic, filters, pats in checks:
596 for name, match, magic, filters, pats in checks:
596 post = pre # discard filtering result of previous check
597 post = pre # discard filtering result of previous check
597 if debug:
598 if debug:
598 print(name, f)
599 print(name, f)
599 fc = 0
600 fc = 0
600 if not (re.match(match, f) or (magic and re.search(magic, pre))):
601 if not (re.match(match, f) or (magic and re.search(magic, pre))):
601 if debug:
602 if debug:
602 print("Skipping %s for %s it doesn't match %s" % (
603 print("Skipping %s for %s it doesn't match %s" % (
603 name, match, f))
604 name, match, f))
604 continue
605 continue
605 if "no-" "check-code" in pre:
606 if "no-" "check-code" in pre:
606 # If you're looking at this line, it's because a file has:
607 # If you're looking at this line, it's because a file has:
607 # no- check- code
608 # no- check- code
608 # but the reason to output skipping is to make life for
609 # but the reason to output skipping is to make life for
609 # tests easier. So, instead of writing it with a normal
610 # tests easier. So, instead of writing it with a normal
610 # spelling, we write it with the expected spelling from
611 # spelling, we write it with the expected spelling from
611 # tests/test-check-code.t
612 # tests/test-check-code.t
612 print("Skipping %s it has no-che?k-code (glob)" % f)
613 print("Skipping %s it has no-che?k-code (glob)" % f)
613 return "Skip" # skip checking this file
614 return "Skip" # skip checking this file
614 for p, r in filters:
615 for p, r in filters:
615 post = re.sub(p, r, post)
616 post = re.sub(p, r, post)
616 nerrs = len(pats[0]) # nerr elements are errors
617 nerrs = len(pats[0]) # nerr elements are errors
617 if warnings:
618 if warnings:
618 pats = pats[0] + pats[1]
619 pats = pats[0] + pats[1]
619 else:
620 else:
620 pats = pats[0]
621 pats = pats[0]
621 # print post # uncomment to show filtered version
622 # print post # uncomment to show filtered version
622
623
623 if debug:
624 if debug:
624 print("Checking %s for %s" % (name, f))
625 print("Checking %s for %s" % (name, f))
625
626
626 prelines = None
627 prelines = None
627 errors = []
628 errors = []
628 for i, pat in enumerate(pats):
629 for i, pat in enumerate(pats):
629 if len(pat) == 3:
630 if len(pat) == 3:
630 p, msg, ignore = pat
631 p, msg, ignore = pat
631 else:
632 else:
632 p, msg = pat
633 p, msg = pat
633 ignore = None
634 ignore = None
634 if i >= nerrs:
635 if i >= nerrs:
635 msg = "warning: " + msg
636 msg = "warning: " + msg
636
637
637 pos = 0
638 pos = 0
638 n = 0
639 n = 0
639 for m in p.finditer(post):
640 for m in p.finditer(post):
640 if prelines is None:
641 if prelines is None:
641 prelines = pre.splitlines()
642 prelines = pre.splitlines()
642 postlines = post.splitlines(True)
643 postlines = post.splitlines(True)
643
644
644 start = m.start()
645 start = m.start()
645 while n < len(postlines):
646 while n < len(postlines):
646 step = len(postlines[n])
647 step = len(postlines[n])
647 if pos + step > start:
648 if pos + step > start:
648 break
649 break
649 pos += step
650 pos += step
650 n += 1
651 n += 1
651 l = prelines[n]
652 l = prelines[n]
652
653
653 if ignore and re.search(ignore, l, re.MULTILINE):
654 if ignore and re.search(ignore, l, re.MULTILINE):
654 if debug:
655 if debug:
655 print("Skipping %s for %s:%s (ignore pattern)" % (
656 print("Skipping %s for %s:%s (ignore pattern)" % (
656 name, f, n))
657 name, f, n))
657 continue
658 continue
658 bd = ""
659 bd = ""
659 if blame:
660 if blame:
660 bd = 'working directory'
661 bd = 'working directory'
661 if not blamecache:
662 if not blamecache:
662 blamecache = getblame(f)
663 blamecache = getblame(f)
663 if n < len(blamecache):
664 if n < len(blamecache):
664 bl, bu, br = blamecache[n]
665 bl, bu, br = blamecache[n]
665 if bl == l:
666 if bl == l:
666 bd = '%s@%s' % (bu, br)
667 bd = '%s@%s' % (bu, br)
667
668
668 errors.append((f, lineno and n + 1, l, msg, bd))
669 errors.append((f, lineno and n + 1, l, msg, bd))
669 result = False
670 result = False
670
671
671 errors.sort()
672 errors.sort()
672 for e in errors:
673 for e in errors:
673 logfunc(*e)
674 logfunc(*e)
674 fc += 1
675 fc += 1
675 if maxerr and fc >= maxerr:
676 if maxerr and fc >= maxerr:
676 print(" (too many errors, giving up)")
677 print(" (too many errors, giving up)")
677 break
678 break
678
679
679 return result
680 return result
680
681
681 def main():
682 def main():
682 parser = optparse.OptionParser("%prog [options] [files | -]")
683 parser = optparse.OptionParser("%prog [options] [files | -]")
683 parser.add_option("-w", "--warnings", action="store_true",
684 parser.add_option("-w", "--warnings", action="store_true",
684 help="include warning-level checks")
685 help="include warning-level checks")
685 parser.add_option("-p", "--per-file", type="int",
686 parser.add_option("-p", "--per-file", type="int",
686 help="max warnings per file")
687 help="max warnings per file")
687 parser.add_option("-b", "--blame", action="store_true",
688 parser.add_option("-b", "--blame", action="store_true",
688 help="use annotate to generate blame info")
689 help="use annotate to generate blame info")
689 parser.add_option("", "--debug", action="store_true",
690 parser.add_option("", "--debug", action="store_true",
690 help="show debug information")
691 help="show debug information")
691 parser.add_option("", "--nolineno", action="store_false",
692 parser.add_option("", "--nolineno", action="store_false",
692 dest='lineno', help="don't show line numbers")
693 dest='lineno', help="don't show line numbers")
693
694
694 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
695 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
695 lineno=True)
696 lineno=True)
696 (options, args) = parser.parse_args()
697 (options, args) = parser.parse_args()
697
698
698 if len(args) == 0:
699 if len(args) == 0:
699 check = glob.glob("*")
700 check = glob.glob("*")
700 elif args == ['-']:
701 elif args == ['-']:
701 # read file list from stdin
702 # read file list from stdin
702 check = sys.stdin.read().splitlines()
703 check = sys.stdin.read().splitlines()
703 else:
704 else:
704 check = args
705 check = args
705
706
706 _preparepats()
707 _preparepats()
707
708
708 ret = 0
709 ret = 0
709 for f in check:
710 for f in check:
710 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
711 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
711 blame=options.blame, debug=options.debug,
712 blame=options.blame, debug=options.debug,
712 lineno=options.lineno):
713 lineno=options.lineno):
713 ret = 1
714 ret = 1
714 return ret
715 return ret
715
716
716 if __name__ == "__main__":
717 if __name__ == "__main__":
717 sys.exit(main())
718 sys.exit(main())
General Comments 0
You need to be logged in to leave comments. Login now