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