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