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