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