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