##// END OF EJS Templates
py3: add warnings in check-code related to py3...
Pulkit Goyal -
r30665:01721d38 default
parent child Browse files
Show More
@@ -1,672 +1,690
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'\b__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
321 (r'\b__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'^import Queue', "don't use Queue, use util.queue + util.empty"),
328 (r'^import Queue', "don't use Queue, use util.queue + util.empty"),
329 (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"),
329 (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"),
330 (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
330 (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
331 (r'^import SocketServer', "don't use SockerServer, use util.socketserver"),
331 (r'^import SocketServer', "don't use SockerServer, use util.socketserver"),
332 (r'^import urlparse', "don't use urlparse, use util.urlparse"),
332 (r'^import urlparse', "don't use urlparse, use util.urlparse"),
333 (r'^import xmlrpclib', "don't use xmlrpclib, use util.xmlrpclib"),
333 (r'^import xmlrpclib', "don't use xmlrpclib, use util.xmlrpclib"),
334 (r'^import cPickle', "don't use cPickle, use util.pickle"),
334 (r'^import cPickle', "don't use cPickle, use util.pickle"),
335 (r'^import pickle', "don't use pickle, use util.pickle"),
335 (r'^import pickle', "don't use pickle, use util.pickle"),
336 (r'^import httplib', "don't use httplib, use util.httplib"),
336 (r'^import httplib', "don't use httplib, use util.httplib"),
337 (r'^import BaseHTTPServer', "use util.httpserver instead"),
337 (r'^import BaseHTTPServer', "use util.httpserver instead"),
338 (r'\.next\(\)', "don't use .next(), use next(...)"),
338 (r'\.next\(\)', "don't use .next(), use next(...)"),
339
339
340 # rules depending on implementation of repquote()
340 # rules depending on implementation of repquote()
341 (r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
341 (r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
342 'string join across lines with no space'),
342 'string join across lines with no space'),
343 (r'''(?x)ui\.(status|progress|write|note|warn)\(
343 (r'''(?x)ui\.(status|progress|write|note|warn)\(
344 [ \t\n#]*
344 [ \t\n#]*
345 (?# any strings/comments might precede a string, which
345 (?# any strings/comments might precede a string, which
346 # contains translatable message)
346 # contains translatable message)
347 ((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
347 ((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
348 (?# sequence consisting of below might precede translatable message
348 (?# sequence consisting of below might precede translatable message
349 # - formatting string: "% 10s", "%05d", "% -3.2f", "%*s", "%%" ...
349 # - formatting string: "% 10s", "%05d", "% -3.2f", "%*s", "%%" ...
350 # - escaped character: "\\", "\n", "\0" ...
350 # - escaped character: "\\", "\n", "\0" ...
351 # - character other than '%', 'b' as '\', and 'x' as alphabet)
351 # - character other than '%', 'b' as '\', and 'x' as alphabet)
352 (['"]|\'\'\'|""")
352 (['"]|\'\'\'|""")
353 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
353 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
354 (?# this regexp can't use [^...] style,
354 (?# this regexp can't use [^...] style,
355 # because _preparepats forcibly adds "\n" into [^...],
355 # because _preparepats forcibly adds "\n" into [^...],
356 # even though this regexp wants match it against "\n")''',
356 # even though this regexp wants match it against "\n")''',
357 "missing _() in ui message (use () to hide false-positives)"),
357 "missing _() in ui message (use () to hide false-positives)"),
358 ],
358 ],
359 # warnings
359 # warnings
360 [
360 [
361 # rules depending on implementation of repquote()
361 # rules depending on implementation of repquote()
362 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
362 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
363 ]
363 ]
364 ]
364 ]
365
365
366 pyfilters = [
366 pyfilters = [
367 (r"""(?msx)(?P<comment>\#.*?$)|
367 (r"""(?msx)(?P<comment>\#.*?$)|
368 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
368 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
369 (?P<text>(([^\\]|\\.)*?))
369 (?P<text>(([^\\]|\\.)*?))
370 (?P=quote))""", reppython),
370 (?P=quote))""", reppython),
371 ]
371 ]
372
372
373 txtfilters = []
373 txtfilters = []
374
374
375 txtpats = [
375 txtpats = [
376 [
376 [
377 ('\s$', 'trailing whitespace'),
377 ('\s$', 'trailing whitespace'),
378 ('.. note::[ \n][^\n]', 'add two newlines after note::')
378 ('.. note::[ \n][^\n]', 'add two newlines after note::')
379 ],
379 ],
380 []
380 []
381 ]
381 ]
382
382
383 cpats = [
383 cpats = [
384 [
384 [
385 (r'//', "don't use //-style comments"),
385 (r'//', "don't use //-style comments"),
386 (r'^ ', "don't use spaces to indent"),
386 (r'^ ', "don't use spaces to indent"),
387 (r'\S\t', "don't use tabs except for indent"),
387 (r'\S\t', "don't use tabs except for indent"),
388 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
388 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
389 (r'.{81}', "line too long"),
389 (r'.{81}', "line too long"),
390 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
390 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
391 (r'return\(', "return is not a function"),
391 (r'return\(', "return is not a function"),
392 (r' ;', "no space before ;"),
392 (r' ;', "no space before ;"),
393 (r'[^;] \)', "no space before )"),
393 (r'[^;] \)', "no space before )"),
394 (r'[)][{]', "space between ) and {"),
394 (r'[)][{]', "space between ) and {"),
395 (r'\w+\* \w+', "use int *foo, not int* foo"),
395 (r'\w+\* \w+', "use int *foo, not int* foo"),
396 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
396 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
397 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
397 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
398 (r'\w,\w', "missing whitespace after ,"),
398 (r'\w,\w', "missing whitespace after ,"),
399 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
399 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
400 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
400 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
401 (r'^#\s+\w', "use #foo, not # foo"),
401 (r'^#\s+\w', "use #foo, not # foo"),
402 (r'[^\n]\Z', "no trailing newline"),
402 (r'[^\n]\Z', "no trailing newline"),
403 (r'^\s*#import\b', "use only #include in standard C code"),
403 (r'^\s*#import\b', "use only #include in standard C code"),
404 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
404 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
405 (r'strcat\(', "don't use strcat"),
405 (r'strcat\(', "don't use strcat"),
406
406
407 # rules depending on implementation of repquote()
407 # rules depending on implementation of repquote()
408 ],
408 ],
409 # warnings
409 # warnings
410 [
410 [
411 # rules depending on implementation of repquote()
411 # rules depending on implementation of repquote()
412 ]
412 ]
413 ]
413 ]
414
414
415 cfilters = [
415 cfilters = [
416 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
416 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
417 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
417 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
418 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
418 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
419 (r'(\()([^)]+\))', repcallspaces),
419 (r'(\()([^)]+\))', repcallspaces),
420 ]
420 ]
421
421
422 inutilpats = [
422 inutilpats = [
423 [
423 [
424 (r'\bui\.', "don't use ui in util"),
424 (r'\bui\.', "don't use ui in util"),
425 ],
425 ],
426 # warnings
426 # warnings
427 []
427 []
428 ]
428 ]
429
429
430 inrevlogpats = [
430 inrevlogpats = [
431 [
431 [
432 (r'\brepo\.', "don't use repo in revlog"),
432 (r'\brepo\.', "don't use repo in revlog"),
433 ],
433 ],
434 # warnings
434 # warnings
435 []
435 []
436 ]
436 ]
437
437
438 webtemplatefilters = []
438 webtemplatefilters = []
439
439
440 webtemplatepats = [
440 webtemplatepats = [
441 [],
441 [],
442 [
442 [
443 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
443 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
444 'follow desc keyword with either firstline or websub'),
444 'follow desc keyword with either firstline or websub'),
445 ]
445 ]
446 ]
446 ]
447
447
448 allfilesfilters = []
448 allfilesfilters = []
449
449
450 allfilespats = [
450 allfilespats = [
451 [
451 [
452 (r'(http|https)://[a-zA-Z0-9./]*selenic.com/',
452 (r'(http|https)://[a-zA-Z0-9./]*selenic.com/',
453 'use mercurial-scm.org domain URL'),
453 'use mercurial-scm.org domain URL'),
454 ],
454 ],
455 # warnings
455 # warnings
456 [],
456 [],
457 ]
457 ]
458
458
459 py3pats = [
460 [
461 (r'os\.environ', "use encoding.environ instead (py3)"),
462 (r'os\.name', "use pycompat.osname instead (py3)"),
463 (r'os\.getcwd', "use pycompat.getcwd instead (py3)"),
464 (r'os\.sep', "use pycompat.ossep instead (py3)"),
465 (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"),
466 (r'os\.altsep', "use pycompat.osaltsep instead (py3)"),
467 (r'os\.getenv', "use pycompat.osgetenv instead (py3)"),
468 (r'sys\.platform', "use pycompat.sysplatform instead (py3)"),
469 (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"),
470 ],
471 # warnings
472 [],
473 ]
474
459 checks = [
475 checks = [
460 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
476 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
477 ('python 3', r'.*(hgext|mercurial).*(?<!pycompat)\.py', '',
478 pyfilters, py3pats),
461 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
479 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
462 ('c', r'.*\.[ch]$', '', cfilters, cpats),
480 ('c', r'.*\.[ch]$', '', cfilters, cpats),
463 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
481 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
464 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
482 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
465 pyfilters, inrevlogpats),
483 pyfilters, inrevlogpats),
466 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
484 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
467 inutilpats),
485 inutilpats),
468 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
486 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
469 ('web template', r'mercurial/templates/.*\.tmpl', '',
487 ('web template', r'mercurial/templates/.*\.tmpl', '',
470 webtemplatefilters, webtemplatepats),
488 webtemplatefilters, webtemplatepats),
471 ('all except for .po', r'.*(?<!\.po)$', '',
489 ('all except for .po', r'.*(?<!\.po)$', '',
472 allfilesfilters, allfilespats),
490 allfilesfilters, allfilespats),
473 ]
491 ]
474
492
475 def _preparepats():
493 def _preparepats():
476 for c in checks:
494 for c in checks:
477 failandwarn = c[-1]
495 failandwarn = c[-1]
478 for pats in failandwarn:
496 for pats in failandwarn:
479 for i, pseq in enumerate(pats):
497 for i, pseq in enumerate(pats):
480 # fix-up regexes for multi-line searches
498 # fix-up regexes for multi-line searches
481 p = pseq[0]
499 p = pseq[0]
482 # \s doesn't match \n
500 # \s doesn't match \n
483 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
501 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
484 # [^...] doesn't match newline
502 # [^...] doesn't match newline
485 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
503 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
486
504
487 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
505 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
488 filters = c[3]
506 filters = c[3]
489 for i, flt in enumerate(filters):
507 for i, flt in enumerate(filters):
490 filters[i] = re.compile(flt[0]), flt[1]
508 filters[i] = re.compile(flt[0]), flt[1]
491
509
492 class norepeatlogger(object):
510 class norepeatlogger(object):
493 def __init__(self):
511 def __init__(self):
494 self._lastseen = None
512 self._lastseen = None
495
513
496 def log(self, fname, lineno, line, msg, blame):
514 def log(self, fname, lineno, line, msg, blame):
497 """print error related a to given line of a given file.
515 """print error related a to given line of a given file.
498
516
499 The faulty line will also be printed but only once in the case
517 The faulty line will also be printed but only once in the case
500 of multiple errors.
518 of multiple errors.
501
519
502 :fname: filename
520 :fname: filename
503 :lineno: line number
521 :lineno: line number
504 :line: actual content of the line
522 :line: actual content of the line
505 :msg: error message
523 :msg: error message
506 """
524 """
507 msgid = fname, lineno, line
525 msgid = fname, lineno, line
508 if msgid != self._lastseen:
526 if msgid != self._lastseen:
509 if blame:
527 if blame:
510 print("%s:%d (%s):" % (fname, lineno, blame))
528 print("%s:%d (%s):" % (fname, lineno, blame))
511 else:
529 else:
512 print("%s:%d:" % (fname, lineno))
530 print("%s:%d:" % (fname, lineno))
513 print(" > %s" % line)
531 print(" > %s" % line)
514 self._lastseen = msgid
532 self._lastseen = msgid
515 print(" " + msg)
533 print(" " + msg)
516
534
517 _defaultlogger = norepeatlogger()
535 _defaultlogger = norepeatlogger()
518
536
519 def getblame(f):
537 def getblame(f):
520 lines = []
538 lines = []
521 for l in os.popen('hg annotate -un %s' % f):
539 for l in os.popen('hg annotate -un %s' % f):
522 start, line = l.split(':', 1)
540 start, line = l.split(':', 1)
523 user, rev = start.split()
541 user, rev = start.split()
524 lines.append((line[1:-1], user, rev))
542 lines.append((line[1:-1], user, rev))
525 return lines
543 return lines
526
544
527 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
545 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
528 blame=False, debug=False, lineno=True):
546 blame=False, debug=False, lineno=True):
529 """checks style and portability of a given file
547 """checks style and portability of a given file
530
548
531 :f: filepath
549 :f: filepath
532 :logfunc: function used to report error
550 :logfunc: function used to report error
533 logfunc(filename, linenumber, linecontent, errormessage)
551 logfunc(filename, linenumber, linecontent, errormessage)
534 :maxerr: number of error to display before aborting.
552 :maxerr: number of error to display before aborting.
535 Set to false (default) to report all errors
553 Set to false (default) to report all errors
536
554
537 return True if no error is found, False otherwise.
555 return True if no error is found, False otherwise.
538 """
556 """
539 blamecache = None
557 blamecache = None
540 result = True
558 result = True
541
559
542 try:
560 try:
543 with opentext(f) as fp:
561 with opentext(f) as fp:
544 try:
562 try:
545 pre = post = fp.read()
563 pre = post = fp.read()
546 except UnicodeDecodeError as e:
564 except UnicodeDecodeError as e:
547 print("%s while reading %s" % (e, f))
565 print("%s while reading %s" % (e, f))
548 return result
566 return result
549 except IOError as e:
567 except IOError as e:
550 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
568 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
551 return result
569 return result
552
570
553 for name, match, magic, filters, pats in checks:
571 for name, match, magic, filters, pats in checks:
554 post = pre # discard filtering result of previous check
572 post = pre # discard filtering result of previous check
555 if debug:
573 if debug:
556 print(name, f)
574 print(name, f)
557 fc = 0
575 fc = 0
558 if not (re.match(match, f) or (magic and re.search(magic, pre))):
576 if not (re.match(match, f) or (magic and re.search(magic, pre))):
559 if debug:
577 if debug:
560 print("Skipping %s for %s it doesn't match %s" % (
578 print("Skipping %s for %s it doesn't match %s" % (
561 name, match, f))
579 name, match, f))
562 continue
580 continue
563 if "no-" "check-code" in pre:
581 if "no-" "check-code" in pre:
564 # If you're looking at this line, it's because a file has:
582 # If you're looking at this line, it's because a file has:
565 # no- check- code
583 # no- check- code
566 # but the reason to output skipping is to make life for
584 # but the reason to output skipping is to make life for
567 # tests easier. So, instead of writing it with a normal
585 # tests easier. So, instead of writing it with a normal
568 # spelling, we write it with the expected spelling from
586 # spelling, we write it with the expected spelling from
569 # tests/test-check-code.t
587 # tests/test-check-code.t
570 print("Skipping %s it has no-che?k-code (glob)" % f)
588 print("Skipping %s it has no-che?k-code (glob)" % f)
571 return "Skip" # skip checking this file
589 return "Skip" # skip checking this file
572 for p, r in filters:
590 for p, r in filters:
573 post = re.sub(p, r, post)
591 post = re.sub(p, r, post)
574 nerrs = len(pats[0]) # nerr elements are errors
592 nerrs = len(pats[0]) # nerr elements are errors
575 if warnings:
593 if warnings:
576 pats = pats[0] + pats[1]
594 pats = pats[0] + pats[1]
577 else:
595 else:
578 pats = pats[0]
596 pats = pats[0]
579 # print post # uncomment to show filtered version
597 # print post # uncomment to show filtered version
580
598
581 if debug:
599 if debug:
582 print("Checking %s for %s" % (name, f))
600 print("Checking %s for %s" % (name, f))
583
601
584 prelines = None
602 prelines = None
585 errors = []
603 errors = []
586 for i, pat in enumerate(pats):
604 for i, pat in enumerate(pats):
587 if len(pat) == 3:
605 if len(pat) == 3:
588 p, msg, ignore = pat
606 p, msg, ignore = pat
589 else:
607 else:
590 p, msg = pat
608 p, msg = pat
591 ignore = None
609 ignore = None
592 if i >= nerrs:
610 if i >= nerrs:
593 msg = "warning: " + msg
611 msg = "warning: " + msg
594
612
595 pos = 0
613 pos = 0
596 n = 0
614 n = 0
597 for m in p.finditer(post):
615 for m in p.finditer(post):
598 if prelines is None:
616 if prelines is None:
599 prelines = pre.splitlines()
617 prelines = pre.splitlines()
600 postlines = post.splitlines(True)
618 postlines = post.splitlines(True)
601
619
602 start = m.start()
620 start = m.start()
603 while n < len(postlines):
621 while n < len(postlines):
604 step = len(postlines[n])
622 step = len(postlines[n])
605 if pos + step > start:
623 if pos + step > start:
606 break
624 break
607 pos += step
625 pos += step
608 n += 1
626 n += 1
609 l = prelines[n]
627 l = prelines[n]
610
628
611 if ignore and re.search(ignore, l, re.MULTILINE):
629 if ignore and re.search(ignore, l, re.MULTILINE):
612 if debug:
630 if debug:
613 print("Skipping %s for %s:%s (ignore pattern)" % (
631 print("Skipping %s for %s:%s (ignore pattern)" % (
614 name, f, n))
632 name, f, n))
615 continue
633 continue
616 bd = ""
634 bd = ""
617 if blame:
635 if blame:
618 bd = 'working directory'
636 bd = 'working directory'
619 if not blamecache:
637 if not blamecache:
620 blamecache = getblame(f)
638 blamecache = getblame(f)
621 if n < len(blamecache):
639 if n < len(blamecache):
622 bl, bu, br = blamecache[n]
640 bl, bu, br = blamecache[n]
623 if bl == l:
641 if bl == l:
624 bd = '%s@%s' % (bu, br)
642 bd = '%s@%s' % (bu, br)
625
643
626 errors.append((f, lineno and n + 1, l, msg, bd))
644 errors.append((f, lineno and n + 1, l, msg, bd))
627 result = False
645 result = False
628
646
629 errors.sort()
647 errors.sort()
630 for e in errors:
648 for e in errors:
631 logfunc(*e)
649 logfunc(*e)
632 fc += 1
650 fc += 1
633 if maxerr and fc >= maxerr:
651 if maxerr and fc >= maxerr:
634 print(" (too many errors, giving up)")
652 print(" (too many errors, giving up)")
635 break
653 break
636
654
637 return result
655 return result
638
656
639 def main():
657 def main():
640 parser = optparse.OptionParser("%prog [options] [files]")
658 parser = optparse.OptionParser("%prog [options] [files]")
641 parser.add_option("-w", "--warnings", action="store_true",
659 parser.add_option("-w", "--warnings", action="store_true",
642 help="include warning-level checks")
660 help="include warning-level checks")
643 parser.add_option("-p", "--per-file", type="int",
661 parser.add_option("-p", "--per-file", type="int",
644 help="max warnings per file")
662 help="max warnings per file")
645 parser.add_option("-b", "--blame", action="store_true",
663 parser.add_option("-b", "--blame", action="store_true",
646 help="use annotate to generate blame info")
664 help="use annotate to generate blame info")
647 parser.add_option("", "--debug", action="store_true",
665 parser.add_option("", "--debug", action="store_true",
648 help="show debug information")
666 help="show debug information")
649 parser.add_option("", "--nolineno", action="store_false",
667 parser.add_option("", "--nolineno", action="store_false",
650 dest='lineno', help="don't show line numbers")
668 dest='lineno', help="don't show line numbers")
651
669
652 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
670 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
653 lineno=True)
671 lineno=True)
654 (options, args) = parser.parse_args()
672 (options, args) = parser.parse_args()
655
673
656 if len(args) == 0:
674 if len(args) == 0:
657 check = glob.glob("*")
675 check = glob.glob("*")
658 else:
676 else:
659 check = args
677 check = args
660
678
661 _preparepats()
679 _preparepats()
662
680
663 ret = 0
681 ret = 0
664 for f in check:
682 for f in check:
665 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
683 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
666 blame=options.blame, debug=options.debug,
684 blame=options.blame, debug=options.debug,
667 lineno=options.lineno):
685 lineno=options.lineno):
668 ret = 1
686 ret = 1
669 return ret
687 return ret
670
688
671 if __name__ == "__main__":
689 if __name__ == "__main__":
672 sys.exit(main())
690 sys.exit(main())
@@ -1,31 +1,68
1 #require test-repo
1 #require test-repo
2
2
3 $ . "$TESTDIR/helpers-testrepo.sh"
3 $ . "$TESTDIR/helpers-testrepo.sh"
4 $ check_code="$TESTDIR"/../contrib/check-code.py
4 $ check_code="$TESTDIR"/../contrib/check-code.py
5 $ cd "$TESTDIR"/..
5 $ cd "$TESTDIR"/..
6
6
7 New errors are not allowed. Warnings are strongly discouraged.
7 New errors are not allowed. Warnings are strongly discouraged.
8 (The writing "no-che?k-code" is for not skipping this file when checking.)
8 (The writing "no-che?k-code" is for not skipping this file when checking.)
9
9
10 $ hg locate -X contrib/python-zstandard -X hgext/fsmonitor/pywatchman |
10 $ hg locate -X contrib/python-zstandard -X hgext/fsmonitor/pywatchman |
11 > sed 's-\\-/-g' | xargs "$check_code" --warnings --per-file=0 || false
11 > sed 's-\\-/-g' | xargs "$check_code" --warnings --per-file=0 || false
12 hgext/fsmonitor/__init__.py:295:
13 > switch_slashes = os.sep == '\\'
14 use pycompat.ossep instead (py3)
15 hgext/fsmonitor/__init__.py:395:
16 > if 'FSMONITOR_LOG_FILE' in os.environ:
17 use encoding.environ instead (py3)
18 hgext/fsmonitor/__init__.py:396:
19 > fn = os.environ['FSMONITOR_LOG_FILE']
20 use encoding.environ instead (py3)
21 hgext/fsmonitor/__init__.py:437:
22 > 'HG_PENDING' not in os.environ)
23 use encoding.environ instead (py3)
24 hgext/fsmonitor/__init__.py:548:
25 > if sys.platform == 'darwin':
26 use pycompat.sysplatform instead (py3)
12 Skipping i18n/polib.py it has no-che?k-code (glob)
27 Skipping i18n/polib.py it has no-che?k-code (glob)
28 mercurial/demandimport.py:309:
29 > if os.environ.get('HGDEMANDIMPORT') != 'disable':
30 use encoding.environ instead (py3)
31 mercurial/encoding.py:54:
32 > environ = os.environ
33 use encoding.environ instead (py3)
34 mercurial/encoding.py:56:
35 > environ = os.environb
36 use encoding.environ instead (py3)
37 mercurial/encoding.py:61:
38 > for k, v in os.environ.items())
39 use encoding.environ instead (py3)
40 mercurial/encoding.py:203:
41 > for k, v in os.environ.items())
42 use encoding.environ instead (py3)
13 Skipping mercurial/httpclient/__init__.py it has no-che?k-code (glob)
43 Skipping mercurial/httpclient/__init__.py it has no-che?k-code (glob)
14 Skipping mercurial/httpclient/_readers.py it has no-che?k-code (glob)
44 Skipping mercurial/httpclient/_readers.py it has no-che?k-code (glob)
45 mercurial/policy.py:45:
46 > policy = os.environ.get('HGMODULEPOLICY', policy)
47 use encoding.environ instead (py3)
15 Skipping mercurial/statprof.py it has no-che?k-code (glob)
48 Skipping mercurial/statprof.py it has no-che?k-code (glob)
49 mercurial/win32.py:443:
50 > env, os.getcwd(), ctypes.byref(si), ctypes.byref(pi))
51 use pycompat.getcwd instead (py3)
52 [1]
16
53
17 @commands in debugcommands.py should be in alphabetical order.
54 @commands in debugcommands.py should be in alphabetical order.
18
55
19 >>> import re
56 >>> import re
20 >>> commands = []
57 >>> commands = []
21 >>> with open('mercurial/debugcommands.py', 'rb') as fh:
58 >>> with open('mercurial/debugcommands.py', 'rb') as fh:
22 ... for line in fh:
59 ... for line in fh:
23 ... m = re.match("^@command\('([a-z]+)", line)
60 ... m = re.match("^@command\('([a-z]+)", line)
24 ... if m:
61 ... if m:
25 ... commands.append(m.group(1))
62 ... commands.append(m.group(1))
26 >>> scommands = list(sorted(commands))
63 >>> scommands = list(sorted(commands))
27 >>> for i, command in enumerate(scommands):
64 >>> for i, command in enumerate(scommands):
28 ... if command != commands[i]:
65 ... if command != commands[i]:
29 ... print('commands in debugcommands.py not sorted; first differing '
66 ... print('commands in debugcommands.py not sorted; first differing '
30 ... 'command is %s; expected %s' % (commands[i], command))
67 ... 'command is %s; expected %s' % (commands[i], command))
31 ... break
68 ... break
General Comments 0
You need to be logged in to leave comments. Login now