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