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