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