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