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