##// END OF EJS Templates
check-code: reject import urllib
timeless -
r28884:75309bad default
parent child Browse files
Show More
@@ -1,607 +1,609 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 (r'^import Queue', "don't use Queue, use util.queue + util.empty"),
319 (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"),
320 (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
319 ],
321 ],
320 # warnings
322 # warnings
321 [
323 [
322 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
324 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
323 ]
325 ]
324 ]
326 ]
325
327
326 pyfilters = [
328 pyfilters = [
327 (r"""(?msx)(?P<comment>\#.*?$)|
329 (r"""(?msx)(?P<comment>\#.*?$)|
328 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
330 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
329 (?P<text>(([^\\]|\\.)*?))
331 (?P<text>(([^\\]|\\.)*?))
330 (?P=quote))""", reppython),
332 (?P=quote))""", reppython),
331 ]
333 ]
332
334
333 txtfilters = []
335 txtfilters = []
334
336
335 txtpats = [
337 txtpats = [
336 [
338 [
337 ('\s$', 'trailing whitespace'),
339 ('\s$', 'trailing whitespace'),
338 ('.. note::[ \n][^\n]', 'add two newlines after note::')
340 ('.. note::[ \n][^\n]', 'add two newlines after note::')
339 ],
341 ],
340 []
342 []
341 ]
343 ]
342
344
343 cpats = [
345 cpats = [
344 [
346 [
345 (r'//', "don't use //-style comments"),
347 (r'//', "don't use //-style comments"),
346 (r'^ ', "don't use spaces to indent"),
348 (r'^ ', "don't use spaces to indent"),
347 (r'\S\t', "don't use tabs except for indent"),
349 (r'\S\t', "don't use tabs except for indent"),
348 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
350 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
349 (r'.{81}', "line too long"),
351 (r'.{81}', "line too long"),
350 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
352 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
351 (r'return\(', "return is not a function"),
353 (r'return\(', "return is not a function"),
352 (r' ;', "no space before ;"),
354 (r' ;', "no space before ;"),
353 (r'[^;] \)', "no space before )"),
355 (r'[^;] \)', "no space before )"),
354 (r'[)][{]', "space between ) and {"),
356 (r'[)][{]', "space between ) and {"),
355 (r'\w+\* \w+', "use int *foo, not int* foo"),
357 (r'\w+\* \w+', "use int *foo, not int* foo"),
356 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
358 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
357 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
359 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
358 (r'\w,\w', "missing whitespace after ,"),
360 (r'\w,\w', "missing whitespace after ,"),
359 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
361 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
360 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
362 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
361 (r'^#\s+\w', "use #foo, not # foo"),
363 (r'^#\s+\w', "use #foo, not # foo"),
362 (r'[^\n]\Z', "no trailing newline"),
364 (r'[^\n]\Z', "no trailing newline"),
363 (r'^\s*#import\b', "use only #include in standard C code"),
365 (r'^\s*#import\b', "use only #include in standard C code"),
364 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
366 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
365 (r'strcat\(', "don't use strcat"),
367 (r'strcat\(', "don't use strcat"),
366 ],
368 ],
367 # warnings
369 # warnings
368 []
370 []
369 ]
371 ]
370
372
371 cfilters = [
373 cfilters = [
372 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
374 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
373 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
375 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
374 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
376 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
375 (r'(\()([^)]+\))', repcallspaces),
377 (r'(\()([^)]+\))', repcallspaces),
376 ]
378 ]
377
379
378 inutilpats = [
380 inutilpats = [
379 [
381 [
380 (r'\bui\.', "don't use ui in util"),
382 (r'\bui\.', "don't use ui in util"),
381 ],
383 ],
382 # warnings
384 # warnings
383 []
385 []
384 ]
386 ]
385
387
386 inrevlogpats = [
388 inrevlogpats = [
387 [
389 [
388 (r'\brepo\.', "don't use repo in revlog"),
390 (r'\brepo\.', "don't use repo in revlog"),
389 ],
391 ],
390 # warnings
392 # warnings
391 []
393 []
392 ]
394 ]
393
395
394 webtemplatefilters = []
396 webtemplatefilters = []
395
397
396 webtemplatepats = [
398 webtemplatepats = [
397 [],
399 [],
398 [
400 [
399 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
401 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
400 'follow desc keyword with either firstline or websub'),
402 'follow desc keyword with either firstline or websub'),
401 ]
403 ]
402 ]
404 ]
403
405
404 checks = [
406 checks = [
405 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
407 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
406 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
408 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
407 ('c', r'.*\.[ch]$', '', cfilters, cpats),
409 ('c', r'.*\.[ch]$', '', cfilters, cpats),
408 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
410 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
409 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
411 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
410 pyfilters, inrevlogpats),
412 pyfilters, inrevlogpats),
411 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
413 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
412 inutilpats),
414 inutilpats),
413 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
415 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
414 ('web template', r'mercurial/templates/.*\.tmpl', '',
416 ('web template', r'mercurial/templates/.*\.tmpl', '',
415 webtemplatefilters, webtemplatepats),
417 webtemplatefilters, webtemplatepats),
416 ]
418 ]
417
419
418 def _preparepats():
420 def _preparepats():
419 for c in checks:
421 for c in checks:
420 failandwarn = c[-1]
422 failandwarn = c[-1]
421 for pats in failandwarn:
423 for pats in failandwarn:
422 for i, pseq in enumerate(pats):
424 for i, pseq in enumerate(pats):
423 # fix-up regexes for multi-line searches
425 # fix-up regexes for multi-line searches
424 p = pseq[0]
426 p = pseq[0]
425 # \s doesn't match \n
427 # \s doesn't match \n
426 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
428 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
427 # [^...] doesn't match newline
429 # [^...] doesn't match newline
428 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
430 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
429
431
430 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
432 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
431 filters = c[3]
433 filters = c[3]
432 for i, flt in enumerate(filters):
434 for i, flt in enumerate(filters):
433 filters[i] = re.compile(flt[0]), flt[1]
435 filters[i] = re.compile(flt[0]), flt[1]
434 _preparepats()
436 _preparepats()
435
437
436 class norepeatlogger(object):
438 class norepeatlogger(object):
437 def __init__(self):
439 def __init__(self):
438 self._lastseen = None
440 self._lastseen = None
439
441
440 def log(self, fname, lineno, line, msg, blame):
442 def log(self, fname, lineno, line, msg, blame):
441 """print error related a to given line of a given file.
443 """print error related a to given line of a given file.
442
444
443 The faulty line will also be printed but only once in the case
445 The faulty line will also be printed but only once in the case
444 of multiple errors.
446 of multiple errors.
445
447
446 :fname: filename
448 :fname: filename
447 :lineno: line number
449 :lineno: line number
448 :line: actual content of the line
450 :line: actual content of the line
449 :msg: error message
451 :msg: error message
450 """
452 """
451 msgid = fname, lineno, line
453 msgid = fname, lineno, line
452 if msgid != self._lastseen:
454 if msgid != self._lastseen:
453 if blame:
455 if blame:
454 print("%s:%d (%s):" % (fname, lineno, blame))
456 print("%s:%d (%s):" % (fname, lineno, blame))
455 else:
457 else:
456 print("%s:%d:" % (fname, lineno))
458 print("%s:%d:" % (fname, lineno))
457 print(" > %s" % line)
459 print(" > %s" % line)
458 self._lastseen = msgid
460 self._lastseen = msgid
459 print(" " + msg)
461 print(" " + msg)
460
462
461 _defaultlogger = norepeatlogger()
463 _defaultlogger = norepeatlogger()
462
464
463 def getblame(f):
465 def getblame(f):
464 lines = []
466 lines = []
465 for l in os.popen('hg annotate -un %s' % f):
467 for l in os.popen('hg annotate -un %s' % f):
466 start, line = l.split(':', 1)
468 start, line = l.split(':', 1)
467 user, rev = start.split()
469 user, rev = start.split()
468 lines.append((line[1:-1], user, rev))
470 lines.append((line[1:-1], user, rev))
469 return lines
471 return lines
470
472
471 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
473 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
472 blame=False, debug=False, lineno=True):
474 blame=False, debug=False, lineno=True):
473 """checks style and portability of a given file
475 """checks style and portability of a given file
474
476
475 :f: filepath
477 :f: filepath
476 :logfunc: function used to report error
478 :logfunc: function used to report error
477 logfunc(filename, linenumber, linecontent, errormessage)
479 logfunc(filename, linenumber, linecontent, errormessage)
478 :maxerr: number of error to display before aborting.
480 :maxerr: number of error to display before aborting.
479 Set to false (default) to report all errors
481 Set to false (default) to report all errors
480
482
481 return True if no error is found, False otherwise.
483 return True if no error is found, False otherwise.
482 """
484 """
483 blamecache = None
485 blamecache = None
484 result = True
486 result = True
485
487
486 try:
488 try:
487 fp = open(f)
489 fp = open(f)
488 except IOError as e:
490 except IOError as e:
489 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
491 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
490 return result
492 return result
491 pre = post = fp.read()
493 pre = post = fp.read()
492 fp.close()
494 fp.close()
493
495
494 for name, match, magic, filters, pats in checks:
496 for name, match, magic, filters, pats in checks:
495 if debug:
497 if debug:
496 print(name, f)
498 print(name, f)
497 fc = 0
499 fc = 0
498 if not (re.match(match, f) or (magic and re.search(magic, pre))):
500 if not (re.match(match, f) or (magic and re.search(magic, pre))):
499 if debug:
501 if debug:
500 print("Skipping %s for %s it doesn't match %s" % (
502 print("Skipping %s for %s it doesn't match %s" % (
501 name, match, f))
503 name, match, f))
502 continue
504 continue
503 if "no-" "check-code" in pre:
505 if "no-" "check-code" in pre:
504 # If you're looking at this line, it's because a file has:
506 # If you're looking at this line, it's because a file has:
505 # no- check- code
507 # no- check- code
506 # but the reason to output skipping is to make life for
508 # but the reason to output skipping is to make life for
507 # tests easier. So, instead of writing it with a normal
509 # tests easier. So, instead of writing it with a normal
508 # spelling, we write it with the expected spelling from
510 # spelling, we write it with the expected spelling from
509 # tests/test-check-code.t
511 # tests/test-check-code.t
510 print("Skipping %s it has no-che?k-code (glob)" % f)
512 print("Skipping %s it has no-che?k-code (glob)" % f)
511 return "Skip" # skip checking this file
513 return "Skip" # skip checking this file
512 for p, r in filters:
514 for p, r in filters:
513 post = re.sub(p, r, post)
515 post = re.sub(p, r, post)
514 nerrs = len(pats[0]) # nerr elements are errors
516 nerrs = len(pats[0]) # nerr elements are errors
515 if warnings:
517 if warnings:
516 pats = pats[0] + pats[1]
518 pats = pats[0] + pats[1]
517 else:
519 else:
518 pats = pats[0]
520 pats = pats[0]
519 # print post # uncomment to show filtered version
521 # print post # uncomment to show filtered version
520
522
521 if debug:
523 if debug:
522 print("Checking %s for %s" % (name, f))
524 print("Checking %s for %s" % (name, f))
523
525
524 prelines = None
526 prelines = None
525 errors = []
527 errors = []
526 for i, pat in enumerate(pats):
528 for i, pat in enumerate(pats):
527 if len(pat) == 3:
529 if len(pat) == 3:
528 p, msg, ignore = pat
530 p, msg, ignore = pat
529 else:
531 else:
530 p, msg = pat
532 p, msg = pat
531 ignore = None
533 ignore = None
532 if i >= nerrs:
534 if i >= nerrs:
533 msg = "warning: " + msg
535 msg = "warning: " + msg
534
536
535 pos = 0
537 pos = 0
536 n = 0
538 n = 0
537 for m in p.finditer(post):
539 for m in p.finditer(post):
538 if prelines is None:
540 if prelines is None:
539 prelines = pre.splitlines()
541 prelines = pre.splitlines()
540 postlines = post.splitlines(True)
542 postlines = post.splitlines(True)
541
543
542 start = m.start()
544 start = m.start()
543 while n < len(postlines):
545 while n < len(postlines):
544 step = len(postlines[n])
546 step = len(postlines[n])
545 if pos + step > start:
547 if pos + step > start:
546 break
548 break
547 pos += step
549 pos += step
548 n += 1
550 n += 1
549 l = prelines[n]
551 l = prelines[n]
550
552
551 if ignore and re.search(ignore, l, re.MULTILINE):
553 if ignore and re.search(ignore, l, re.MULTILINE):
552 if debug:
554 if debug:
553 print("Skipping %s for %s:%s (ignore pattern)" % (
555 print("Skipping %s for %s:%s (ignore pattern)" % (
554 name, f, n))
556 name, f, n))
555 continue
557 continue
556 bd = ""
558 bd = ""
557 if blame:
559 if blame:
558 bd = 'working directory'
560 bd = 'working directory'
559 if not blamecache:
561 if not blamecache:
560 blamecache = getblame(f)
562 blamecache = getblame(f)
561 if n < len(blamecache):
563 if n < len(blamecache):
562 bl, bu, br = blamecache[n]
564 bl, bu, br = blamecache[n]
563 if bl == l:
565 if bl == l:
564 bd = '%s@%s' % (bu, br)
566 bd = '%s@%s' % (bu, br)
565
567
566 errors.append((f, lineno and n + 1, l, msg, bd))
568 errors.append((f, lineno and n + 1, l, msg, bd))
567 result = False
569 result = False
568
570
569 errors.sort()
571 errors.sort()
570 for e in errors:
572 for e in errors:
571 logfunc(*e)
573 logfunc(*e)
572 fc += 1
574 fc += 1
573 if maxerr and fc >= maxerr:
575 if maxerr and fc >= maxerr:
574 print(" (too many errors, giving up)")
576 print(" (too many errors, giving up)")
575 break
577 break
576
578
577 return result
579 return result
578
580
579 if __name__ == "__main__":
581 if __name__ == "__main__":
580 parser = optparse.OptionParser("%prog [options] [files]")
582 parser = optparse.OptionParser("%prog [options] [files]")
581 parser.add_option("-w", "--warnings", action="store_true",
583 parser.add_option("-w", "--warnings", action="store_true",
582 help="include warning-level checks")
584 help="include warning-level checks")
583 parser.add_option("-p", "--per-file", type="int",
585 parser.add_option("-p", "--per-file", type="int",
584 help="max warnings per file")
586 help="max warnings per file")
585 parser.add_option("-b", "--blame", action="store_true",
587 parser.add_option("-b", "--blame", action="store_true",
586 help="use annotate to generate blame info")
588 help="use annotate to generate blame info")
587 parser.add_option("", "--debug", action="store_true",
589 parser.add_option("", "--debug", action="store_true",
588 help="show debug information")
590 help="show debug information")
589 parser.add_option("", "--nolineno", action="store_false",
591 parser.add_option("", "--nolineno", action="store_false",
590 dest='lineno', help="don't show line numbers")
592 dest='lineno', help="don't show line numbers")
591
593
592 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
594 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
593 lineno=True)
595 lineno=True)
594 (options, args) = parser.parse_args()
596 (options, args) = parser.parse_args()
595
597
596 if len(args) == 0:
598 if len(args) == 0:
597 check = glob.glob("*")
599 check = glob.glob("*")
598 else:
600 else:
599 check = args
601 check = args
600
602
601 ret = 0
603 ret = 0
602 for f in check:
604 for f in check:
603 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
605 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
604 blame=options.blame, debug=options.debug,
606 blame=options.blame, debug=options.debug,
605 lineno=options.lineno):
607 lineno=options.lineno):
606 ret = 1
608 ret = 1
607 sys.exit(ret)
609 sys.exit(ret)
General Comments 0
You need to be logged in to leave comments. Login now