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