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