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