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