##// END OF EJS Templates
contrib: make check-code.py check code fragments embedded in test scripts
FUJIWARA Katsunori -
r41992:867883d4 default
parent child Browse files
Show More
@@ -1,828 +1,876 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='latin1')
33 return open(f, encoding='latin1')
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 import testparseutil
44
43 def compilere(pat, multiline=False):
45 def compilere(pat, multiline=False):
44 if multiline:
46 if multiline:
45 pat = '(?m)' + pat
47 pat = '(?m)' + pat
46 if re2:
48 if re2:
47 try:
49 try:
48 return re2.compile(pat)
50 return re2.compile(pat)
49 except re2.error:
51 except re2.error:
50 pass
52 pass
51 return re.compile(pat)
53 return re.compile(pat)
52
54
53 # check "rules depending on implementation of repquote()" in each
55 # check "rules depending on implementation of repquote()" in each
54 # patterns (especially pypats), before changing around repquote()
56 # patterns (especially pypats), before changing around repquote()
55 _repquotefixedmap = {' ': ' ', '\n': '\n', '.': 'p', ':': 'q',
57 _repquotefixedmap = {' ': ' ', '\n': '\n', '.': 'p', ':': 'q',
56 '%': '%', '\\': 'b', '*': 'A', '+': 'P', '-': 'M'}
58 '%': '%', '\\': 'b', '*': 'A', '+': 'P', '-': 'M'}
57 def _repquoteencodechr(i):
59 def _repquoteencodechr(i):
58 if i > 255:
60 if i > 255:
59 return 'u'
61 return 'u'
60 c = chr(i)
62 c = chr(i)
61 if c in _repquotefixedmap:
63 if c in _repquotefixedmap:
62 return _repquotefixedmap[c]
64 return _repquotefixedmap[c]
63 if c.isalpha():
65 if c.isalpha():
64 return 'x'
66 return 'x'
65 if c.isdigit():
67 if c.isdigit():
66 return 'n'
68 return 'n'
67 return 'o'
69 return 'o'
68 _repquotett = ''.join(_repquoteencodechr(i) for i in xrange(256))
70 _repquotett = ''.join(_repquoteencodechr(i) for i in xrange(256))
69
71
70 def repquote(m):
72 def repquote(m):
71 t = m.group('text')
73 t = m.group('text')
72 t = t.translate(_repquotett)
74 t = t.translate(_repquotett)
73 return m.group('quote') + t + m.group('quote')
75 return m.group('quote') + t + m.group('quote')
74
76
75 def reppython(m):
77 def reppython(m):
76 comment = m.group('comment')
78 comment = m.group('comment')
77 if comment:
79 if comment:
78 l = len(comment.rstrip())
80 l = len(comment.rstrip())
79 return "#" * l + comment[l:]
81 return "#" * l + comment[l:]
80 return repquote(m)
82 return repquote(m)
81
83
82 def repcomment(m):
84 def repcomment(m):
83 return m.group(1) + "#" * len(m.group(2))
85 return m.group(1) + "#" * len(m.group(2))
84
86
85 def repccomment(m):
87 def repccomment(m):
86 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
88 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
87 return m.group(1) + t + "*/"
89 return m.group(1) + t + "*/"
88
90
89 def repcallspaces(m):
91 def repcallspaces(m):
90 t = re.sub(r"\n\s+", "\n", m.group(2))
92 t = re.sub(r"\n\s+", "\n", m.group(2))
91 return m.group(1) + t
93 return m.group(1) + t
92
94
93 def repinclude(m):
95 def repinclude(m):
94 return m.group(1) + "<foo>"
96 return m.group(1) + "<foo>"
95
97
96 def rephere(m):
98 def rephere(m):
97 t = re.sub(r"\S", "x", m.group(2))
99 t = re.sub(r"\S", "x", m.group(2))
98 return m.group(1) + t
100 return m.group(1) + t
99
101
100
102
101 testpats = [
103 testpats = [
102 [
104 [
103 (r'\b(push|pop)d\b', "don't use 'pushd' or 'popd', use 'cd'"),
105 (r'\b(push|pop)d\b', "don't use 'pushd' or 'popd', use 'cd'"),
104 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
106 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
105 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
107 (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"),
108 (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"),
109 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
108 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
110 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
109 (r'echo -n', "don't use 'echo -n', use printf"),
111 (r'echo -n', "don't use 'echo -n', use printf"),
110 (r'(^|\|\s*)\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"),
112 (r'(^|\|\s*)\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"),
111 (r'head -c', "don't use 'head -c', use 'dd'"),
113 (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>'"),
114 (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"),
115 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
114 (r'\bls\b.*-\w*R', "don't use 'ls -R', use 'find'"),
116 (r'\bls\b.*-\w*R', "don't use 'ls -R', use 'find'"),
115 (r'printf.*[^\\]\\([1-9]|0\d)', r"don't use 'printf \NNN', use Python"),
117 (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"),
118 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
117 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
119 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
118 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
120 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
119 (r'\[[^\]]+==', '[ foo == bar ] is a bashism, use [ foo = bar ] instead'),
121 (r'\[[^\]]+==', '[ foo == bar ] is a bashism, use [ foo = bar ] instead'),
120 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
122 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
121 "use egrep for extended grep syntax"),
123 "use egrep for extended grep syntax"),
122 (r'(^|\|\s*)e?grep .*\\S', "don't use \\S in regular expression"),
124 (r'(^|\|\s*)e?grep .*\\S', "don't use \\S in regular expression"),
123 (r'(?<!!)/bin/', "don't use explicit paths for tools"),
125 (r'(?<!!)/bin/', "don't use explicit paths for tools"),
124 (r'#!.*/bash', "don't use bash in shebang, use sh"),
126 (r'#!.*/bash', "don't use bash in shebang, use sh"),
125 (r'[^\n]\Z', "no trailing newline"),
127 (r'[^\n]\Z', "no trailing newline"),
126 (r'export .*=', "don't export and assign at once"),
128 (r'export .*=', "don't export and assign at once"),
127 (r'^source\b', "don't use 'source', use '.'"),
129 (r'^source\b', "don't use 'source', use '.'"),
128 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
130 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
129 (r'\bls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
131 (r'\bls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
130 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
132 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
131 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
133 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
132 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
134 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
133 (r'\[\[\s+[^\]]*\]\]', "don't use '[[ ]]', use '[ ]'"),
135 (r'\[\[\s+[^\]]*\]\]', "don't use '[[ ]]', use '[ ]'"),
134 (r'^alias\b.*=', "don't use alias, use a function"),
136 (r'^alias\b.*=', "don't use alias, use a function"),
135 (r'if\s*!', "don't use '!' to negate exit status"),
137 (r'if\s*!', "don't use '!' to negate exit status"),
136 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
138 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
137 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
139 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
138 (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
140 (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
139 "put a backslash-escaped newline after sed 'i' command"),
141 "put a backslash-escaped newline after sed 'i' command"),
140 (r'^diff *-\w*[uU].*$\n(^ \$ |^$)', "prefix diff -u/-U with cmp"),
142 (r'^diff *-\w*[uU].*$\n(^ \$ |^$)', "prefix diff -u/-U with cmp"),
141 (r'^\s+(if)? diff *-\w*[uU]', "prefix diff -u/-U with cmp"),
143 (r'^\s+(if)? diff *-\w*[uU]', "prefix diff -u/-U with cmp"),
142 (r'[\s="`\']python\s(?!bindings)', "don't use 'python', use '$PYTHON'"),
144 (r'[\s="`\']python\s(?!bindings)', "don't use 'python', use '$PYTHON'"),
143 (r'seq ', "don't use 'seq', use $TESTDIR/seq.py"),
145 (r'seq ', "don't use 'seq', use $TESTDIR/seq.py"),
144 (r'\butil\.Abort\b', "directly use error.Abort"),
146 (r'\butil\.Abort\b', "directly use error.Abort"),
145 (r'\|&', "don't use |&, use 2>&1"),
147 (r'\|&', "don't use |&, use 2>&1"),
146 (r'\w = +\w', "only one space after = allowed"),
148 (r'\w = +\w', "only one space after = allowed"),
147 (r'\bsed\b.*[^\\]\\n', "don't use 'sed ... \\n', use a \\ and a newline"),
149 (r'\bsed\b.*[^\\]\\n', "don't use 'sed ... \\n', use a \\ and a newline"),
148 (r'env.*-u', "don't use 'env -u VAR', use 'unset VAR'"),
150 (r'env.*-u', "don't use 'env -u VAR', use 'unset VAR'"),
149 (r'cp.* -r ', "don't use 'cp -r', use 'cp -R'"),
151 (r'cp.* -r ', "don't use 'cp -r', use 'cp -R'"),
150 (r'grep.* -[ABC]', "don't use grep's context flags"),
152 (r'grep.* -[ABC]', "don't use grep's context flags"),
151 (r'find.*-printf',
153 (r'find.*-printf',
152 "don't use 'find -printf', it doesn't exist on BSD find(1)"),
154 "don't use 'find -printf', it doesn't exist on BSD find(1)"),
153 (r'\$RANDOM ', "don't use bash-only $RANDOM to generate random values"),
155 (r'\$RANDOM ', "don't use bash-only $RANDOM to generate random values"),
154 ],
156 ],
155 # warnings
157 # warnings
156 [
158 [
157 (r'^function', "don't use 'function', use old style"),
159 (r'^function', "don't use 'function', use old style"),
158 (r'^diff.*-\w*N', "don't use 'diff -N'"),
160 (r'^diff.*-\w*N', "don't use 'diff -N'"),
159 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
161 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
160 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
162 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
161 (r'kill (`|\$\()', "don't use kill, use killdaemons.py")
163 (r'kill (`|\$\()', "don't use kill, use killdaemons.py")
162 ]
164 ]
163 ]
165 ]
164
166
165 testfilters = [
167 testfilters = [
166 (r"( *)(#([^!][^\n]*\S)?)", repcomment),
168 (r"( *)(#([^!][^\n]*\S)?)", repcomment),
167 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
169 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
168 ]
170 ]
169
171
170 uprefix = r"^ \$ "
172 uprefix = r"^ \$ "
171 utestpats = [
173 utestpats = [
172 [
174 [
173 (r'^(\S.*|| [$>] \S.*)[ \t]\n', "trailing whitespace on non-output"),
175 (r'^(\S.*|| [$>] \S.*)[ \t]\n', "trailing whitespace on non-output"),
174 (uprefix + r'.*\|\s*sed[^|>\n]*\n',
176 (uprefix + r'.*\|\s*sed[^|>\n]*\n',
175 "use regex test output patterns instead of sed"),
177 "use regex test output patterns instead of sed"),
176 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
178 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
177 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
179 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
178 (uprefix + r'.*\|\| echo.*(fail|error)',
180 (uprefix + r'.*\|\| echo.*(fail|error)',
179 "explicit exit code checks unnecessary"),
181 "explicit exit code checks unnecessary"),
180 (uprefix + r'set -e', "don't use set -e"),
182 (uprefix + r'set -e', "don't use set -e"),
181 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
183 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
182 (uprefix + r'.*:\.\S*/', "x:.y in a path does not work on msys, rewrite "
184 (uprefix + r'.*:\.\S*/', "x:.y in a path does not work on msys, rewrite "
183 "as x://.y, or see `hg log -k msys` for alternatives", r'-\S+:\.|' #-Rxxx
185 "as x://.y, or see `hg log -k msys` for alternatives", r'-\S+:\.|' #-Rxxx
184 '# no-msys'), # in test-pull.t which is skipped on windows
186 '# no-msys'), # in test-pull.t which is skipped on windows
185 (r'^ [^$>].*27\.0\.0\.1',
187 (r'^ [^$>].*27\.0\.0\.1',
186 'use $LOCALIP not an explicit loopback address'),
188 'use $LOCALIP not an explicit loopback address'),
187 (r'^ (?![>$] ).*\$LOCALIP.*[^)]$',
189 (r'^ (?![>$] ).*\$LOCALIP.*[^)]$',
188 'mark $LOCALIP output lines with (glob) to help tests in BSD jails'),
190 'mark $LOCALIP output lines with (glob) to help tests in BSD jails'),
189 (r'^ (cat|find): .*: \$ENOENT\$',
191 (r'^ (cat|find): .*: \$ENOENT\$',
190 'use test -f to test for file existence'),
192 'use test -f to test for file existence'),
191 (r'^ diff -[^ -]*p',
193 (r'^ diff -[^ -]*p',
192 "don't use (external) diff with -p for portability"),
194 "don't use (external) diff with -p for portability"),
193 (r' readlink ', 'use readlink.py instead of readlink'),
195 (r' readlink ', 'use readlink.py instead of readlink'),
194 (r'^ [-+][-+][-+] .* [-+]0000 \(glob\)',
196 (r'^ [-+][-+][-+] .* [-+]0000 \(glob\)',
195 "glob timezone field in diff output for portability"),
197 "glob timezone field in diff output for portability"),
196 (r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@',
198 (r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@',
197 "use '@@ -N* +N,n @@ (glob)' style chunk header for portability"),
199 "use '@@ -N* +N,n @@ (glob)' style chunk header for portability"),
198 (r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@',
200 (r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@',
199 "use '@@ -N,n +N* @@ (glob)' style chunk header for portability"),
201 "use '@@ -N,n +N* @@ (glob)' style chunk header for portability"),
200 (r'^ @@ -[0-9]+ [+][0-9]+ @@',
202 (r'^ @@ -[0-9]+ [+][0-9]+ @@',
201 "use '@@ -N* +N* @@ (glob)' style chunk header for portability"),
203 "use '@@ -N* +N* @@ (glob)' style chunk header for portability"),
202 (uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff'
204 (uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff'
203 r'( +(-[^ po-]+|--(?!program|option)[^ ]+|[^-][^ ]*))*$',
205 r'( +(-[^ po-]+|--(?!program|option)[^ ]+|[^-][^ ]*))*$',
204 "use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)"),
206 "use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)"),
205 ],
207 ],
206 # warnings
208 # warnings
207 [
209 [
208 (r'^ (?!.*\$LOCALIP)[^*?/\n]* \(glob\)$',
210 (r'^ (?!.*\$LOCALIP)[^*?/\n]* \(glob\)$',
209 "glob match with no glob string (?, *, /, and $LOCALIP)"),
211 "glob match with no glob string (?, *, /, and $LOCALIP)"),
210 ]
212 ]
211 ]
213 ]
212
214
213 # transform plain test rules to unified test's
215 # transform plain test rules to unified test's
214 for i in [0, 1]:
216 for i in [0, 1]:
215 for tp in testpats[i]:
217 for tp in testpats[i]:
216 p = tp[0]
218 p = tp[0]
217 m = tp[1]
219 m = tp[1]
218 if p.startswith(r'^'):
220 if p.startswith(r'^'):
219 p = r"^ [$>] (%s)" % p[1:]
221 p = r"^ [$>] (%s)" % p[1:]
220 else:
222 else:
221 p = r"^ [$>] .*(%s)" % p
223 p = r"^ [$>] .*(%s)" % p
222 utestpats[i].append((p, m) + tp[2:])
224 utestpats[i].append((p, m) + tp[2:])
223
225
224 # don't transform the following rules:
226 # don't transform the following rules:
225 # " > \t" and " \t" should be allowed in unified tests
227 # " > \t" and " \t" should be allowed in unified tests
226 testpats[0].append((r'^( *)\t', "don't use tabs to indent"))
228 testpats[0].append((r'^( *)\t', "don't use tabs to indent"))
227 utestpats[0].append((r'^( ?)\t', "don't use tabs to indent"))
229 utestpats[0].append((r'^( ?)\t', "don't use tabs to indent"))
228
230
229 utestfilters = [
231 utestfilters = [
230 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
232 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
231 (r"( +)(#([^!][^\n]*\S)?)", repcomment),
233 (r"( +)(#([^!][^\n]*\S)?)", repcomment),
232 ]
234 ]
233
235
234 # common patterns to check *.py
236 # common patterns to check *.py
235 commonpypats = [
237 commonpypats = [
236 [
238 [
237 (r'\\$', 'Use () to wrap long lines in Python, not \\'),
239 (r'\\$', 'Use () to wrap long lines in Python, not \\'),
238 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
240 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
239 "tuple parameter unpacking not available in Python 3+"),
241 "tuple parameter unpacking not available in Python 3+"),
240 (r'lambda\s*\(.*,.*\)',
242 (r'lambda\s*\(.*,.*\)',
241 "tuple parameter unpacking not available in Python 3+"),
243 "tuple parameter unpacking not available in Python 3+"),
242 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
244 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
243 (r'(?<!\.)\breduce\s*\(.*', "reduce is not available in Python 3+"),
245 (r'(?<!\.)\breduce\s*\(.*', "reduce is not available in Python 3+"),
244 (r'\bdict\(.*=', 'dict() is different in Py2 and 3 and is slower than {}',
246 (r'\bdict\(.*=', 'dict() is different in Py2 and 3 and is slower than {}',
245 'dict-from-generator'),
247 'dict-from-generator'),
246 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
248 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
247 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
249 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
248 (r'^\s*\t', "don't use tabs"),
250 (r'^\s*\t', "don't use tabs"),
249 (r'\S;\s*\n', "semicolon"),
251 (r'\S;\s*\n', "semicolon"),
250 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
252 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
251 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
253 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
252 (r'(\w|\)),\w', "missing whitespace after ,"),
254 (r'(\w|\)),\w', "missing whitespace after ,"),
253 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
255 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
254 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
256 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
255 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
257 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
256 ((
258 ((
257 # a line ending with a colon, potentially with trailing comments
259 # a line ending with a colon, potentially with trailing comments
258 r':([ \t]*#[^\n]*)?\n'
260 r':([ \t]*#[^\n]*)?\n'
259 # one that is not a pass and not only a comment
261 # one that is not a pass and not only a comment
260 r'(?P<indent>[ \t]+)[^#][^\n]+\n'
262 r'(?P<indent>[ \t]+)[^#][^\n]+\n'
261 # more lines at the same indent level
263 # more lines at the same indent level
262 r'((?P=indent)[^\n]+\n)*'
264 r'((?P=indent)[^\n]+\n)*'
263 # a pass at the same indent level, which is bogus
265 # a pass at the same indent level, which is bogus
264 r'(?P=indent)pass[ \t\n#]'
266 r'(?P=indent)pass[ \t\n#]'
265 ), 'omit superfluous pass'),
267 ), 'omit superfluous pass'),
266 (r'[^\n]\Z', "no trailing newline"),
268 (r'[^\n]\Z', "no trailing newline"),
267 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
269 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
268 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
270 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
269 # "don't use underbars in identifiers"),
271 # "don't use underbars in identifiers"),
270 (r'^\s+(self\.)?[A-Za-z][a-z0-9]+[A-Z]\w* = ',
272 (r'^\s+(self\.)?[A-Za-z][a-z0-9]+[A-Z]\w* = ',
271 "don't use camelcase in identifiers", r'#.*camelcase-required'),
273 "don't use camelcase in identifiers", r'#.*camelcase-required'),
272 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
274 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
273 "linebreak after :"),
275 "linebreak after :"),
274 (r'class\s[^( \n]+:', "old-style class, use class foo(object)",
276 (r'class\s[^( \n]+:', "old-style class, use class foo(object)",
275 r'#.*old-style'),
277 r'#.*old-style'),
276 (r'class\s[^( \n]+\(\):',
278 (r'class\s[^( \n]+\(\):',
277 "class foo() creates old style object, use class foo(object)",
279 "class foo() creates old style object, use class foo(object)",
278 r'#.*old-style'),
280 r'#.*old-style'),
279 (r'\b(%s)\(' % '|'.join(k for k in keyword.kwlist
281 (r'\b(%s)\(' % '|'.join(k for k in keyword.kwlist
280 if k not in ('print', 'exec')),
282 if k not in ('print', 'exec')),
281 "Python keyword is not a function"),
283 "Python keyword is not a function"),
282 (r',]', "unneeded trailing ',' in list"),
284 (r',]', "unneeded trailing ',' in list"),
283 # (r'class\s[A-Z][^\(]*\((?!Exception)',
285 # (r'class\s[A-Z][^\(]*\((?!Exception)',
284 # "don't capitalize non-exception classes"),
286 # "don't capitalize non-exception classes"),
285 # (r'in range\(', "use xrange"),
287 # (r'in range\(', "use xrange"),
286 # (r'^\s*print\s+', "avoid using print in core and extensions"),
288 # (r'^\s*print\s+', "avoid using print in core and extensions"),
287 (r'[\x80-\xff]', "non-ASCII character literal"),
289 (r'[\x80-\xff]', "non-ASCII character literal"),
288 (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"),
290 (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"),
289 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
291 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
290 "gratuitous whitespace after Python keyword"),
292 "gratuitous whitespace after Python keyword"),
291 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
293 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
292 # (r'\s\s=', "gratuitous whitespace before ="),
294 # (r'\s\s=', "gratuitous whitespace before ="),
293 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
295 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
294 "missing whitespace around operator"),
296 "missing whitespace around operator"),
295 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
297 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
296 "missing whitespace around operator"),
298 "missing whitespace around operator"),
297 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
299 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
298 "missing whitespace around operator"),
300 "missing whitespace around operator"),
299 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
301 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
300 "wrong whitespace around ="),
302 "wrong whitespace around ="),
301 (r'\([^()]*( =[^=]|[^<>!=]= )',
303 (r'\([^()]*( =[^=]|[^<>!=]= )',
302 "no whitespace around = for named parameters"),
304 "no whitespace around = for named parameters"),
303 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
305 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
304 "don't use old-style two-argument raise, use Exception(message)"),
306 "don't use old-style two-argument raise, use Exception(message)"),
305 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
307 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
306 (r' [=!]=\s+(True|False|None)',
308 (r' [=!]=\s+(True|False|None)',
307 "comparison with singleton, use 'is' or 'is not' instead"),
309 "comparison with singleton, use 'is' or 'is not' instead"),
308 (r'^\s*(while|if) [01]:',
310 (r'^\s*(while|if) [01]:',
309 "use True/False for constant Boolean expression"),
311 "use True/False for constant Boolean expression"),
310 (r'^\s*if False(:| +and)', 'Remove code instead of using `if False`'),
312 (r'^\s*if False(:| +and)', 'Remove code instead of using `if False`'),
311 (r'(?:(?<!def)\s+|\()hasattr\(',
313 (r'(?:(?<!def)\s+|\()hasattr\(',
312 'hasattr(foo, bar) is broken on py2, use util.safehasattr(foo, bar) '
314 'hasattr(foo, bar) is broken on py2, use util.safehasattr(foo, bar) '
313 'instead', r'#.*hasattr-py3-only'),
315 'instead', r'#.*hasattr-py3-only'),
314 (r'opener\([^)]*\).read\(',
316 (r'opener\([^)]*\).read\(',
315 "use opener.read() instead"),
317 "use opener.read() instead"),
316 (r'opener\([^)]*\).write\(',
318 (r'opener\([^)]*\).write\(',
317 "use opener.write() instead"),
319 "use opener.write() instead"),
318 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
320 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
319 (r'\.debug\(\_', "don't mark debug messages for translation"),
321 (r'\.debug\(\_', "don't mark debug messages for translation"),
320 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
322 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
321 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
323 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
322 (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
324 (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
323 'legacy exception syntax; use "as" instead of ","'),
325 'legacy exception syntax; use "as" instead of ","'),
324 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
326 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
325 (r'\bdef\s+__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
327 (r'\bdef\s+__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
326 (r'os\.path\.join\(.*, *(""|\'\')\)',
328 (r'os\.path\.join\(.*, *(""|\'\')\)',
327 "use pathutil.normasprefix(path) instead of os.path.join(path, '')"),
329 "use pathutil.normasprefix(path) instead of os.path.join(path, '')"),
328 (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
330 (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
329 # XXX only catch mutable arguments on the first line of the definition
331 # XXX only catch mutable arguments on the first line of the definition
330 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
332 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
331 (r'\butil\.Abort\b', "directly use error.Abort"),
333 (r'\butil\.Abort\b', "directly use error.Abort"),
332 (r'^@(\w*\.)?cachefunc', "module-level @cachefunc is risky, please avoid"),
334 (r'^@(\w*\.)?cachefunc', "module-level @cachefunc is risky, please avoid"),
333 (r'^import Queue', "don't use Queue, use pycompat.queue.Queue + "
335 (r'^import Queue', "don't use Queue, use pycompat.queue.Queue + "
334 "pycompat.queue.Empty"),
336 "pycompat.queue.Empty"),
335 (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"),
337 (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"),
336 (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
338 (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
337 (r'^import SocketServer', "don't use SockerServer, use util.socketserver"),
339 (r'^import SocketServer', "don't use SockerServer, use util.socketserver"),
338 (r'^import urlparse', "don't use urlparse, use util.urlreq"),
340 (r'^import urlparse', "don't use urlparse, use util.urlreq"),
339 (r'^import xmlrpclib', "don't use xmlrpclib, use util.xmlrpclib"),
341 (r'^import xmlrpclib', "don't use xmlrpclib, use util.xmlrpclib"),
340 (r'^import cPickle', "don't use cPickle, use util.pickle"),
342 (r'^import cPickle', "don't use cPickle, use util.pickle"),
341 (r'^import pickle', "don't use pickle, use util.pickle"),
343 (r'^import pickle', "don't use pickle, use util.pickle"),
342 (r'^import httplib', "don't use httplib, use util.httplib"),
344 (r'^import httplib', "don't use httplib, use util.httplib"),
343 (r'^import BaseHTTPServer', "use util.httpserver instead"),
345 (r'^import BaseHTTPServer', "use util.httpserver instead"),
344 (r'^(from|import) mercurial\.(cext|pure|cffi)',
346 (r'^(from|import) mercurial\.(cext|pure|cffi)',
345 "use mercurial.policy.importmod instead"),
347 "use mercurial.policy.importmod instead"),
346 (r'\.next\(\)', "don't use .next(), use next(...)"),
348 (r'\.next\(\)', "don't use .next(), use next(...)"),
347 (r'([a-z]*).revision\(\1\.node\(',
349 (r'([a-z]*).revision\(\1\.node\(',
348 "don't convert rev to node before passing to revision(nodeorrev)"),
350 "don't convert rev to node before passing to revision(nodeorrev)"),
349 (r'platform\.system\(\)', "don't use platform.system(), use pycompat"),
351 (r'platform\.system\(\)', "don't use platform.system(), use pycompat"),
350
352
351 ],
353 ],
352 # warnings
354 # warnings
353 [
355 [
354 ]
356 ]
355 ]
357 ]
356
358
357 # patterns to check normal *.py files
359 # patterns to check normal *.py files
358 pypats = [
360 pypats = [
359 [
361 [
360 # Ideally, these should be placed in "commonpypats" for
362 # Ideally, these should be placed in "commonpypats" for
361 # consistency of coding rules in Mercurial source tree.
363 # consistency of coding rules in Mercurial source tree.
362 # But on the other hand, these are not so seriously required for
364 # But on the other hand, these are not so seriously required for
363 # python code fragments embedded in test scripts. Fixing test
365 # python code fragments embedded in test scripts. Fixing test
364 # scripts for these patterns requires many changes, and has less
366 # scripts for these patterns requires many changes, and has less
365 # profit than effort.
367 # profit than effort.
366 (r'.{81}', "line too long"),
368 (r'.{81}', "line too long"),
367 (r'raise Exception', "don't raise generic exceptions"),
369 (r'raise Exception', "don't raise generic exceptions"),
368 (r'[\s\(](open|file)\([^)]*\)\.read\(',
370 (r'[\s\(](open|file)\([^)]*\)\.read\(',
369 "use util.readfile() instead"),
371 "use util.readfile() instead"),
370 (r'[\s\(](open|file)\([^)]*\)\.write\(',
372 (r'[\s\(](open|file)\([^)]*\)\.write\(',
371 "use util.writefile() instead"),
373 "use util.writefile() instead"),
372 (r'^[\s\(]*(open(er)?|file)\([^)]*\)(?!\.close\(\))',
374 (r'^[\s\(]*(open(er)?|file)\([^)]*\)(?!\.close\(\))',
373 "always assign an opened file to a variable, and close it afterwards"),
375 "always assign an opened file to a variable, and close it afterwards"),
374 (r'[\s\(](open|file)\([^)]*\)\.(?!close\(\))',
376 (r'[\s\(](open|file)\([^)]*\)\.(?!close\(\))',
375 "always assign an opened file to a variable, and close it afterwards"),
377 "always assign an opened file to a variable, and close it afterwards"),
376 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
378 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
377 (r'^import atexit', "don't use atexit, use ui.atexit"),
379 (r'^import atexit', "don't use atexit, use ui.atexit"),
378
380
379 # rules depending on implementation of repquote()
381 # rules depending on implementation of repquote()
380 (r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
382 (r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
381 'string join across lines with no space'),
383 'string join across lines with no space'),
382 (r'''(?x)ui\.(status|progress|write|note|warn)\(
384 (r'''(?x)ui\.(status|progress|write|note|warn)\(
383 [ \t\n#]*
385 [ \t\n#]*
384 (?# any strings/comments might precede a string, which
386 (?# any strings/comments might precede a string, which
385 # contains translatable message)
387 # contains translatable message)
386 ((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
388 ((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
387 (?# sequence consisting of below might precede translatable message
389 (?# sequence consisting of below might precede translatable message
388 # - formatting string: "% 10s", "%05d", "% -3.2f", "%*s", "%%" ...
390 # - formatting string: "% 10s", "%05d", "% -3.2f", "%*s", "%%" ...
389 # - escaped character: "\\", "\n", "\0" ...
391 # - escaped character: "\\", "\n", "\0" ...
390 # - character other than '%', 'b' as '\', and 'x' as alphabet)
392 # - character other than '%', 'b' as '\', and 'x' as alphabet)
391 (['"]|\'\'\'|""")
393 (['"]|\'\'\'|""")
392 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
394 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
393 (?# this regexp can't use [^...] style,
395 (?# this regexp can't use [^...] style,
394 # because _preparepats forcibly adds "\n" into [^...],
396 # because _preparepats forcibly adds "\n" into [^...],
395 # even though this regexp wants match it against "\n")''',
397 # even though this regexp wants match it against "\n")''',
396 "missing _() in ui message (use () to hide false-positives)"),
398 "missing _() in ui message (use () to hide false-positives)"),
397 ] + commonpypats[0],
399 ] + commonpypats[0],
398 # warnings
400 # warnings
399 [
401 [
400 # rules depending on implementation of repquote()
402 # rules depending on implementation of repquote()
401 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
403 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
402 ] + commonpypats[1]
404 ] + commonpypats[1]
403 ]
405 ]
404
406
407 # patterns to check *.py for embedded ones in test script
408 embeddedpypats = [
409 [
410 ] + commonpypats[0],
411 # warnings
412 [
413 ] + commonpypats[1]
414 ]
415
405 # common filters to convert *.py
416 # common filters to convert *.py
406 commonpyfilters = [
417 commonpyfilters = [
407 (r"""(?msx)(?P<comment>\#.*?$)|
418 (r"""(?msx)(?P<comment>\#.*?$)|
408 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
419 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
409 (?P<text>(([^\\]|\\.)*?))
420 (?P<text>(([^\\]|\\.)*?))
410 (?P=quote))""", reppython),
421 (?P=quote))""", reppython),
411 ]
422 ]
412
423
413 # filters to convert normal *.py files
424 # filters to convert normal *.py files
414 pyfilters = [
425 pyfilters = [
415 ] + commonpyfilters
426 ] + commonpyfilters
416
427
417 # non-filter patterns
428 # non-filter patterns
418 pynfpats = [
429 pynfpats = [
419 [
430 [
420 (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"),
431 (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"),
421 (r'pycompat\.osname\s*[=!]=\s*[\'"]posix[\'"]', "use pycompat.isposix"),
432 (r'pycompat\.osname\s*[=!]=\s*[\'"]posix[\'"]', "use pycompat.isposix"),
422 (r'pycompat\.sysplatform\s*[!=]=\s*[\'"]darwin[\'"]',
433 (r'pycompat\.sysplatform\s*[!=]=\s*[\'"]darwin[\'"]',
423 "use pycompat.isdarwin"),
434 "use pycompat.isdarwin"),
424 ],
435 ],
425 # warnings
436 # warnings
426 [],
437 [],
427 ]
438 ]
428
439
440 # filters to convert *.py for embedded ones in test script
441 embeddedpyfilters = [
442 ] + commonpyfilters
443
429 # extension non-filter patterns
444 # extension non-filter patterns
430 pyextnfpats = [
445 pyextnfpats = [
431 [(r'^"""\n?[A-Z]', "don't capitalize docstring title")],
446 [(r'^"""\n?[A-Z]', "don't capitalize docstring title")],
432 # warnings
447 # warnings
433 [],
448 [],
434 ]
449 ]
435
450
436 txtfilters = []
451 txtfilters = []
437
452
438 txtpats = [
453 txtpats = [
439 [
454 [
440 (r'\s$', 'trailing whitespace'),
455 (r'\s$', 'trailing whitespace'),
441 ('.. note::[ \n][^\n]', 'add two newlines after note::')
456 ('.. note::[ \n][^\n]', 'add two newlines after note::')
442 ],
457 ],
443 []
458 []
444 ]
459 ]
445
460
446 cpats = [
461 cpats = [
447 [
462 [
448 (r'//', "don't use //-style comments"),
463 (r'//', "don't use //-style comments"),
449 (r'\S\t', "don't use tabs except for indent"),
464 (r'\S\t', "don't use tabs except for indent"),
450 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
465 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
451 (r'.{81}', "line too long"),
466 (r'.{81}', "line too long"),
452 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
467 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
453 (r'return\(', "return is not a function"),
468 (r'return\(', "return is not a function"),
454 (r' ;', "no space before ;"),
469 (r' ;', "no space before ;"),
455 (r'[^;] \)', "no space before )"),
470 (r'[^;] \)', "no space before )"),
456 (r'[)][{]', "space between ) and {"),
471 (r'[)][{]', "space between ) and {"),
457 (r'\w+\* \w+', "use int *foo, not int* foo"),
472 (r'\w+\* \w+', "use int *foo, not int* foo"),
458 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
473 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
459 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
474 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
460 (r'\w,\w', "missing whitespace after ,"),
475 (r'\w,\w', "missing whitespace after ,"),
461 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
476 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
462 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
477 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
463 (r'^#\s+\w', "use #foo, not # foo"),
478 (r'^#\s+\w', "use #foo, not # foo"),
464 (r'[^\n]\Z', "no trailing newline"),
479 (r'[^\n]\Z', "no trailing newline"),
465 (r'^\s*#import\b', "use only #include in standard C code"),
480 (r'^\s*#import\b', "use only #include in standard C code"),
466 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
481 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
467 (r'strcat\(', "don't use strcat"),
482 (r'strcat\(', "don't use strcat"),
468
483
469 # rules depending on implementation of repquote()
484 # rules depending on implementation of repquote()
470 ],
485 ],
471 # warnings
486 # warnings
472 [
487 [
473 # rules depending on implementation of repquote()
488 # rules depending on implementation of repquote()
474 ]
489 ]
475 ]
490 ]
476
491
477 cfilters = [
492 cfilters = [
478 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
493 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
479 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
494 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
480 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
495 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
481 (r'(\()([^)]+\))', repcallspaces),
496 (r'(\()([^)]+\))', repcallspaces),
482 ]
497 ]
483
498
484 inutilpats = [
499 inutilpats = [
485 [
500 [
486 (r'\bui\.', "don't use ui in util"),
501 (r'\bui\.', "don't use ui in util"),
487 ],
502 ],
488 # warnings
503 # warnings
489 []
504 []
490 ]
505 ]
491
506
492 inrevlogpats = [
507 inrevlogpats = [
493 [
508 [
494 (r'\brepo\.', "don't use repo in revlog"),
509 (r'\brepo\.', "don't use repo in revlog"),
495 ],
510 ],
496 # warnings
511 # warnings
497 []
512 []
498 ]
513 ]
499
514
500 webtemplatefilters = []
515 webtemplatefilters = []
501
516
502 webtemplatepats = [
517 webtemplatepats = [
503 [],
518 [],
504 [
519 [
505 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
520 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
506 'follow desc keyword with either firstline or websub'),
521 'follow desc keyword with either firstline or websub'),
507 ]
522 ]
508 ]
523 ]
509
524
510 allfilesfilters = []
525 allfilesfilters = []
511
526
512 allfilespats = [
527 allfilespats = [
513 [
528 [
514 (r'(http|https)://[a-zA-Z0-9./]*selenic.com/',
529 (r'(http|https)://[a-zA-Z0-9./]*selenic.com/',
515 'use mercurial-scm.org domain URL'),
530 'use mercurial-scm.org domain URL'),
516 (r'mercurial@selenic\.com',
531 (r'mercurial@selenic\.com',
517 'use mercurial-scm.org domain for mercurial ML address'),
532 'use mercurial-scm.org domain for mercurial ML address'),
518 (r'mercurial-devel@selenic\.com',
533 (r'mercurial-devel@selenic\.com',
519 'use mercurial-scm.org domain for mercurial-devel ML address'),
534 'use mercurial-scm.org domain for mercurial-devel ML address'),
520 ],
535 ],
521 # warnings
536 # warnings
522 [],
537 [],
523 ]
538 ]
524
539
525 py3pats = [
540 py3pats = [
526 [
541 [
527 (r'os\.environ', "use encoding.environ instead (py3)", r'#.*re-exports'),
542 (r'os\.environ', "use encoding.environ instead (py3)", r'#.*re-exports'),
528 (r'os\.name', "use pycompat.osname instead (py3)"),
543 (r'os\.name', "use pycompat.osname instead (py3)"),
529 (r'os\.getcwd', "use encoding.getcwd instead (py3)", r'#.*re-exports'),
544 (r'os\.getcwd', "use encoding.getcwd instead (py3)", r'#.*re-exports'),
530 (r'os\.sep', "use pycompat.ossep instead (py3)"),
545 (r'os\.sep', "use pycompat.ossep instead (py3)"),
531 (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"),
546 (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"),
532 (r'os\.altsep', "use pycompat.osaltsep instead (py3)"),
547 (r'os\.altsep', "use pycompat.osaltsep instead (py3)"),
533 (r'sys\.platform', "use pycompat.sysplatform instead (py3)"),
548 (r'sys\.platform', "use pycompat.sysplatform instead (py3)"),
534 (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"),
549 (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"),
535 (r'os\.getenv', "use encoding.environ.get instead"),
550 (r'os\.getenv', "use encoding.environ.get instead"),
536 (r'os\.setenv', "modifying the environ dict is not preferred"),
551 (r'os\.setenv', "modifying the environ dict is not preferred"),
537 (r'(?<!pycompat\.)xrange', "use pycompat.xrange instead (py3)"),
552 (r'(?<!pycompat\.)xrange', "use pycompat.xrange instead (py3)"),
538 ],
553 ],
539 # warnings
554 # warnings
540 [],
555 [],
541 ]
556 ]
542
557
543 checks = [
558 checks = [
544 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
559 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
545 ('python', r'.*\.(py|cgi)$', r'^#!.*python', [], pynfpats),
560 ('python', r'.*\.(py|cgi)$', r'^#!.*python', [], pynfpats),
546 ('python', r'.*hgext.*\.py$', '', [], pyextnfpats),
561 ('python', r'.*hgext.*\.py$', '', [], pyextnfpats),
547 ('python 3', r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
562 ('python 3', r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
548 '', pyfilters, py3pats),
563 '', pyfilters, py3pats),
549 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
564 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
550 ('c', r'.*\.[ch]$', '', cfilters, cpats),
565 ('c', r'.*\.[ch]$', '', cfilters, cpats),
551 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
566 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
552 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
567 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
553 pyfilters, inrevlogpats),
568 pyfilters, inrevlogpats),
554 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
569 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
555 inutilpats),
570 inutilpats),
556 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
571 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
557 ('web template', r'mercurial/templates/.*\.tmpl', '',
572 ('web template', r'mercurial/templates/.*\.tmpl', '',
558 webtemplatefilters, webtemplatepats),
573 webtemplatefilters, webtemplatepats),
559 ('all except for .po', r'.*(?<!\.po)$', '',
574 ('all except for .po', r'.*(?<!\.po)$', '',
560 allfilesfilters, allfilespats),
575 allfilesfilters, allfilespats),
561 ]
576 ]
562
577
578 # (desc,
579 # func to pick up embedded code fragments,
580 # list of patterns to convert target files
581 # list of patterns to detect errors/warnings)
582 embeddedchecks = [
583 ('embedded python',
584 testparseutil.pyembedded, embeddedpyfilters, embeddedpypats)
585 ]
586
563 def _preparepats():
587 def _preparepats():
564 def preparefailandwarn(failandwarn):
588 def preparefailandwarn(failandwarn):
565 for pats in failandwarn:
589 for pats in failandwarn:
566 for i, pseq in enumerate(pats):
590 for i, pseq in enumerate(pats):
567 # fix-up regexes for multi-line searches
591 # fix-up regexes for multi-line searches
568 p = pseq[0]
592 p = pseq[0]
569 # \s doesn't match \n (done in two steps)
593 # \s doesn't match \n (done in two steps)
570 # first, we replace \s that appears in a set already
594 # first, we replace \s that appears in a set already
571 p = re.sub(r'\[\\s', r'[ \\t', p)
595 p = re.sub(r'\[\\s', r'[ \\t', p)
572 # now we replace other \s instances.
596 # now we replace other \s instances.
573 p = re.sub(r'(?<!(\\|\[))\\s', r'[ \\t]', p)
597 p = re.sub(r'(?<!(\\|\[))\\s', r'[ \\t]', p)
574 # [^...] doesn't match newline
598 # [^...] doesn't match newline
575 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
599 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
576
600
577 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
601 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
578
602
579 def preparefilters(filters):
603 def preparefilters(filters):
580 for i, flt in enumerate(filters):
604 for i, flt in enumerate(filters):
581 filters[i] = re.compile(flt[0]), flt[1]
605 filters[i] = re.compile(flt[0]), flt[1]
582
606
583 for cs in (checks,):
607 for cs in (checks, embeddedchecks):
584 for c in cs:
608 for c in cs:
585 failandwarn = c[-1]
609 failandwarn = c[-1]
586 preparefailandwarn(failandwarn)
610 preparefailandwarn(failandwarn)
587
611
588 filters = c[-2]
612 filters = c[-2]
589 preparefilters(filters)
613 preparefilters(filters)
590
614
591 class norepeatlogger(object):
615 class norepeatlogger(object):
592 def __init__(self):
616 def __init__(self):
593 self._lastseen = None
617 self._lastseen = None
594
618
595 def log(self, fname, lineno, line, msg, blame):
619 def log(self, fname, lineno, line, msg, blame):
596 """print error related a to given line of a given file.
620 """print error related a to given line of a given file.
597
621
598 The faulty line will also be printed but only once in the case
622 The faulty line will also be printed but only once in the case
599 of multiple errors.
623 of multiple errors.
600
624
601 :fname: filename
625 :fname: filename
602 :lineno: line number
626 :lineno: line number
603 :line: actual content of the line
627 :line: actual content of the line
604 :msg: error message
628 :msg: error message
605 """
629 """
606 msgid = fname, lineno, line
630 msgid = fname, lineno, line
607 if msgid != self._lastseen:
631 if msgid != self._lastseen:
608 if blame:
632 if blame:
609 print("%s:%d (%s):" % (fname, lineno, blame))
633 print("%s:%d (%s):" % (fname, lineno, blame))
610 else:
634 else:
611 print("%s:%d:" % (fname, lineno))
635 print("%s:%d:" % (fname, lineno))
612 print(" > %s" % line)
636 print(" > %s" % line)
613 self._lastseen = msgid
637 self._lastseen = msgid
614 print(" " + msg)
638 print(" " + msg)
615
639
616 _defaultlogger = norepeatlogger()
640 _defaultlogger = norepeatlogger()
617
641
618 def getblame(f):
642 def getblame(f):
619 lines = []
643 lines = []
620 for l in os.popen('hg annotate -un %s' % f):
644 for l in os.popen('hg annotate -un %s' % f):
621 start, line = l.split(':', 1)
645 start, line = l.split(':', 1)
622 user, rev = start.split()
646 user, rev = start.split()
623 lines.append((line[1:-1], user, rev))
647 lines.append((line[1:-1], user, rev))
624 return lines
648 return lines
625
649
626 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
650 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
627 blame=False, debug=False, lineno=True):
651 blame=False, debug=False, lineno=True):
628 """checks style and portability of a given file
652 """checks style and portability of a given file
629
653
630 :f: filepath
654 :f: filepath
631 :logfunc: function used to report error
655 :logfunc: function used to report error
632 logfunc(filename, linenumber, linecontent, errormessage)
656 logfunc(filename, linenumber, linecontent, errormessage)
633 :maxerr: number of error to display before aborting.
657 :maxerr: number of error to display before aborting.
634 Set to false (default) to report all errors
658 Set to false (default) to report all errors
635
659
636 return True if no error is found, False otherwise.
660 return True if no error is found, False otherwise.
637 """
661 """
638 result = True
662 result = True
639
663
640 try:
664 try:
641 with opentext(f) as fp:
665 with opentext(f) as fp:
642 try:
666 try:
643 pre = fp.read()
667 pre = fp.read()
644 except UnicodeDecodeError as e:
668 except UnicodeDecodeError as e:
645 print("%s while reading %s" % (e, f))
669 print("%s while reading %s" % (e, f))
646 return result
670 return result
647 except IOError as e:
671 except IOError as e:
648 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
672 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
649 return result
673 return result
650
674
651 # context information shared while single checkfile() invocation
675 # context information shared while single checkfile() invocation
652 context = {'blamecache': None}
676 context = {'blamecache': None}
653
677
654 for name, match, magic, filters, pats in checks:
678 for name, match, magic, filters, pats in checks:
655 if debug:
679 if debug:
656 print(name, f)
680 print(name, f)
657 if not (re.match(match, f) or (magic and re.search(magic, pre))):
681 if not (re.match(match, f) or (magic and re.search(magic, pre))):
658 if debug:
682 if debug:
659 print("Skipping %s for %s it doesn't match %s" % (
683 print("Skipping %s for %s it doesn't match %s" % (
660 name, match, f))
684 name, match, f))
661 continue
685 continue
662 if "no-" "check-code" in pre:
686 if "no-" "check-code" in pre:
663 # If you're looking at this line, it's because a file has:
687 # If you're looking at this line, it's because a file has:
664 # no- check- code
688 # no- check- code
665 # but the reason to output skipping is to make life for
689 # but the reason to output skipping is to make life for
666 # tests easier. So, instead of writing it with a normal
690 # tests easier. So, instead of writing it with a normal
667 # spelling, we write it with the expected spelling from
691 # spelling, we write it with the expected spelling from
668 # tests/test-check-code.t
692 # tests/test-check-code.t
669 print("Skipping %s it has no-che?k-code (glob)" % f)
693 print("Skipping %s it has no-che?k-code (glob)" % f)
670 return "Skip" # skip checking this file
694 return "Skip" # skip checking this file
671
695
672 fc = _checkfiledata(name, f, pre, filters, pats, context,
696 fc = _checkfiledata(name, f, pre, filters, pats, context,
673 logfunc, maxerr, warnings, blame, debug, lineno)
697 logfunc, maxerr, warnings, blame, debug, lineno)
674 if fc:
698 if fc:
675 result = False
699 result = False
676
700
701 if f.endswith('.t') and "no-" "check-code" not in pre:
702 if debug:
703 print("Checking embedded code in %s" % (f))
704
705 prelines = pre.splitlines()
706 embeddederros = []
707 for name, embedded, filters, pats in embeddedchecks:
708 # "reset curmax at each repetition" treats maxerr as "max
709 # nubmer of errors in an actual file per entry of
710 # (embedded)checks"
711 curmaxerr = maxerr
712
713 for found in embedded(f, prelines, embeddederros):
714 filename, starts, ends, code = found
715 fc = _checkfiledata(name, f, code, filters, pats, context,
716 logfunc, curmaxerr, warnings, blame, debug,
717 lineno, offset=starts - 1)
718 if fc:
719 result = False
720 if curmaxerr:
721 if fc >= curmaxerr:
722 break
723 curmaxerr -= fc
724
677 return result
725 return result
678
726
679 def _checkfiledata(name, f, filedata, filters, pats, context,
727 def _checkfiledata(name, f, filedata, filters, pats, context,
680 logfunc, maxerr, warnings, blame, debug, lineno,
728 logfunc, maxerr, warnings, blame, debug, lineno,
681 offset=None):
729 offset=None):
682 """Execute actual error check for file data
730 """Execute actual error check for file data
683
731
684 :name: of the checking category
732 :name: of the checking category
685 :f: filepath
733 :f: filepath
686 :filedata: content of a file
734 :filedata: content of a file
687 :filters: to be applied before checking
735 :filters: to be applied before checking
688 :pats: to detect errors
736 :pats: to detect errors
689 :context: a dict of information shared while single checkfile() invocation
737 :context: a dict of information shared while single checkfile() invocation
690 Valid keys: 'blamecache'.
738 Valid keys: 'blamecache'.
691 :logfunc: function used to report error
739 :logfunc: function used to report error
692 logfunc(filename, linenumber, linecontent, errormessage)
740 logfunc(filename, linenumber, linecontent, errormessage)
693 :maxerr: number of error to display before aborting, or False to
741 :maxerr: number of error to display before aborting, or False to
694 report all errors
742 report all errors
695 :warnings: whether warning level checks should be applied
743 :warnings: whether warning level checks should be applied
696 :blame: whether blame information should be displayed at error reporting
744 :blame: whether blame information should be displayed at error reporting
697 :debug: whether debug information should be displayed
745 :debug: whether debug information should be displayed
698 :lineno: whether lineno should be displayed at error reporting
746 :lineno: whether lineno should be displayed at error reporting
699 :offset: line number offset of 'filedata' in 'f' for checking
747 :offset: line number offset of 'filedata' in 'f' for checking
700 an embedded code fragment, or None (offset=0 is different
748 an embedded code fragment, or None (offset=0 is different
701 from offset=None)
749 from offset=None)
702
750
703 returns number of detected errors.
751 returns number of detected errors.
704 """
752 """
705 blamecache = context['blamecache']
753 blamecache = context['blamecache']
706 if offset is None:
754 if offset is None:
707 lineoffset = 0
755 lineoffset = 0
708 else:
756 else:
709 lineoffset = offset
757 lineoffset = offset
710
758
711 fc = 0
759 fc = 0
712 pre = post = filedata
760 pre = post = filedata
713
761
714 if True: # TODO: get rid of this redundant 'if' block
762 if True: # TODO: get rid of this redundant 'if' block
715 for p, r in filters:
763 for p, r in filters:
716 post = re.sub(p, r, post)
764 post = re.sub(p, r, post)
717 nerrs = len(pats[0]) # nerr elements are errors
765 nerrs = len(pats[0]) # nerr elements are errors
718 if warnings:
766 if warnings:
719 pats = pats[0] + pats[1]
767 pats = pats[0] + pats[1]
720 else:
768 else:
721 pats = pats[0]
769 pats = pats[0]
722 # print post # uncomment to show filtered version
770 # print post # uncomment to show filtered version
723
771
724 if debug:
772 if debug:
725 print("Checking %s for %s" % (name, f))
773 print("Checking %s for %s" % (name, f))
726
774
727 prelines = None
775 prelines = None
728 errors = []
776 errors = []
729 for i, pat in enumerate(pats):
777 for i, pat in enumerate(pats):
730 if len(pat) == 3:
778 if len(pat) == 3:
731 p, msg, ignore = pat
779 p, msg, ignore = pat
732 else:
780 else:
733 p, msg = pat
781 p, msg = pat
734 ignore = None
782 ignore = None
735 if i >= nerrs:
783 if i >= nerrs:
736 msg = "warning: " + msg
784 msg = "warning: " + msg
737
785
738 pos = 0
786 pos = 0
739 n = 0
787 n = 0
740 for m in p.finditer(post):
788 for m in p.finditer(post):
741 if prelines is None:
789 if prelines is None:
742 prelines = pre.splitlines()
790 prelines = pre.splitlines()
743 postlines = post.splitlines(True)
791 postlines = post.splitlines(True)
744
792
745 start = m.start()
793 start = m.start()
746 while n < len(postlines):
794 while n < len(postlines):
747 step = len(postlines[n])
795 step = len(postlines[n])
748 if pos + step > start:
796 if pos + step > start:
749 break
797 break
750 pos += step
798 pos += step
751 n += 1
799 n += 1
752 l = prelines[n]
800 l = prelines[n]
753
801
754 if ignore and re.search(ignore, l, re.MULTILINE):
802 if ignore and re.search(ignore, l, re.MULTILINE):
755 if debug:
803 if debug:
756 print("Skipping %s for %s:%s (ignore pattern)" % (
804 print("Skipping %s for %s:%s (ignore pattern)" % (
757 name, f, (n + lineoffset)))
805 name, f, (n + lineoffset)))
758 continue
806 continue
759 bd = ""
807 bd = ""
760 if blame:
808 if blame:
761 bd = 'working directory'
809 bd = 'working directory'
762 if blamecache is None:
810 if blamecache is None:
763 blamecache = getblame(f)
811 blamecache = getblame(f)
764 context['blamecache'] = blamecache
812 context['blamecache'] = blamecache
765 if (n + lineoffset) < len(blamecache):
813 if (n + lineoffset) < len(blamecache):
766 bl, bu, br = blamecache[(n + lineoffset)]
814 bl, bu, br = blamecache[(n + lineoffset)]
767 if offset is None and bl == l:
815 if offset is None and bl == l:
768 bd = '%s@%s' % (bu, br)
816 bd = '%s@%s' % (bu, br)
769 elif offset is not None and bl.endswith(l):
817 elif offset is not None and bl.endswith(l):
770 # "offset is not None" means "checking
818 # "offset is not None" means "checking
771 # embedded code fragment". In this case,
819 # embedded code fragment". In this case,
772 # "l" does not have information about the
820 # "l" does not have information about the
773 # beginning of an *original* line in the
821 # beginning of an *original* line in the
774 # file (e.g. ' > ').
822 # file (e.g. ' > ').
775 # Therefore, use "str.endswith()", and
823 # Therefore, use "str.endswith()", and
776 # show "maybe" for a little loose
824 # show "maybe" for a little loose
777 # examination.
825 # examination.
778 bd = '%s@%s, maybe' % (bu, br)
826 bd = '%s@%s, maybe' % (bu, br)
779
827
780 errors.append((f, lineno and (n + lineoffset + 1), l, msg, bd))
828 errors.append((f, lineno and (n + lineoffset + 1), l, msg, bd))
781
829
782 errors.sort()
830 errors.sort()
783 for e in errors:
831 for e in errors:
784 logfunc(*e)
832 logfunc(*e)
785 fc += 1
833 fc += 1
786 if maxerr and fc >= maxerr:
834 if maxerr and fc >= maxerr:
787 print(" (too many errors, giving up)")
835 print(" (too many errors, giving up)")
788 break
836 break
789
837
790 return fc
838 return fc
791
839
792 def main():
840 def main():
793 parser = optparse.OptionParser("%prog [options] [files | -]")
841 parser = optparse.OptionParser("%prog [options] [files | -]")
794 parser.add_option("-w", "--warnings", action="store_true",
842 parser.add_option("-w", "--warnings", action="store_true",
795 help="include warning-level checks")
843 help="include warning-level checks")
796 parser.add_option("-p", "--per-file", type="int",
844 parser.add_option("-p", "--per-file", type="int",
797 help="max warnings per file")
845 help="max warnings per file")
798 parser.add_option("-b", "--blame", action="store_true",
846 parser.add_option("-b", "--blame", action="store_true",
799 help="use annotate to generate blame info")
847 help="use annotate to generate blame info")
800 parser.add_option("", "--debug", action="store_true",
848 parser.add_option("", "--debug", action="store_true",
801 help="show debug information")
849 help="show debug information")
802 parser.add_option("", "--nolineno", action="store_false",
850 parser.add_option("", "--nolineno", action="store_false",
803 dest='lineno', help="don't show line numbers")
851 dest='lineno', help="don't show line numbers")
804
852
805 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
853 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
806 lineno=True)
854 lineno=True)
807 (options, args) = parser.parse_args()
855 (options, args) = parser.parse_args()
808
856
809 if len(args) == 0:
857 if len(args) == 0:
810 check = glob.glob("*")
858 check = glob.glob("*")
811 elif args == ['-']:
859 elif args == ['-']:
812 # read file list from stdin
860 # read file list from stdin
813 check = sys.stdin.read().splitlines()
861 check = sys.stdin.read().splitlines()
814 else:
862 else:
815 check = args
863 check = args
816
864
817 _preparepats()
865 _preparepats()
818
866
819 ret = 0
867 ret = 0
820 for f in check:
868 for f in check:
821 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
869 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
822 blame=options.blame, debug=options.debug,
870 blame=options.blame, debug=options.debug,
823 lineno=options.lineno):
871 lineno=options.lineno):
824 ret = 1
872 ret = 1
825 return ret
873 return ret
826
874
827 if __name__ == "__main__":
875 if __name__ == "__main__":
828 sys.exit(main())
876 sys.exit(main())
@@ -1,381 +1,429 b''
1 $ cat > correct.py <<NO_CHECK_EOF
1 $ cat > correct.py <<NO_CHECK_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 > NO_CHECK_EOF
5 > NO_CHECK_EOF
6 $ cat > wrong.py <<NO_CHECK_EOF
6 $ cat > wrong.py <<NO_CHECK_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 > def badwrap():
10 > def badwrap():
11 > return 1 + \\
11 > return 1 + \\
12 > 2
12 > 2
13 > NO_CHECK_EOF
13 > NO_CHECK_EOF
14 $ cat > quote.py <<NO_CHECK_EOF
14 $ cat > quote.py <<NO_CHECK_EOF
15 > # let's use quote in comments
15 > # let's use quote in comments
16 > (''' ( 4x5 )
16 > (''' ( 4x5 )
17 > but """\\''' and finally''',
17 > but """\\''' and finally''',
18 > """let's fool checkpatch""", '1+2',
18 > """let's fool checkpatch""", '1+2',
19 > '"""', 42+1, """and
19 > '"""', 42+1, """and
20 > ( 4-1 ) """, "( 1+1 )\" and ")
20 > ( 4-1 ) """, "( 1+1 )\" and ")
21 > a, '\\\\\\\\', "\\\\\\" x-2", "c-1"
21 > a, '\\\\\\\\', "\\\\\\" x-2", "c-1"
22 > NO_CHECK_EOF
22 > NO_CHECK_EOF
23 $ cat > classstyle.py <<NO_CHECK_EOF
23 $ cat > classstyle.py <<NO_CHECK_EOF
24 > class newstyle_class(object):
24 > class newstyle_class(object):
25 > pass
25 > pass
26 >
26 >
27 > class oldstyle_class:
27 > class oldstyle_class:
28 > pass
28 > pass
29 >
29 >
30 > class empty():
30 > class empty():
31 > pass
31 > pass
32 >
32 >
33 > no_class = 1:
33 > no_class = 1:
34 > pass
34 > pass
35 > NO_CHECK_EOF
35 > NO_CHECK_EOF
36 $ check_code="$TESTDIR"/../contrib/check-code.py
36 $ check_code="$TESTDIR"/../contrib/check-code.py
37 $ "$check_code" ./wrong.py ./correct.py ./quote.py ./classstyle.py
37 $ "$check_code" ./wrong.py ./correct.py ./quote.py ./classstyle.py
38 ./wrong.py:1:
38 ./wrong.py:1:
39 > def toto( arg1, arg2):
39 > def toto( arg1, arg2):
40 gratuitous whitespace in () or []
40 gratuitous whitespace in () or []
41 ./wrong.py:2:
41 ./wrong.py:2:
42 > del(arg2)
42 > del(arg2)
43 Python keyword is not a function
43 Python keyword is not a function
44 ./wrong.py:3:
44 ./wrong.py:3:
45 > return ( 5+6, 9)
45 > return ( 5+6, 9)
46 gratuitous whitespace in () or []
46 gratuitous whitespace in () or []
47 missing whitespace in expression
47 missing whitespace in expression
48 ./wrong.py:5:
48 ./wrong.py:5:
49 > return 1 + \
49 > return 1 + \
50 Use () to wrap long lines in Python, not \
50 Use () to wrap long lines in Python, not \
51 ./quote.py:5:
51 ./quote.py:5:
52 > '"""', 42+1, """and
52 > '"""', 42+1, """and
53 missing whitespace in expression
53 missing whitespace in expression
54 ./classstyle.py:4:
54 ./classstyle.py:4:
55 > class oldstyle_class:
55 > class oldstyle_class:
56 old-style class, use class foo(object)
56 old-style class, use class foo(object)
57 ./classstyle.py:7:
57 ./classstyle.py:7:
58 > class empty():
58 > class empty():
59 class foo() creates old style object, use class foo(object)
59 class foo() creates old style object, use class foo(object)
60 [1]
60 [1]
61 $ cat > python3-compat.py << NO_CHECK_EOF
61 $ cat > python3-compat.py << NO_CHECK_EOF
62 > foo <> bar
62 > foo <> bar
63 > reduce(lambda a, b: a + b, [1, 2, 3, 4])
63 > reduce(lambda a, b: a + b, [1, 2, 3, 4])
64 > dict(key=value)
64 > dict(key=value)
65 > NO_CHECK_EOF
65 > NO_CHECK_EOF
66 $ "$check_code" python3-compat.py
66 $ "$check_code" python3-compat.py
67 python3-compat.py:1:
67 python3-compat.py:1:
68 > foo <> bar
68 > foo <> bar
69 <> operator is not available in Python 3+, use !=
69 <> operator is not available in Python 3+, use !=
70 python3-compat.py:2:
70 python3-compat.py:2:
71 > reduce(lambda a, b: a + b, [1, 2, 3, 4])
71 > reduce(lambda a, b: a + b, [1, 2, 3, 4])
72 reduce is not available in Python 3+
72 reduce is not available in Python 3+
73 python3-compat.py:3:
73 python3-compat.py:3:
74 > dict(key=value)
74 > dict(key=value)
75 dict() is different in Py2 and 3 and is slower than {}
75 dict() is different in Py2 and 3 and is slower than {}
76 [1]
76 [1]
77
77
78 $ cat > foo.c <<NO_CHECK_EOF
78 $ cat > foo.c <<NO_CHECK_EOF
79 > void narf() {
79 > void narf() {
80 > strcpy(foo, bar);
80 > strcpy(foo, bar);
81 > // strcpy_s is okay, but this comment is not
81 > // strcpy_s is okay, but this comment is not
82 > strcpy_s(foo, bar);
82 > strcpy_s(foo, bar);
83 > }
83 > }
84 > NO_CHECK_EOF
84 > NO_CHECK_EOF
85 $ "$check_code" ./foo.c
85 $ "$check_code" ./foo.c
86 ./foo.c:2:
86 ./foo.c:2:
87 > strcpy(foo, bar);
87 > strcpy(foo, bar);
88 don't use strcpy, use strlcpy or memcpy
88 don't use strcpy, use strlcpy or memcpy
89 ./foo.c:3:
89 ./foo.c:3:
90 > // strcpy_s is okay, but this comment is not
90 > // strcpy_s is okay, but this comment is not
91 don't use //-style comments
91 don't use //-style comments
92 [1]
92 [1]
93
93
94 $ cat > is-op.py <<NO_CHECK_EOF
94 $ cat > is-op.py <<NO_CHECK_EOF
95 > # is-operator comparing number or string literal
95 > # is-operator comparing number or string literal
96 > x = None
96 > x = None
97 > y = x is 'foo'
97 > y = x is 'foo'
98 > y = x is "foo"
98 > y = x is "foo"
99 > y = x is 5346
99 > y = x is 5346
100 > y = x is -6
100 > y = x is -6
101 > y = x is not 'foo'
101 > y = x is not 'foo'
102 > y = x is not "foo"
102 > y = x is not "foo"
103 > y = x is not 5346
103 > y = x is not 5346
104 > y = x is not -6
104 > y = x is not -6
105 > NO_CHECK_EOF
105 > NO_CHECK_EOF
106
106
107 $ "$check_code" ./is-op.py
107 $ "$check_code" ./is-op.py
108 ./is-op.py:3:
108 ./is-op.py:3:
109 > y = x is 'foo'
109 > y = x is 'foo'
110 object comparison with literal
110 object comparison with literal
111 ./is-op.py:4:
111 ./is-op.py:4:
112 > y = x is "foo"
112 > y = x is "foo"
113 object comparison with literal
113 object comparison with literal
114 ./is-op.py:5:
114 ./is-op.py:5:
115 > y = x is 5346
115 > y = x is 5346
116 object comparison with literal
116 object comparison with literal
117 ./is-op.py:6:
117 ./is-op.py:6:
118 > y = x is -6
118 > y = x is -6
119 object comparison with literal
119 object comparison with literal
120 ./is-op.py:7:
120 ./is-op.py:7:
121 > y = x is not 'foo'
121 > y = x is not 'foo'
122 object comparison with literal
122 object comparison with literal
123 ./is-op.py:8:
123 ./is-op.py:8:
124 > y = x is not "foo"
124 > y = x is not "foo"
125 object comparison with literal
125 object comparison with literal
126 ./is-op.py:9:
126 ./is-op.py:9:
127 > y = x is not 5346
127 > y = x is not 5346
128 object comparison with literal
128 object comparison with literal
129 ./is-op.py:10:
129 ./is-op.py:10:
130 > y = x is not -6
130 > y = x is not -6
131 object comparison with literal
131 object comparison with literal
132 [1]
132 [1]
133
133
134 $ cat > for-nolineno.py <<NO_CHECK_EOF
134 $ cat > for-nolineno.py <<NO_CHECK_EOF
135 > except:
135 > except:
136 > NO_CHECK_EOF
136 > NO_CHECK_EOF
137 $ "$check_code" for-nolineno.py --nolineno
137 $ "$check_code" for-nolineno.py --nolineno
138 for-nolineno.py:0:
138 for-nolineno.py:0:
139 > except:
139 > except:
140 naked except clause
140 naked except clause
141 [1]
141 [1]
142
142
143 $ cat > warning.t <<NO_CHECK_EOF
143 $ cat > warning.t <<NO_CHECK_EOF
144 > $ function warnonly {
144 > $ function warnonly {
145 > > }
145 > > }
146 > $ diff -N aaa
146 > $ diff -N aaa
147 > $ function onwarn {}
147 > $ function onwarn {}
148 > NO_CHECK_EOF
148 > NO_CHECK_EOF
149 $ "$check_code" warning.t
149 $ "$check_code" warning.t
150 $ "$check_code" --warn warning.t
150 $ "$check_code" --warn warning.t
151 warning.t:1:
151 warning.t:1:
152 > $ function warnonly {
152 > $ function warnonly {
153 warning: don't use 'function', use old style
153 warning: don't use 'function', use old style
154 warning.t:3:
154 warning.t:3:
155 > $ diff -N aaa
155 > $ diff -N aaa
156 warning: don't use 'diff -N'
156 warning: don't use 'diff -N'
157 warning.t:4:
157 warning.t:4:
158 > $ function onwarn {}
158 > $ function onwarn {}
159 warning: don't use 'function', use old style
159 warning: don't use 'function', use old style
160 [1]
160 [1]
161 $ cat > error.t <<NO_CHECK_EOF
161 $ cat > error.t <<NO_CHECK_EOF
162 > $ [ foo == bar ]
162 > $ [ foo == bar ]
163 > NO_CHECK_EOF
163 > NO_CHECK_EOF
164 $ "$check_code" error.t
164 $ "$check_code" error.t
165 error.t:1:
165 error.t:1:
166 > $ [ foo == bar ]
166 > $ [ foo == bar ]
167 [ foo == bar ] is a bashism, use [ foo = bar ] instead
167 [ foo == bar ] is a bashism, use [ foo = bar ] instead
168 [1]
168 [1]
169 $ rm error.t
169 $ rm error.t
170 $ cat > raise-format.py <<NO_CHECK_EOF
170 $ cat > raise-format.py <<NO_CHECK_EOF
171 > raise SomeException, message
171 > raise SomeException, message
172 > # this next line is okay
172 > # this next line is okay
173 > raise SomeException(arg1, arg2)
173 > raise SomeException(arg1, arg2)
174 > NO_CHECK_EOF
174 > NO_CHECK_EOF
175 $ "$check_code" not-existing.py raise-format.py
175 $ "$check_code" not-existing.py raise-format.py
176 Skipping*not-existing.py* (glob)
176 Skipping*not-existing.py* (glob)
177 raise-format.py:1:
177 raise-format.py:1:
178 > raise SomeException, message
178 > raise SomeException, message
179 don't use old-style two-argument raise, use Exception(message)
179 don't use old-style two-argument raise, use Exception(message)
180 [1]
180 [1]
181
181
182 $ cat <<NO_CHECK_EOF > tab.t
182 $ cat <<NO_CHECK_EOF > tab.t
183 > indent
183 > indent
184 > > heredoc
184 > > heredoc
185 > NO_CHECK_EOF
185 > NO_CHECK_EOF
186 $ "$check_code" tab.t
186 $ "$check_code" tab.t
187 tab.t:1:
187 tab.t:1:
188 > indent
188 > indent
189 don't use tabs to indent
189 don't use tabs to indent
190 [1]
190 [1]
191 $ rm tab.t
191 $ rm tab.t
192
192
193 $ cat > rst.py <<NO_CHECK_EOF
193 $ cat > rst.py <<NO_CHECK_EOF
194 > """problematic rst text
194 > """problematic rst text
195 >
195 >
196 > .. note::
196 > .. note::
197 > wrong
197 > wrong
198 > """
198 > """
199 >
199 >
200 > '''
200 > '''
201 >
201 >
202 > .. note::
202 > .. note::
203 >
203 >
204 > valid
204 > valid
205 >
205 >
206 > new text
206 > new text
207 >
207 >
208 > .. note::
208 > .. note::
209 >
209 >
210 > also valid
210 > also valid
211 > '''
211 > '''
212 >
212 >
213 > """mixed
213 > """mixed
214 >
214 >
215 > .. note::
215 > .. note::
216 >
216 >
217 > good
217 > good
218 >
218 >
219 > .. note::
219 > .. note::
220 > plus bad
220 > plus bad
221 > """
221 > """
222 > NO_CHECK_EOF
222 > NO_CHECK_EOF
223 $ $check_code -w rst.py
223 $ $check_code -w rst.py
224 rst.py:3:
224 rst.py:3:
225 > .. note::
225 > .. note::
226 warning: add two newlines after '.. note::'
226 warning: add two newlines after '.. note::'
227 rst.py:26:
227 rst.py:26:
228 > .. note::
228 > .. note::
229 warning: add two newlines after '.. note::'
229 warning: add two newlines after '.. note::'
230 [1]
230 [1]
231
231
232 $ cat > ./map-inside-gettext.py <<NO_CHECK_EOF
232 $ cat > ./map-inside-gettext.py <<NO_CHECK_EOF
233 > print(_("map inside gettext %s" % v))
233 > print(_("map inside gettext %s" % v))
234 >
234 >
235 > print(_("concatenating " " by " " space %s" % v))
235 > print(_("concatenating " " by " " space %s" % v))
236 > print(_("concatenating " + " by " + " '+' %s" % v))
236 > print(_("concatenating " + " by " + " '+' %s" % v))
237 >
237 >
238 > print(_("mapping operation in different line %s"
238 > print(_("mapping operation in different line %s"
239 > % v))
239 > % v))
240 >
240 >
241 > print(_(
241 > print(_(
242 > "leading spaces inside of '(' %s" % v))
242 > "leading spaces inside of '(' %s" % v))
243 > NO_CHECK_EOF
243 > NO_CHECK_EOF
244 $ "$check_code" ./map-inside-gettext.py
244 $ "$check_code" ./map-inside-gettext.py
245 ./map-inside-gettext.py:1:
245 ./map-inside-gettext.py:1:
246 > print(_("map inside gettext %s" % v))
246 > print(_("map inside gettext %s" % v))
247 don't use % inside _()
247 don't use % inside _()
248 ./map-inside-gettext.py:3:
248 ./map-inside-gettext.py:3:
249 > print(_("concatenating " " by " " space %s" % v))
249 > print(_("concatenating " " by " " space %s" % v))
250 don't use % inside _()
250 don't use % inside _()
251 ./map-inside-gettext.py:4:
251 ./map-inside-gettext.py:4:
252 > print(_("concatenating " + " by " + " '+' %s" % v))
252 > print(_("concatenating " + " by " + " '+' %s" % v))
253 don't use % inside _()
253 don't use % inside _()
254 ./map-inside-gettext.py:6:
254 ./map-inside-gettext.py:6:
255 > print(_("mapping operation in different line %s"
255 > print(_("mapping operation in different line %s"
256 don't use % inside _()
256 don't use % inside _()
257 ./map-inside-gettext.py:9:
257 ./map-inside-gettext.py:9:
258 > print(_(
258 > print(_(
259 don't use % inside _()
259 don't use % inside _()
260 [1]
260 [1]
261
261
262 web templates
262 web templates
263
263
264 $ mkdir -p mercurial/templates
264 $ mkdir -p mercurial/templates
265 $ cat > mercurial/templates/example.tmpl <<NO_CHECK_EOF
265 $ cat > mercurial/templates/example.tmpl <<NO_CHECK_EOF
266 > {desc}
266 > {desc}
267 > {desc|escape}
267 > {desc|escape}
268 > {desc|firstline}
268 > {desc|firstline}
269 > {desc|websub}
269 > {desc|websub}
270 > NO_CHECK_EOF
270 > NO_CHECK_EOF
271
271
272 $ "$check_code" --warnings mercurial/templates/example.tmpl
272 $ "$check_code" --warnings mercurial/templates/example.tmpl
273 mercurial/templates/example.tmpl:2:
273 mercurial/templates/example.tmpl:2:
274 > {desc|escape}
274 > {desc|escape}
275 warning: follow desc keyword with either firstline or websub
275 warning: follow desc keyword with either firstline or websub
276 [1]
276 [1]
277
277
278 'string join across lines with no space' detection
278 'string join across lines with no space' detection
279
279
280 $ cat > stringjoin.py <<NO_CHECK_EOF
280 $ cat > stringjoin.py <<NO_CHECK_EOF
281 > foo = (' foo'
281 > foo = (' foo'
282 > 'bar foo.'
282 > 'bar foo.'
283 > 'bar foo:'
283 > 'bar foo:'
284 > 'bar foo@'
284 > 'bar foo@'
285 > 'bar foo%'
285 > 'bar foo%'
286 > 'bar foo*'
286 > 'bar foo*'
287 > 'bar foo+'
287 > 'bar foo+'
288 > 'bar foo-'
288 > 'bar foo-'
289 > 'bar')
289 > 'bar')
290 > NO_CHECK_EOF
290 > NO_CHECK_EOF
291
291
292 'missing _() in ui message' detection
292 'missing _() in ui message' detection
293
293
294 $ cat > uigettext.py <<NO_CHECK_EOF
294 $ cat > uigettext.py <<NO_CHECK_EOF
295 > ui.status("% 10s %05d % -3.2f %*s %%"
295 > ui.status("% 10s %05d % -3.2f %*s %%"
296 > # this use '\\\\' instead of '\\', because the latter in
296 > # this use '\\\\' instead of '\\', because the latter in
297 > # heredoc on shell becomes just '\'
297 > # heredoc on shell becomes just '\'
298 > '\\\\ \n \t \0'
298 > '\\\\ \n \t \0'
299 > """12345
299 > """12345
300 > """
300 > """
301 > '''.:*+-=
301 > '''.:*+-=
302 > ''' "%-6d \n 123456 .:*+-= foobar")
302 > ''' "%-6d \n 123456 .:*+-= foobar")
303 > NO_CHECK_EOF
303 > NO_CHECK_EOF
304
304
305 superfluous pass
305 superfluous pass
306
306
307 $ cat > superfluous_pass.py <<NO_CHECK_EOF
307 $ cat > superfluous_pass.py <<NO_CHECK_EOF
308 > # correct examples
308 > # correct examples
309 > if foo:
309 > if foo:
310 > pass
310 > pass
311 > else:
311 > else:
312 > # comment-only line means still need pass
312 > # comment-only line means still need pass
313 > pass
313 > pass
314 > def nothing():
314 > def nothing():
315 > pass
315 > pass
316 > class empty(object):
316 > class empty(object):
317 > pass
317 > pass
318 > if whatever:
318 > if whatever:
319 > passvalue(value)
319 > passvalue(value)
320 > # bad examples
320 > # bad examples
321 > if foo:
321 > if foo:
322 > "foo"
322 > "foo"
323 > pass
323 > pass
324 > else: # trailing comment doesn't fool checker
324 > else: # trailing comment doesn't fool checker
325 > wat()
325 > wat()
326 > pass
326 > pass
327 > def nothing():
327 > def nothing():
328 > "docstring means no pass"
328 > "docstring means no pass"
329 > pass
329 > pass
330 > class empty(object):
330 > class empty(object):
331 > """multiline
331 > """multiline
332 > docstring also
332 > docstring also
333 > means no pass"""
333 > means no pass"""
334 > pass
334 > pass
335 > NO_CHECK_EOF
335 > NO_CHECK_EOF
336
336
337 (Checking multiple invalid files at once examines whether caching
337 (Checking multiple invalid files at once examines whether caching
338 translation table for repquote() works as expected or not. All files
338 translation table for repquote() works as expected or not. All files
339 should break rules depending on result of repquote(), in this case)
339 should break rules depending on result of repquote(), in this case)
340
340
341 $ "$check_code" stringjoin.py uigettext.py superfluous_pass.py
341 $ "$check_code" stringjoin.py uigettext.py superfluous_pass.py
342 stringjoin.py:1:
342 stringjoin.py:1:
343 > foo = (' foo'
343 > foo = (' foo'
344 string join across lines with no space
344 string join across lines with no space
345 stringjoin.py:2:
345 stringjoin.py:2:
346 > 'bar foo.'
346 > 'bar foo.'
347 string join across lines with no space
347 string join across lines with no space
348 stringjoin.py:3:
348 stringjoin.py:3:
349 > 'bar foo:'
349 > 'bar foo:'
350 string join across lines with no space
350 string join across lines with no space
351 stringjoin.py:4:
351 stringjoin.py:4:
352 > 'bar foo@'
352 > 'bar foo@'
353 string join across lines with no space
353 string join across lines with no space
354 stringjoin.py:5:
354 stringjoin.py:5:
355 > 'bar foo%'
355 > 'bar foo%'
356 string join across lines with no space
356 string join across lines with no space
357 stringjoin.py:6:
357 stringjoin.py:6:
358 > 'bar foo*'
358 > 'bar foo*'
359 string join across lines with no space
359 string join across lines with no space
360 stringjoin.py:7:
360 stringjoin.py:7:
361 > 'bar foo+'
361 > 'bar foo+'
362 string join across lines with no space
362 string join across lines with no space
363 stringjoin.py:8:
363 stringjoin.py:8:
364 > 'bar foo-'
364 > 'bar foo-'
365 string join across lines with no space
365 string join across lines with no space
366 uigettext.py:1:
366 uigettext.py:1:
367 > ui.status("% 10s %05d % -3.2f %*s %%"
367 > ui.status("% 10s %05d % -3.2f %*s %%"
368 missing _() in ui message (use () to hide false-positives)
368 missing _() in ui message (use () to hide false-positives)
369 superfluous_pass.py:14:
369 superfluous_pass.py:14:
370 > if foo:
370 > if foo:
371 omit superfluous pass
371 omit superfluous pass
372 superfluous_pass.py:17:
372 superfluous_pass.py:17:
373 > else: # trailing comment doesn't fool checker
373 > else: # trailing comment doesn't fool checker
374 omit superfluous pass
374 omit superfluous pass
375 superfluous_pass.py:20:
375 superfluous_pass.py:20:
376 > def nothing():
376 > def nothing():
377 omit superfluous pass
377 omit superfluous pass
378 superfluous_pass.py:23:
378 superfluous_pass.py:23:
379 > class empty(object):
379 > class empty(object):
380 omit superfluous pass
380 omit superfluous pass
381 [1]
381 [1]
382
383 Check code fragments embedded in test script
384
385 $ cat > embedded-code.t <<NO_CHECK_EOF
386 > code fragment in doctest style
387 > >>> x = (1,2)
388 > ...
389 > ... x = (1,2)
390 >
391 > code fragment in heredoc style
392 > $ python <<EOF
393 > > x = (1,2)
394 > > EOF
395 >
396 > code fragment in file heredoc style
397 > $ python > file.py <<EOF
398 > > x = (1,2)
399 > > EOF
400 > NO_CHECK_EOF
401 $ "$check_code" embedded-code.t
402 embedded-code.t:2:
403 > x = (1,2)
404 missing whitespace after ,
405 embedded-code.t:4:
406 > x = (1,2)
407 missing whitespace after ,
408 embedded-code.t:8:
409 > x = (1,2)
410 missing whitespace after ,
411 embedded-code.t:13:
412 > x = (1,2)
413 missing whitespace after ,
414 [1]
415
416 "max warnings per file" is shared by all embedded code fragments
417
418 $ "$check_code" --per-file=3 embedded-code.t
419 embedded-code.t:2:
420 > x = (1,2)
421 missing whitespace after ,
422 embedded-code.t:4:
423 > x = (1,2)
424 missing whitespace after ,
425 embedded-code.t:8:
426 > x = (1,2)
427 missing whitespace after ,
428 (too many errors, giving up)
429 [1]
General Comments 0
You need to be logged in to leave comments. Login now