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