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