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