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