##// END OF EJS Templates
check-code: forbid platform.system()...
Jun Wu -
r34643:a679aa58 default
parent child Browse files
Show More
@@ -1,732 +1,733 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
366
366 # rules depending on implementation of repquote()
367 # rules depending on implementation of repquote()
367 (r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
368 (r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
368 'string join across lines with no space'),
369 'string join across lines with no space'),
369 (r'''(?x)ui\.(status|progress|write|note|warn)\(
370 (r'''(?x)ui\.(status|progress|write|note|warn)\(
370 [ \t\n#]*
371 [ \t\n#]*
371 (?# any strings/comments might precede a string, which
372 (?# any strings/comments might precede a string, which
372 # contains translatable message)
373 # contains translatable message)
373 ((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
374 ((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
374 (?# sequence consisting of below might precede translatable message
375 (?# sequence consisting of below might precede translatable message
375 # - formatting string: "% 10s", "%05d", "% -3.2f", "%*s", "%%" ...
376 # - formatting string: "% 10s", "%05d", "% -3.2f", "%*s", "%%" ...
376 # - escaped character: "\\", "\n", "\0" ...
377 # - escaped character: "\\", "\n", "\0" ...
377 # - character other than '%', 'b' as '\', and 'x' as alphabet)
378 # - character other than '%', 'b' as '\', and 'x' as alphabet)
378 (['"]|\'\'\'|""")
379 (['"]|\'\'\'|""")
379 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
380 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
380 (?# this regexp can't use [^...] style,
381 (?# this regexp can't use [^...] style,
381 # because _preparepats forcibly adds "\n" into [^...],
382 # because _preparepats forcibly adds "\n" into [^...],
382 # even though this regexp wants match it against "\n")''',
383 # even though this regexp wants match it against "\n")''',
383 "missing _() in ui message (use () to hide false-positives)"),
384 "missing _() in ui message (use () to hide false-positives)"),
384 ],
385 ],
385 # warnings
386 # warnings
386 [
387 [
387 # rules depending on implementation of repquote()
388 # rules depending on implementation of repquote()
388 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
389 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
389 ]
390 ]
390 ]
391 ]
391
392
392 pyfilters = [
393 pyfilters = [
393 (r"""(?msx)(?P<comment>\#.*?$)|
394 (r"""(?msx)(?P<comment>\#.*?$)|
394 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
395 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
395 (?P<text>(([^\\]|\\.)*?))
396 (?P<text>(([^\\]|\\.)*?))
396 (?P=quote))""", reppython),
397 (?P=quote))""", reppython),
397 ]
398 ]
398
399
399 # extension non-filter patterns
400 # extension non-filter patterns
400 pyextnfpats = [
401 pyextnfpats = [
401 [(r'^"""\n?[A-Z]', "don't capitalize docstring title")],
402 [(r'^"""\n?[A-Z]', "don't capitalize docstring title")],
402 # warnings
403 # warnings
403 [],
404 [],
404 ]
405 ]
405
406
406 txtfilters = []
407 txtfilters = []
407
408
408 txtpats = [
409 txtpats = [
409 [
410 [
410 ('\s$', 'trailing whitespace'),
411 ('\s$', 'trailing whitespace'),
411 ('.. note::[ \n][^\n]', 'add two newlines after note::')
412 ('.. note::[ \n][^\n]', 'add two newlines after note::')
412 ],
413 ],
413 []
414 []
414 ]
415 ]
415
416
416 cpats = [
417 cpats = [
417 [
418 [
418 (r'//', "don't use //-style comments"),
419 (r'//', "don't use //-style comments"),
419 (r'^ ', "don't use spaces to indent"),
420 (r'^ ', "don't use spaces to indent"),
420 (r'\S\t', "don't use tabs except for indent"),
421 (r'\S\t', "don't use tabs except for indent"),
421 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
422 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
422 (r'.{81}', "line too long"),
423 (r'.{81}', "line too long"),
423 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
424 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
424 (r'return\(', "return is not a function"),
425 (r'return\(', "return is not a function"),
425 (r' ;', "no space before ;"),
426 (r' ;', "no space before ;"),
426 (r'[^;] \)', "no space before )"),
427 (r'[^;] \)', "no space before )"),
427 (r'[)][{]', "space between ) and {"),
428 (r'[)][{]', "space between ) and {"),
428 (r'\w+\* \w+', "use int *foo, not int* foo"),
429 (r'\w+\* \w+', "use int *foo, not int* foo"),
429 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
430 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
430 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
431 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
431 (r'\w,\w', "missing whitespace after ,"),
432 (r'\w,\w', "missing whitespace after ,"),
432 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
433 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
433 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
434 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
434 (r'^#\s+\w', "use #foo, not # foo"),
435 (r'^#\s+\w', "use #foo, not # foo"),
435 (r'[^\n]\Z', "no trailing newline"),
436 (r'[^\n]\Z', "no trailing newline"),
436 (r'^\s*#import\b', "use only #include in standard C code"),
437 (r'^\s*#import\b', "use only #include in standard C code"),
437 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
438 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
438 (r'strcat\(', "don't use strcat"),
439 (r'strcat\(', "don't use strcat"),
439
440
440 # rules depending on implementation of repquote()
441 # rules depending on implementation of repquote()
441 ],
442 ],
442 # warnings
443 # warnings
443 [
444 [
444 # rules depending on implementation of repquote()
445 # rules depending on implementation of repquote()
445 ]
446 ]
446 ]
447 ]
447
448
448 cfilters = [
449 cfilters = [
449 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
450 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
450 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
451 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
451 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
452 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
452 (r'(\()([^)]+\))', repcallspaces),
453 (r'(\()([^)]+\))', repcallspaces),
453 ]
454 ]
454
455
455 inutilpats = [
456 inutilpats = [
456 [
457 [
457 (r'\bui\.', "don't use ui in util"),
458 (r'\bui\.', "don't use ui in util"),
458 ],
459 ],
459 # warnings
460 # warnings
460 []
461 []
461 ]
462 ]
462
463
463 inrevlogpats = [
464 inrevlogpats = [
464 [
465 [
465 (r'\brepo\.', "don't use repo in revlog"),
466 (r'\brepo\.', "don't use repo in revlog"),
466 ],
467 ],
467 # warnings
468 # warnings
468 []
469 []
469 ]
470 ]
470
471
471 webtemplatefilters = []
472 webtemplatefilters = []
472
473
473 webtemplatepats = [
474 webtemplatepats = [
474 [],
475 [],
475 [
476 [
476 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
477 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
477 'follow desc keyword with either firstline or websub'),
478 'follow desc keyword with either firstline or websub'),
478 ]
479 ]
479 ]
480 ]
480
481
481 allfilesfilters = []
482 allfilesfilters = []
482
483
483 allfilespats = [
484 allfilespats = [
484 [
485 [
485 (r'(http|https)://[a-zA-Z0-9./]*selenic.com/',
486 (r'(http|https)://[a-zA-Z0-9./]*selenic.com/',
486 'use mercurial-scm.org domain URL'),
487 'use mercurial-scm.org domain URL'),
487 (r'mercurial@selenic\.com',
488 (r'mercurial@selenic\.com',
488 'use mercurial-scm.org domain for mercurial ML address'),
489 'use mercurial-scm.org domain for mercurial ML address'),
489 (r'mercurial-devel@selenic\.com',
490 (r'mercurial-devel@selenic\.com',
490 'use mercurial-scm.org domain for mercurial-devel ML address'),
491 'use mercurial-scm.org domain for mercurial-devel ML address'),
491 ],
492 ],
492 # warnings
493 # warnings
493 [],
494 [],
494 ]
495 ]
495
496
496 py3pats = [
497 py3pats = [
497 [
498 [
498 (r'os\.environ', "use encoding.environ instead (py3)", r'#.*re-exports'),
499 (r'os\.environ', "use encoding.environ instead (py3)", r'#.*re-exports'),
499 (r'os\.name', "use pycompat.osname instead (py3)"),
500 (r'os\.name', "use pycompat.osname instead (py3)"),
500 (r'os\.getcwd', "use pycompat.getcwd instead (py3)"),
501 (r'os\.getcwd', "use pycompat.getcwd instead (py3)"),
501 (r'os\.sep', "use pycompat.ossep instead (py3)"),
502 (r'os\.sep', "use pycompat.ossep instead (py3)"),
502 (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"),
503 (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"),
503 (r'os\.altsep', "use pycompat.osaltsep instead (py3)"),
504 (r'os\.altsep', "use pycompat.osaltsep instead (py3)"),
504 (r'sys\.platform', "use pycompat.sysplatform instead (py3)"),
505 (r'sys\.platform', "use pycompat.sysplatform instead (py3)"),
505 (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"),
506 (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"),
506 (r'os\.getenv', "use encoding.environ.get instead"),
507 (r'os\.getenv', "use encoding.environ.get instead"),
507 (r'os\.setenv', "modifying the environ dict is not preferred"),
508 (r'os\.setenv', "modifying the environ dict is not preferred"),
508 ],
509 ],
509 # warnings
510 # warnings
510 [],
511 [],
511 ]
512 ]
512
513
513 checks = [
514 checks = [
514 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
515 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
515 ('python', r'.*hgext.*\.py$', '', [], pyextnfpats),
516 ('python', r'.*hgext.*\.py$', '', [], pyextnfpats),
516 ('python 3', r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
517 ('python 3', r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
517 '', pyfilters, py3pats),
518 '', pyfilters, py3pats),
518 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
519 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
519 ('c', r'.*\.[ch]$', '', cfilters, cpats),
520 ('c', r'.*\.[ch]$', '', cfilters, cpats),
520 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
521 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
521 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
522 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
522 pyfilters, inrevlogpats),
523 pyfilters, inrevlogpats),
523 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
524 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
524 inutilpats),
525 inutilpats),
525 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
526 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
526 ('web template', r'mercurial/templates/.*\.tmpl', '',
527 ('web template', r'mercurial/templates/.*\.tmpl', '',
527 webtemplatefilters, webtemplatepats),
528 webtemplatefilters, webtemplatepats),
528 ('all except for .po', r'.*(?<!\.po)$', '',
529 ('all except for .po', r'.*(?<!\.po)$', '',
529 allfilesfilters, allfilespats),
530 allfilesfilters, allfilespats),
530 ]
531 ]
531
532
532 def _preparepats():
533 def _preparepats():
533 for c in checks:
534 for c in checks:
534 failandwarn = c[-1]
535 failandwarn = c[-1]
535 for pats in failandwarn:
536 for pats in failandwarn:
536 for i, pseq in enumerate(pats):
537 for i, pseq in enumerate(pats):
537 # fix-up regexes for multi-line searches
538 # fix-up regexes for multi-line searches
538 p = pseq[0]
539 p = pseq[0]
539 # \s doesn't match \n
540 # \s doesn't match \n
540 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
541 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
541 # [^...] doesn't match newline
542 # [^...] doesn't match newline
542 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
543 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
543
544
544 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
545 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
545 filters = c[3]
546 filters = c[3]
546 for i, flt in enumerate(filters):
547 for i, flt in enumerate(filters):
547 filters[i] = re.compile(flt[0]), flt[1]
548 filters[i] = re.compile(flt[0]), flt[1]
548
549
549 class norepeatlogger(object):
550 class norepeatlogger(object):
550 def __init__(self):
551 def __init__(self):
551 self._lastseen = None
552 self._lastseen = None
552
553
553 def log(self, fname, lineno, line, msg, blame):
554 def log(self, fname, lineno, line, msg, blame):
554 """print error related a to given line of a given file.
555 """print error related a to given line of a given file.
555
556
556 The faulty line will also be printed but only once in the case
557 The faulty line will also be printed but only once in the case
557 of multiple errors.
558 of multiple errors.
558
559
559 :fname: filename
560 :fname: filename
560 :lineno: line number
561 :lineno: line number
561 :line: actual content of the line
562 :line: actual content of the line
562 :msg: error message
563 :msg: error message
563 """
564 """
564 msgid = fname, lineno, line
565 msgid = fname, lineno, line
565 if msgid != self._lastseen:
566 if msgid != self._lastseen:
566 if blame:
567 if blame:
567 print("%s:%d (%s):" % (fname, lineno, blame))
568 print("%s:%d (%s):" % (fname, lineno, blame))
568 else:
569 else:
569 print("%s:%d:" % (fname, lineno))
570 print("%s:%d:" % (fname, lineno))
570 print(" > %s" % line)
571 print(" > %s" % line)
571 self._lastseen = msgid
572 self._lastseen = msgid
572 print(" " + msg)
573 print(" " + msg)
573
574
574 _defaultlogger = norepeatlogger()
575 _defaultlogger = norepeatlogger()
575
576
576 def getblame(f):
577 def getblame(f):
577 lines = []
578 lines = []
578 for l in os.popen('hg annotate -un %s' % f):
579 for l in os.popen('hg annotate -un %s' % f):
579 start, line = l.split(':', 1)
580 start, line = l.split(':', 1)
580 user, rev = start.split()
581 user, rev = start.split()
581 lines.append((line[1:-1], user, rev))
582 lines.append((line[1:-1], user, rev))
582 return lines
583 return lines
583
584
584 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
585 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
585 blame=False, debug=False, lineno=True):
586 blame=False, debug=False, lineno=True):
586 """checks style and portability of a given file
587 """checks style and portability of a given file
587
588
588 :f: filepath
589 :f: filepath
589 :logfunc: function used to report error
590 :logfunc: function used to report error
590 logfunc(filename, linenumber, linecontent, errormessage)
591 logfunc(filename, linenumber, linecontent, errormessage)
591 :maxerr: number of error to display before aborting.
592 :maxerr: number of error to display before aborting.
592 Set to false (default) to report all errors
593 Set to false (default) to report all errors
593
594
594 return True if no error is found, False otherwise.
595 return True if no error is found, False otherwise.
595 """
596 """
596 blamecache = None
597 blamecache = None
597 result = True
598 result = True
598
599
599 try:
600 try:
600 with opentext(f) as fp:
601 with opentext(f) as fp:
601 try:
602 try:
602 pre = post = fp.read()
603 pre = post = fp.read()
603 except UnicodeDecodeError as e:
604 except UnicodeDecodeError as e:
604 print("%s while reading %s" % (e, f))
605 print("%s while reading %s" % (e, f))
605 return result
606 return result
606 except IOError as e:
607 except IOError as e:
607 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
608 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
608 return result
609 return result
609
610
610 for name, match, magic, filters, pats in checks:
611 for name, match, magic, filters, pats in checks:
611 post = pre # discard filtering result of previous check
612 post = pre # discard filtering result of previous check
612 if debug:
613 if debug:
613 print(name, f)
614 print(name, f)
614 fc = 0
615 fc = 0
615 if not (re.match(match, f) or (magic and re.search(magic, pre))):
616 if not (re.match(match, f) or (magic and re.search(magic, pre))):
616 if debug:
617 if debug:
617 print("Skipping %s for %s it doesn't match %s" % (
618 print("Skipping %s for %s it doesn't match %s" % (
618 name, match, f))
619 name, match, f))
619 continue
620 continue
620 if "no-" "check-code" in pre:
621 if "no-" "check-code" in pre:
621 # If you're looking at this line, it's because a file has:
622 # If you're looking at this line, it's because a file has:
622 # no- check- code
623 # no- check- code
623 # but the reason to output skipping is to make life for
624 # but the reason to output skipping is to make life for
624 # tests easier. So, instead of writing it with a normal
625 # tests easier. So, instead of writing it with a normal
625 # spelling, we write it with the expected spelling from
626 # spelling, we write it with the expected spelling from
626 # tests/test-check-code.t
627 # tests/test-check-code.t
627 print("Skipping %s it has no-che?k-code (glob)" % f)
628 print("Skipping %s it has no-che?k-code (glob)" % f)
628 return "Skip" # skip checking this file
629 return "Skip" # skip checking this file
629 for p, r in filters:
630 for p, r in filters:
630 post = re.sub(p, r, post)
631 post = re.sub(p, r, post)
631 nerrs = len(pats[0]) # nerr elements are errors
632 nerrs = len(pats[0]) # nerr elements are errors
632 if warnings:
633 if warnings:
633 pats = pats[0] + pats[1]
634 pats = pats[0] + pats[1]
634 else:
635 else:
635 pats = pats[0]
636 pats = pats[0]
636 # print post # uncomment to show filtered version
637 # print post # uncomment to show filtered version
637
638
638 if debug:
639 if debug:
639 print("Checking %s for %s" % (name, f))
640 print("Checking %s for %s" % (name, f))
640
641
641 prelines = None
642 prelines = None
642 errors = []
643 errors = []
643 for i, pat in enumerate(pats):
644 for i, pat in enumerate(pats):
644 if len(pat) == 3:
645 if len(pat) == 3:
645 p, msg, ignore = pat
646 p, msg, ignore = pat
646 else:
647 else:
647 p, msg = pat
648 p, msg = pat
648 ignore = None
649 ignore = None
649 if i >= nerrs:
650 if i >= nerrs:
650 msg = "warning: " + msg
651 msg = "warning: " + msg
651
652
652 pos = 0
653 pos = 0
653 n = 0
654 n = 0
654 for m in p.finditer(post):
655 for m in p.finditer(post):
655 if prelines is None:
656 if prelines is None:
656 prelines = pre.splitlines()
657 prelines = pre.splitlines()
657 postlines = post.splitlines(True)
658 postlines = post.splitlines(True)
658
659
659 start = m.start()
660 start = m.start()
660 while n < len(postlines):
661 while n < len(postlines):
661 step = len(postlines[n])
662 step = len(postlines[n])
662 if pos + step > start:
663 if pos + step > start:
663 break
664 break
664 pos += step
665 pos += step
665 n += 1
666 n += 1
666 l = prelines[n]
667 l = prelines[n]
667
668
668 if ignore and re.search(ignore, l, re.MULTILINE):
669 if ignore and re.search(ignore, l, re.MULTILINE):
669 if debug:
670 if debug:
670 print("Skipping %s for %s:%s (ignore pattern)" % (
671 print("Skipping %s for %s:%s (ignore pattern)" % (
671 name, f, n))
672 name, f, n))
672 continue
673 continue
673 bd = ""
674 bd = ""
674 if blame:
675 if blame:
675 bd = 'working directory'
676 bd = 'working directory'
676 if not blamecache:
677 if not blamecache:
677 blamecache = getblame(f)
678 blamecache = getblame(f)
678 if n < len(blamecache):
679 if n < len(blamecache):
679 bl, bu, br = blamecache[n]
680 bl, bu, br = blamecache[n]
680 if bl == l:
681 if bl == l:
681 bd = '%s@%s' % (bu, br)
682 bd = '%s@%s' % (bu, br)
682
683
683 errors.append((f, lineno and n + 1, l, msg, bd))
684 errors.append((f, lineno and n + 1, l, msg, bd))
684 result = False
685 result = False
685
686
686 errors.sort()
687 errors.sort()
687 for e in errors:
688 for e in errors:
688 logfunc(*e)
689 logfunc(*e)
689 fc += 1
690 fc += 1
690 if maxerr and fc >= maxerr:
691 if maxerr and fc >= maxerr:
691 print(" (too many errors, giving up)")
692 print(" (too many errors, giving up)")
692 break
693 break
693
694
694 return result
695 return result
695
696
696 def main():
697 def main():
697 parser = optparse.OptionParser("%prog [options] [files | -]")
698 parser = optparse.OptionParser("%prog [options] [files | -]")
698 parser.add_option("-w", "--warnings", action="store_true",
699 parser.add_option("-w", "--warnings", action="store_true",
699 help="include warning-level checks")
700 help="include warning-level checks")
700 parser.add_option("-p", "--per-file", type="int",
701 parser.add_option("-p", "--per-file", type="int",
701 help="max warnings per file")
702 help="max warnings per file")
702 parser.add_option("-b", "--blame", action="store_true",
703 parser.add_option("-b", "--blame", action="store_true",
703 help="use annotate to generate blame info")
704 help="use annotate to generate blame info")
704 parser.add_option("", "--debug", action="store_true",
705 parser.add_option("", "--debug", action="store_true",
705 help="show debug information")
706 help="show debug information")
706 parser.add_option("", "--nolineno", action="store_false",
707 parser.add_option("", "--nolineno", action="store_false",
707 dest='lineno', help="don't show line numbers")
708 dest='lineno', help="don't show line numbers")
708
709
709 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
710 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
710 lineno=True)
711 lineno=True)
711 (options, args) = parser.parse_args()
712 (options, args) = parser.parse_args()
712
713
713 if len(args) == 0:
714 if len(args) == 0:
714 check = glob.glob("*")
715 check = glob.glob("*")
715 elif args == ['-']:
716 elif args == ['-']:
716 # read file list from stdin
717 # read file list from stdin
717 check = sys.stdin.read().splitlines()
718 check = sys.stdin.read().splitlines()
718 else:
719 else:
719 check = args
720 check = args
720
721
721 _preparepats()
722 _preparepats()
722
723
723 ret = 0
724 ret = 0
724 for f in check:
725 for f in check:
725 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
726 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
726 blame=options.blame, debug=options.debug,
727 blame=options.blame, debug=options.debug,
727 lineno=options.lineno):
728 lineno=options.lineno):
728 ret = 1
729 ret = 1
729 return ret
730 return ret
730
731
731 if __name__ == "__main__":
732 if __name__ == "__main__":
732 sys.exit(main())
733 sys.exit(main())
General Comments 0
You need to be logged in to leave comments. Login now