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