##// END OF EJS Templates
check-code: grep's context flags don't need an extra space before number...
av6 -
r35086:dd000a95 default
parent child Browse files
Show More
@@ -1,745 +1,745
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 # check "rules depending on implementation of repquote()" in each
53 # check "rules depending on implementation of repquote()" in each
54 # patterns (especially pypats), before changing around repquote()
54 # patterns (especially pypats), before changing around repquote()
55 _repquotefixedmap = {' ': ' ', '\n': '\n', '.': 'p', ':': 'q',
55 _repquotefixedmap = {' ': ' ', '\n': '\n', '.': 'p', ':': 'q',
56 '%': '%', '\\': 'b', '*': 'A', '+': 'P', '-': 'M'}
56 '%': '%', '\\': 'b', '*': 'A', '+': 'P', '-': 'M'}
57 def _repquoteencodechr(i):
57 def _repquoteencodechr(i):
58 if i > 255:
58 if i > 255:
59 return 'u'
59 return 'u'
60 c = chr(i)
60 c = chr(i)
61 if c in _repquotefixedmap:
61 if c in _repquotefixedmap:
62 return _repquotefixedmap[c]
62 return _repquotefixedmap[c]
63 if c.isalpha():
63 if c.isalpha():
64 return 'x'
64 return 'x'
65 if c.isdigit():
65 if c.isdigit():
66 return 'n'
66 return 'n'
67 return 'o'
67 return 'o'
68 _repquotett = ''.join(_repquoteencodechr(i) for i in xrange(256))
68 _repquotett = ''.join(_repquoteencodechr(i) for i in xrange(256))
69
69
70 def repquote(m):
70 def repquote(m):
71 t = m.group('text')
71 t = m.group('text')
72 t = t.translate(_repquotett)
72 t = t.translate(_repquotett)
73 return m.group('quote') + t + m.group('quote')
73 return m.group('quote') + t + m.group('quote')
74
74
75 def reppython(m):
75 def reppython(m):
76 comment = m.group('comment')
76 comment = m.group('comment')
77 if comment:
77 if comment:
78 l = len(comment.rstrip())
78 l = len(comment.rstrip())
79 return "#" * l + comment[l:]
79 return "#" * l + comment[l:]
80 return repquote(m)
80 return repquote(m)
81
81
82 def repcomment(m):
82 def repcomment(m):
83 return m.group(1) + "#" * len(m.group(2))
83 return m.group(1) + "#" * len(m.group(2))
84
84
85 def repccomment(m):
85 def repccomment(m):
86 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
86 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
87 return m.group(1) + t + "*/"
87 return m.group(1) + t + "*/"
88
88
89 def repcallspaces(m):
89 def repcallspaces(m):
90 t = re.sub(r"\n\s+", "\n", m.group(2))
90 t = re.sub(r"\n\s+", "\n", m.group(2))
91 return m.group(1) + t
91 return m.group(1) + t
92
92
93 def repinclude(m):
93 def repinclude(m):
94 return m.group(1) + "<foo>"
94 return m.group(1) + "<foo>"
95
95
96 def rephere(m):
96 def rephere(m):
97 t = re.sub(r"\S", "x", m.group(2))
97 t = re.sub(r"\S", "x", m.group(2))
98 return m.group(1) + t
98 return m.group(1) + t
99
99
100
100
101 testpats = [
101 testpats = [
102 [
102 [
103 (r'\b(push|pop)d\b', "don't use 'pushd' or 'popd', use 'cd'"),
103 (r'\b(push|pop)d\b', "don't use 'pushd' or 'popd', use 'cd'"),
104 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
104 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
105 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
105 (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"),
106 (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"),
107 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
108 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
108 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
109 (r'echo -n', "don't use 'echo -n', use printf"),
109 (r'echo -n', "don't use 'echo -n', use printf"),
110 (r'(^|\|\s*)\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"),
110 (r'(^|\|\s*)\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"),
111 (r'head -c', "don't use 'head -c', use 'dd'"),
111 (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>'"),
112 (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"),
113 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
114 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
114 (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"),
115 (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"),
116 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
117 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
117 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
118 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
118 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
119 (r'\[[^\]]+==', '[ foo == bar ] is a bashism, use [ foo = bar ] instead'),
119 (r'\[[^\]]+==', '[ foo == bar ] is a bashism, use [ foo = bar ] instead'),
120 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
120 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
121 "use egrep for extended grep syntax"),
121 "use egrep for extended grep syntax"),
122 (r'(^|\|\s*)e?grep .*\\S', "don't use \\S in regular expression"),
122 (r'(^|\|\s*)e?grep .*\\S', "don't use \\S in regular expression"),
123 (r'(?<!!)/bin/', "don't use explicit paths for tools"),
123 (r'(?<!!)/bin/', "don't use explicit paths for tools"),
124 (r'#!.*/bash', "don't use bash in shebang, use sh"),
124 (r'#!.*/bash', "don't use bash in shebang, use sh"),
125 (r'[^\n]\Z', "no trailing newline"),
125 (r'[^\n]\Z', "no trailing newline"),
126 (r'export .*=', "don't export and assign at once"),
126 (r'export .*=', "don't export and assign at once"),
127 (r'^source\b', "don't use 'source', use '.'"),
127 (r'^source\b', "don't use 'source', use '.'"),
128 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
128 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
129 (r'\bls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
129 (r'\bls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
130 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
130 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
131 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
131 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
132 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
132 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
133 (r'\[\[\s+[^\]]*\]\]', "don't use '[[ ]]', use '[ ]'"),
133 (r'\[\[\s+[^\]]*\]\]', "don't use '[[ ]]', use '[ ]'"),
134 (r'^alias\b.*=', "don't use alias, use a function"),
134 (r'^alias\b.*=', "don't use alias, use a function"),
135 (r'if\s*!', "don't use '!' to negate exit status"),
135 (r'if\s*!', "don't use '!' to negate exit status"),
136 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
136 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
137 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
137 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
138 (r'^( *)\t', "don't use tabs to indent"),
138 (r'^( *)\t', "don't use tabs to indent"),
139 (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
139 (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
140 "put a backslash-escaped newline after sed 'i' command"),
140 "put a backslash-escaped newline after sed 'i' command"),
141 (r'^diff *-\w*[uU].*$\n(^ \$ |^$)', "prefix diff -u/-U with cmp"),
141 (r'^diff *-\w*[uU].*$\n(^ \$ |^$)', "prefix diff -u/-U with cmp"),
142 (r'^\s+(if)? diff *-\w*[uU]', "prefix diff -u/-U with cmp"),
142 (r'^\s+(if)? diff *-\w*[uU]', "prefix diff -u/-U with cmp"),
143 (r'[\s="`\']python\s(?!bindings)', "don't use 'python', use '$PYTHON'"),
143 (r'[\s="`\']python\s(?!bindings)', "don't use 'python', use '$PYTHON'"),
144 (r'seq ', "don't use 'seq', use $TESTDIR/seq.py"),
144 (r'seq ', "don't use 'seq', use $TESTDIR/seq.py"),
145 (r'\butil\.Abort\b', "directly use error.Abort"),
145 (r'\butil\.Abort\b', "directly use error.Abort"),
146 (r'\|&', "don't use |&, use 2>&1"),
146 (r'\|&', "don't use |&, use 2>&1"),
147 (r'\w = +\w', "only one space after = allowed"),
147 (r'\w = +\w', "only one space after = allowed"),
148 (r'\bsed\b.*[^\\]\\n', "don't use 'sed ... \\n', use a \\ and a newline"),
148 (r'\bsed\b.*[^\\]\\n', "don't use 'sed ... \\n', use a \\ and a newline"),
149 (r'env.*-u', "don't use 'env -u VAR', use 'unset VAR'"),
149 (r'env.*-u', "don't use 'env -u VAR', use 'unset VAR'"),
150 (r'cp.* -r ', "don't use 'cp -r', use 'cp -R'"),
150 (r'cp.* -r ', "don't use 'cp -r', use 'cp -R'"),
151 (r'grep.* -[ABC] ', "don't use grep's context flags"),
151 (r'grep.* -[ABC]', "don't use grep's context flags"),
152 ],
152 ],
153 # warnings
153 # warnings
154 [
154 [
155 (r'^function', "don't use 'function', use old style"),
155 (r'^function', "don't use 'function', use old style"),
156 (r'^diff.*-\w*N', "don't use 'diff -N'"),
156 (r'^diff.*-\w*N', "don't use 'diff -N'"),
157 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
157 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
158 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
158 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
159 (r'kill (`|\$\()', "don't use kill, use killdaemons.py")
159 (r'kill (`|\$\()', "don't use kill, use killdaemons.py")
160 ]
160 ]
161 ]
161 ]
162
162
163 testfilters = [
163 testfilters = [
164 (r"( *)(#([^!][^\n]*\S)?)", repcomment),
164 (r"( *)(#([^!][^\n]*\S)?)", repcomment),
165 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
165 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
166 ]
166 ]
167
167
168 winglobmsg = "use (glob) to match Windows paths too"
168 winglobmsg = "use (glob) to match Windows paths too"
169 uprefix = r"^ \$ "
169 uprefix = r"^ \$ "
170 utestpats = [
170 utestpats = [
171 [
171 [
172 (r'^(\S.*|| [$>] \S.*)[ \t]\n', "trailing whitespace on non-output"),
172 (r'^(\S.*|| [$>] \S.*)[ \t]\n', "trailing whitespace on non-output"),
173 (uprefix + r'.*\|\s*sed[^|>\n]*\n',
173 (uprefix + r'.*\|\s*sed[^|>\n]*\n',
174 "use regex test output patterns instead of sed"),
174 "use regex test output patterns instead of sed"),
175 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
175 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
176 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
176 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
177 (uprefix + r'.*\|\| echo.*(fail|error)',
177 (uprefix + r'.*\|\| echo.*(fail|error)',
178 "explicit exit code checks unnecessary"),
178 "explicit exit code checks unnecessary"),
179 (uprefix + r'set -e', "don't use set -e"),
179 (uprefix + r'set -e', "don't use set -e"),
180 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
180 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
181 (uprefix + r'.*:\.\S*/', "x:.y in a path does not work on msys, rewrite "
181 (uprefix + r'.*:\.\S*/', "x:.y in a path does not work on msys, rewrite "
182 "as x://.y, or see `hg log -k msys` for alternatives", r'-\S+:\.|' #-Rxxx
182 "as x://.y, or see `hg log -k msys` for alternatives", r'-\S+:\.|' #-Rxxx
183 '# no-msys'), # in test-pull.t which is skipped on windows
183 '# no-msys'), # in test-pull.t which is skipped on windows
184 (r'^ saved backup bundle to \$TESTTMP.*\.hg$', winglobmsg),
184 (r'^ saved backup bundle to \$TESTTMP.*\.hg$', winglobmsg),
185 (r'^ changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
185 (r'^ changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
186 winglobmsg),
186 winglobmsg),
187 (r'^ pulling from \$TESTTMP/.*[^)]$', winglobmsg,
187 (r'^ pulling from \$TESTTMP/.*[^)]$', winglobmsg,
188 '\$TESTTMP/unix-repo$'), # in test-issue1802.t which skipped on windows
188 '\$TESTTMP/unix-repo$'), # in test-issue1802.t which skipped on windows
189 (r'^ reverting (?!subrepo ).*/.*[^)]$', winglobmsg),
189 (r'^ reverting (?!subrepo ).*/.*[^)]$', winglobmsg),
190 (r'^ cloning subrepo \S+/.*[^)]$', winglobmsg),
190 (r'^ cloning subrepo \S+/.*[^)]$', winglobmsg),
191 (r'^ pushing to \$TESTTMP/.*[^)]$', winglobmsg),
191 (r'^ pushing to \$TESTTMP/.*[^)]$', winglobmsg),
192 (r'^ pushing subrepo \S+/\S+ to.*[^)]$', winglobmsg),
192 (r'^ pushing subrepo \S+/\S+ to.*[^)]$', winglobmsg),
193 (r'^ moving \S+/.*[^)]$', winglobmsg),
193 (r'^ moving \S+/.*[^)]$', winglobmsg),
194 (r'^ no changes made to subrepo since.*/.*[^)]$', winglobmsg),
194 (r'^ no changes made to subrepo since.*/.*[^)]$', winglobmsg),
195 (r'^ .*: largefile \S+ not available from file:.*/.*[^)]$', winglobmsg),
195 (r'^ .*: largefile \S+ not available from file:.*/.*[^)]$', winglobmsg),
196 (r'^ .*file://\$TESTTMP',
196 (r'^ .*file://\$TESTTMP',
197 'write "file:/*/$TESTTMP" + (glob) to match on windows too'),
197 'write "file:/*/$TESTTMP" + (glob) to match on windows too'),
198 (r'^ [^$>].*27\.0\.0\.1',
198 (r'^ [^$>].*27\.0\.0\.1',
199 'use $LOCALIP not an explicit loopback address'),
199 'use $LOCALIP not an explicit loopback address'),
200 (r'^ [^$>].*\$LOCALIP.*[^)]$',
200 (r'^ [^$>].*\$LOCALIP.*[^)]$',
201 'mark $LOCALIP output lines with (glob) to help tests in BSD jails'),
201 'mark $LOCALIP output lines with (glob) to help tests in BSD jails'),
202 (r'^ (cat|find): .*: No such file or directory',
202 (r'^ (cat|find): .*: No such file or directory',
203 'use test -f to test for file existence'),
203 'use test -f to test for file existence'),
204 (r'^ diff -[^ -]*p',
204 (r'^ diff -[^ -]*p',
205 "don't use (external) diff with -p for portability"),
205 "don't use (external) diff with -p for portability"),
206 (r' readlink ', 'use readlink.py instead of readlink'),
206 (r' readlink ', 'use readlink.py instead of readlink'),
207 (r'^ [-+][-+][-+] .* [-+]0000 \(glob\)',
207 (r'^ [-+][-+][-+] .* [-+]0000 \(glob\)',
208 "glob timezone field in diff output for portability"),
208 "glob timezone field in diff output for portability"),
209 (r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@',
209 (r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@',
210 "use '@@ -N* +N,n @@ (glob)' style chunk header for portability"),
210 "use '@@ -N* +N,n @@ (glob)' style chunk header for portability"),
211 (r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@',
211 (r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@',
212 "use '@@ -N,n +N* @@ (glob)' style chunk header for portability"),
212 "use '@@ -N,n +N* @@ (glob)' style chunk header for portability"),
213 (r'^ @@ -[0-9]+ [+][0-9]+ @@',
213 (r'^ @@ -[0-9]+ [+][0-9]+ @@',
214 "use '@@ -N* +N* @@ (glob)' style chunk header for portability"),
214 "use '@@ -N* +N* @@ (glob)' style chunk header for portability"),
215 (uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff'
215 (uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff'
216 r'( +(-[^ po-]+|--(?!program|option)[^ ]+|[^-][^ ]*))*$',
216 r'( +(-[^ po-]+|--(?!program|option)[^ ]+|[^-][^ ]*))*$',
217 "use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)"),
217 "use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)"),
218 ],
218 ],
219 # warnings
219 # warnings
220 [
220 [
221 (r'^ (?!.*\$LOCALIP)[^*?/\n]* \(glob\)$',
221 (r'^ (?!.*\$LOCALIP)[^*?/\n]* \(glob\)$',
222 "glob match with no glob string (?, *, /, and $LOCALIP)"),
222 "glob match with no glob string (?, *, /, and $LOCALIP)"),
223 ]
223 ]
224 ]
224 ]
225
225
226 for i in [0, 1]:
226 for i in [0, 1]:
227 for tp in testpats[i]:
227 for tp in testpats[i]:
228 p = tp[0]
228 p = tp[0]
229 m = tp[1]
229 m = tp[1]
230 if p.startswith(r'^'):
230 if p.startswith(r'^'):
231 p = r"^ [$>] (%s)" % p[1:]
231 p = r"^ [$>] (%s)" % p[1:]
232 else:
232 else:
233 p = r"^ [$>] .*(%s)" % p
233 p = r"^ [$>] .*(%s)" % p
234 utestpats[i].append((p, m) + tp[2:])
234 utestpats[i].append((p, m) + tp[2:])
235
235
236 utestfilters = [
236 utestfilters = [
237 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
237 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
238 (r"( +)(#([^!][^\n]*\S)?)", repcomment),
238 (r"( +)(#([^!][^\n]*\S)?)", repcomment),
239 ]
239 ]
240
240
241 pypats = [
241 pypats = [
242 [
242 [
243 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
243 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
244 "tuple parameter unpacking not available in Python 3+"),
244 "tuple parameter unpacking not available in Python 3+"),
245 (r'lambda\s*\(.*,.*\)',
245 (r'lambda\s*\(.*,.*\)',
246 "tuple parameter unpacking not available in Python 3+"),
246 "tuple parameter unpacking not available in Python 3+"),
247 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
247 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
248 (r'(?<!\.)\breduce\s*\(.*', "reduce is not available in Python 3+"),
248 (r'(?<!\.)\breduce\s*\(.*', "reduce is not available in Python 3+"),
249 (r'\bdict\(.*=', 'dict() is different in Py2 and 3 and is slower than {}',
249 (r'\bdict\(.*=', 'dict() is different in Py2 and 3 and is slower than {}',
250 'dict-from-generator'),
250 'dict-from-generator'),
251 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
251 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
252 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
252 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
253 (r'^\s*\t', "don't use tabs"),
253 (r'^\s*\t', "don't use tabs"),
254 (r'\S;\s*\n', "semicolon"),
254 (r'\S;\s*\n', "semicolon"),
255 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
255 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
256 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
256 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
257 (r'(\w|\)),\w', "missing whitespace after ,"),
257 (r'(\w|\)),\w', "missing whitespace after ,"),
258 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
258 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
259 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
259 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
260 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
260 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
261 ((
261 ((
262 # a line ending with a colon, potentially with trailing comments
262 # a line ending with a colon, potentially with trailing comments
263 r':([ \t]*#[^\n]*)?\n'
263 r':([ \t]*#[^\n]*)?\n'
264 # one that is not a pass and not only a comment
264 # one that is not a pass and not only a comment
265 r'(?P<indent>[ \t]+)[^#][^\n]+\n'
265 r'(?P<indent>[ \t]+)[^#][^\n]+\n'
266 # more lines at the same indent level
266 # more lines at the same indent level
267 r'((?P=indent)[^\n]+\n)*'
267 r'((?P=indent)[^\n]+\n)*'
268 # a pass at the same indent level, which is bogus
268 # a pass at the same indent level, which is bogus
269 r'(?P=indent)pass[ \t\n#]'
269 r'(?P=indent)pass[ \t\n#]'
270 ), 'omit superfluous pass'),
270 ), 'omit superfluous pass'),
271 (r'.{81}', "line too long"),
271 (r'.{81}', "line too long"),
272 (r'[^\n]\Z', "no trailing newline"),
272 (r'[^\n]\Z', "no trailing newline"),
273 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
273 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
274 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
274 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
275 # "don't use underbars in identifiers"),
275 # "don't use underbars in identifiers"),
276 (r'^\s+(self\.)?[A-Za-z][a-z0-9]+[A-Z]\w* = ',
276 (r'^\s+(self\.)?[A-Za-z][a-z0-9]+[A-Z]\w* = ',
277 "don't use camelcase in identifiers", r'#.*camelcase-required'),
277 "don't use camelcase in identifiers", r'#.*camelcase-required'),
278 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
278 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
279 "linebreak after :"),
279 "linebreak after :"),
280 (r'class\s[^( \n]+:', "old-style class, use class foo(object)",
280 (r'class\s[^( \n]+:', "old-style class, use class foo(object)",
281 r'#.*old-style'),
281 r'#.*old-style'),
282 (r'class\s[^( \n]+\(\):',
282 (r'class\s[^( \n]+\(\):',
283 "class foo() creates old style object, use class foo(object)",
283 "class foo() creates old style object, use class foo(object)",
284 r'#.*old-style'),
284 r'#.*old-style'),
285 (r'\b(%s)\(' % '|'.join(k for k in keyword.kwlist
285 (r'\b(%s)\(' % '|'.join(k for k in keyword.kwlist
286 if k not in ('print', 'exec')),
286 if k not in ('print', 'exec')),
287 "Python keyword is not a function"),
287 "Python keyword is not a function"),
288 (r',]', "unneeded trailing ',' in list"),
288 (r',]', "unneeded trailing ',' in list"),
289 # (r'class\s[A-Z][^\(]*\((?!Exception)',
289 # (r'class\s[A-Z][^\(]*\((?!Exception)',
290 # "don't capitalize non-exception classes"),
290 # "don't capitalize non-exception classes"),
291 # (r'in range\(', "use xrange"),
291 # (r'in range\(', "use xrange"),
292 # (r'^\s*print\s+', "avoid using print in core and extensions"),
292 # (r'^\s*print\s+', "avoid using print in core and extensions"),
293 (r'[\x80-\xff]', "non-ASCII character literal"),
293 (r'[\x80-\xff]', "non-ASCII character literal"),
294 (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"),
294 (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"),
295 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
295 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
296 "gratuitous whitespace after Python keyword"),
296 "gratuitous whitespace after Python keyword"),
297 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
297 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
298 # (r'\s\s=', "gratuitous whitespace before ="),
298 # (r'\s\s=', "gratuitous whitespace before ="),
299 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
299 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
300 "missing whitespace around operator"),
300 "missing whitespace around operator"),
301 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
301 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
302 "missing whitespace around operator"),
302 "missing whitespace around operator"),
303 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
303 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
304 "missing whitespace around operator"),
304 "missing whitespace around operator"),
305 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
305 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
306 "wrong whitespace around ="),
306 "wrong whitespace around ="),
307 (r'\([^()]*( =[^=]|[^<>!=]= )',
307 (r'\([^()]*( =[^=]|[^<>!=]= )',
308 "no whitespace around = for named parameters"),
308 "no whitespace around = for named parameters"),
309 (r'raise Exception', "don't raise generic exceptions"),
309 (r'raise Exception', "don't raise generic exceptions"),
310 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
310 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
311 "don't use old-style two-argument raise, use Exception(message)"),
311 "don't use old-style two-argument raise, use Exception(message)"),
312 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
312 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
313 (r' [=!]=\s+(True|False|None)',
313 (r' [=!]=\s+(True|False|None)',
314 "comparison with singleton, use 'is' or 'is not' instead"),
314 "comparison with singleton, use 'is' or 'is not' instead"),
315 (r'^\s*(while|if) [01]:',
315 (r'^\s*(while|if) [01]:',
316 "use True/False for constant Boolean expression"),
316 "use True/False for constant Boolean expression"),
317 (r'^\s*if False(:| +and)', 'Remove code instead of using `if False`'),
317 (r'^\s*if False(:| +and)', 'Remove code instead of using `if False`'),
318 (r'(?:(?<!def)\s+|\()hasattr\(',
318 (r'(?:(?<!def)\s+|\()hasattr\(',
319 'hasattr(foo, bar) is broken on py2, use util.safehasattr(foo, bar) '
319 'hasattr(foo, bar) is broken on py2, use util.safehasattr(foo, bar) '
320 'instead', r'#.*hasattr-py3-only'),
320 'instead', r'#.*hasattr-py3-only'),
321 (r'opener\([^)]*\).read\(',
321 (r'opener\([^)]*\).read\(',
322 "use opener.read() instead"),
322 "use opener.read() instead"),
323 (r'opener\([^)]*\).write\(',
323 (r'opener\([^)]*\).write\(',
324 "use opener.write() instead"),
324 "use opener.write() instead"),
325 (r'[\s\(](open|file)\([^)]*\)\.read\(',
325 (r'[\s\(](open|file)\([^)]*\)\.read\(',
326 "use util.readfile() instead"),
326 "use util.readfile() instead"),
327 (r'[\s\(](open|file)\([^)]*\)\.write\(',
327 (r'[\s\(](open|file)\([^)]*\)\.write\(',
328 "use util.writefile() instead"),
328 "use util.writefile() instead"),
329 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
329 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
330 "always assign an opened file to a variable, and close it afterwards"),
330 "always assign an opened file to a variable, and close it afterwards"),
331 (r'[\s\(](open|file)\([^)]*\)\.',
331 (r'[\s\(](open|file)\([^)]*\)\.',
332 "always assign an opened file to a variable, and close it afterwards"),
332 "always assign an opened file to a variable, and close it afterwards"),
333 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
333 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
334 (r'\.debug\(\_', "don't mark debug messages for translation"),
334 (r'\.debug\(\_', "don't mark debug messages for translation"),
335 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
335 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
336 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
336 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
337 (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
337 (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
338 'legacy exception syntax; use "as" instead of ","'),
338 'legacy exception syntax; use "as" instead of ","'),
339 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
339 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
340 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
340 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
341 (r'\bdef\s+__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
341 (r'\bdef\s+__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
342 (r'os\.path\.join\(.*, *(""|\'\')\)',
342 (r'os\.path\.join\(.*, *(""|\'\')\)',
343 "use pathutil.normasprefix(path) instead of os.path.join(path, '')"),
343 "use pathutil.normasprefix(path) instead of os.path.join(path, '')"),
344 (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
344 (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
345 # XXX only catch mutable arguments on the first line of the definition
345 # XXX only catch mutable arguments on the first line of the definition
346 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
346 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
347 (r'\butil\.Abort\b', "directly use error.Abort"),
347 (r'\butil\.Abort\b', "directly use error.Abort"),
348 (r'^@(\w*\.)?cachefunc', "module-level @cachefunc is risky, please avoid"),
348 (r'^@(\w*\.)?cachefunc', "module-level @cachefunc is risky, please avoid"),
349 (r'^import atexit', "don't use atexit, use ui.atexit"),
349 (r'^import atexit', "don't use atexit, use ui.atexit"),
350 (r'^import Queue', "don't use Queue, use util.queue + util.empty"),
350 (r'^import Queue', "don't use Queue, use util.queue + util.empty"),
351 (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"),
351 (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"),
352 (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
352 (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
353 (r'^import SocketServer', "don't use SockerServer, use util.socketserver"),
353 (r'^import SocketServer', "don't use SockerServer, use util.socketserver"),
354 (r'^import urlparse', "don't use urlparse, use util.urlreq"),
354 (r'^import urlparse', "don't use urlparse, use util.urlreq"),
355 (r'^import xmlrpclib', "don't use xmlrpclib, use util.xmlrpclib"),
355 (r'^import xmlrpclib', "don't use xmlrpclib, use util.xmlrpclib"),
356 (r'^import cPickle', "don't use cPickle, use util.pickle"),
356 (r'^import cPickle', "don't use cPickle, use util.pickle"),
357 (r'^import pickle', "don't use pickle, use util.pickle"),
357 (r'^import pickle', "don't use pickle, use util.pickle"),
358 (r'^import httplib', "don't use httplib, use util.httplib"),
358 (r'^import httplib', "don't use httplib, use util.httplib"),
359 (r'^import BaseHTTPServer', "use util.httpserver instead"),
359 (r'^import BaseHTTPServer', "use util.httpserver instead"),
360 (r'^(from|import) mercurial\.(cext|pure|cffi)',
360 (r'^(from|import) mercurial\.(cext|pure|cffi)',
361 "use mercurial.policy.importmod instead"),
361 "use mercurial.policy.importmod instead"),
362 (r'\.next\(\)', "don't use .next(), use next(...)"),
362 (r'\.next\(\)', "don't use .next(), use next(...)"),
363 (r'([a-z]*).revision\(\1\.node\(',
363 (r'([a-z]*).revision\(\1\.node\(',
364 "don't convert rev to node before passing to revision(nodeorrev)"),
364 "don't convert rev to node before passing to revision(nodeorrev)"),
365 (r'platform\.system\(\)', "don't use platform.system(), use pycompat"),
365 (r'platform\.system\(\)', "don't use platform.system(), use pycompat"),
366
366
367 # rules depending on implementation of repquote()
367 # rules depending on implementation of repquote()
368 (r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
368 (r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
369 'string join across lines with no space'),
369 'string join across lines with no space'),
370 (r'''(?x)ui\.(status|progress|write|note|warn)\(
370 (r'''(?x)ui\.(status|progress|write|note|warn)\(
371 [ \t\n#]*
371 [ \t\n#]*
372 (?# any strings/comments might precede a string, which
372 (?# any strings/comments might precede a string, which
373 # contains translatable message)
373 # contains translatable message)
374 ((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
374 ((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
375 (?# sequence consisting of below might precede translatable message
375 (?# sequence consisting of below might precede translatable message
376 # - formatting string: "% 10s", "%05d", "% -3.2f", "%*s", "%%" ...
376 # - formatting string: "% 10s", "%05d", "% -3.2f", "%*s", "%%" ...
377 # - escaped character: "\\", "\n", "\0" ...
377 # - escaped character: "\\", "\n", "\0" ...
378 # - character other than '%', 'b' as '\', and 'x' as alphabet)
378 # - character other than '%', 'b' as '\', and 'x' as alphabet)
379 (['"]|\'\'\'|""")
379 (['"]|\'\'\'|""")
380 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
380 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
381 (?# this regexp can't use [^...] style,
381 (?# this regexp can't use [^...] style,
382 # because _preparepats forcibly adds "\n" into [^...],
382 # because _preparepats forcibly adds "\n" into [^...],
383 # even though this regexp wants match it against "\n")''',
383 # even though this regexp wants match it against "\n")''',
384 "missing _() in ui message (use () to hide false-positives)"),
384 "missing _() in ui message (use () to hide false-positives)"),
385 ],
385 ],
386 # warnings
386 # warnings
387 [
387 [
388 # rules depending on implementation of repquote()
388 # rules depending on implementation of repquote()
389 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
389 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
390 ]
390 ]
391 ]
391 ]
392
392
393 pyfilters = [
393 pyfilters = [
394 (r"""(?msx)(?P<comment>\#.*?$)|
394 (r"""(?msx)(?P<comment>\#.*?$)|
395 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
395 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
396 (?P<text>(([^\\]|\\.)*?))
396 (?P<text>(([^\\]|\\.)*?))
397 (?P=quote))""", reppython),
397 (?P=quote))""", reppython),
398 ]
398 ]
399
399
400 # non-filter patterns
400 # non-filter patterns
401 pynfpats = [
401 pynfpats = [
402 [
402 [
403 (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"),
403 (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"),
404 (r'pycompat\.osname\s*[=!]=\s*[\'"]posix[\'"]', "use pycompat.isposix"),
404 (r'pycompat\.osname\s*[=!]=\s*[\'"]posix[\'"]', "use pycompat.isposix"),
405 (r'pycompat\.sysplatform\s*[!=]=\s*[\'"]darwin[\'"]',
405 (r'pycompat\.sysplatform\s*[!=]=\s*[\'"]darwin[\'"]',
406 "use pycompat.isdarwin"),
406 "use pycompat.isdarwin"),
407 ],
407 ],
408 # warnings
408 # warnings
409 [],
409 [],
410 ]
410 ]
411
411
412 # extension non-filter patterns
412 # extension non-filter patterns
413 pyextnfpats = [
413 pyextnfpats = [
414 [(r'^"""\n?[A-Z]', "don't capitalize docstring title")],
414 [(r'^"""\n?[A-Z]', "don't capitalize docstring title")],
415 # warnings
415 # warnings
416 [],
416 [],
417 ]
417 ]
418
418
419 txtfilters = []
419 txtfilters = []
420
420
421 txtpats = [
421 txtpats = [
422 [
422 [
423 ('\s$', 'trailing whitespace'),
423 ('\s$', 'trailing whitespace'),
424 ('.. note::[ \n][^\n]', 'add two newlines after note::')
424 ('.. note::[ \n][^\n]', 'add two newlines after note::')
425 ],
425 ],
426 []
426 []
427 ]
427 ]
428
428
429 cpats = [
429 cpats = [
430 [
430 [
431 (r'//', "don't use //-style comments"),
431 (r'//', "don't use //-style comments"),
432 (r'\S\t', "don't use tabs except for indent"),
432 (r'\S\t', "don't use tabs except for indent"),
433 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
433 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
434 (r'.{81}', "line too long"),
434 (r'.{81}', "line too long"),
435 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
435 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
436 (r'return\(', "return is not a function"),
436 (r'return\(', "return is not a function"),
437 (r' ;', "no space before ;"),
437 (r' ;', "no space before ;"),
438 (r'[^;] \)', "no space before )"),
438 (r'[^;] \)', "no space before )"),
439 (r'[)][{]', "space between ) and {"),
439 (r'[)][{]', "space between ) and {"),
440 (r'\w+\* \w+', "use int *foo, not int* foo"),
440 (r'\w+\* \w+', "use int *foo, not int* foo"),
441 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
441 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
442 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
442 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
443 (r'\w,\w', "missing whitespace after ,"),
443 (r'\w,\w', "missing whitespace after ,"),
444 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
444 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
445 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
445 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
446 (r'^#\s+\w', "use #foo, not # foo"),
446 (r'^#\s+\w', "use #foo, not # foo"),
447 (r'[^\n]\Z', "no trailing newline"),
447 (r'[^\n]\Z', "no trailing newline"),
448 (r'^\s*#import\b', "use only #include in standard C code"),
448 (r'^\s*#import\b', "use only #include in standard C code"),
449 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
449 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
450 (r'strcat\(', "don't use strcat"),
450 (r'strcat\(', "don't use strcat"),
451
451
452 # rules depending on implementation of repquote()
452 # rules depending on implementation of repquote()
453 ],
453 ],
454 # warnings
454 # warnings
455 [
455 [
456 # rules depending on implementation of repquote()
456 # rules depending on implementation of repquote()
457 ]
457 ]
458 ]
458 ]
459
459
460 cfilters = [
460 cfilters = [
461 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
461 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
462 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
462 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
463 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
463 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
464 (r'(\()([^)]+\))', repcallspaces),
464 (r'(\()([^)]+\))', repcallspaces),
465 ]
465 ]
466
466
467 inutilpats = [
467 inutilpats = [
468 [
468 [
469 (r'\bui\.', "don't use ui in util"),
469 (r'\bui\.', "don't use ui in util"),
470 ],
470 ],
471 # warnings
471 # warnings
472 []
472 []
473 ]
473 ]
474
474
475 inrevlogpats = [
475 inrevlogpats = [
476 [
476 [
477 (r'\brepo\.', "don't use repo in revlog"),
477 (r'\brepo\.', "don't use repo in revlog"),
478 ],
478 ],
479 # warnings
479 # warnings
480 []
480 []
481 ]
481 ]
482
482
483 webtemplatefilters = []
483 webtemplatefilters = []
484
484
485 webtemplatepats = [
485 webtemplatepats = [
486 [],
486 [],
487 [
487 [
488 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
488 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
489 'follow desc keyword with either firstline or websub'),
489 'follow desc keyword with either firstline or websub'),
490 ]
490 ]
491 ]
491 ]
492
492
493 allfilesfilters = []
493 allfilesfilters = []
494
494
495 allfilespats = [
495 allfilespats = [
496 [
496 [
497 (r'(http|https)://[a-zA-Z0-9./]*selenic.com/',
497 (r'(http|https)://[a-zA-Z0-9./]*selenic.com/',
498 'use mercurial-scm.org domain URL'),
498 'use mercurial-scm.org domain URL'),
499 (r'mercurial@selenic\.com',
499 (r'mercurial@selenic\.com',
500 'use mercurial-scm.org domain for mercurial ML address'),
500 'use mercurial-scm.org domain for mercurial ML address'),
501 (r'mercurial-devel@selenic\.com',
501 (r'mercurial-devel@selenic\.com',
502 'use mercurial-scm.org domain for mercurial-devel ML address'),
502 'use mercurial-scm.org domain for mercurial-devel ML address'),
503 ],
503 ],
504 # warnings
504 # warnings
505 [],
505 [],
506 ]
506 ]
507
507
508 py3pats = [
508 py3pats = [
509 [
509 [
510 (r'os\.environ', "use encoding.environ instead (py3)", r'#.*re-exports'),
510 (r'os\.environ', "use encoding.environ instead (py3)", r'#.*re-exports'),
511 (r'os\.name', "use pycompat.osname instead (py3)"),
511 (r'os\.name', "use pycompat.osname instead (py3)"),
512 (r'os\.getcwd', "use pycompat.getcwd instead (py3)"),
512 (r'os\.getcwd', "use pycompat.getcwd instead (py3)"),
513 (r'os\.sep', "use pycompat.ossep instead (py3)"),
513 (r'os\.sep', "use pycompat.ossep instead (py3)"),
514 (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"),
514 (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"),
515 (r'os\.altsep', "use pycompat.osaltsep instead (py3)"),
515 (r'os\.altsep', "use pycompat.osaltsep instead (py3)"),
516 (r'sys\.platform', "use pycompat.sysplatform instead (py3)"),
516 (r'sys\.platform', "use pycompat.sysplatform instead (py3)"),
517 (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"),
517 (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"),
518 (r'os\.getenv', "use encoding.environ.get instead"),
518 (r'os\.getenv', "use encoding.environ.get instead"),
519 (r'os\.setenv', "modifying the environ dict is not preferred"),
519 (r'os\.setenv', "modifying the environ dict is not preferred"),
520 ],
520 ],
521 # warnings
521 # warnings
522 [],
522 [],
523 ]
523 ]
524
524
525 checks = [
525 checks = [
526 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
526 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
527 ('python', r'.*\.(py|cgi)$', r'^#!.*python', [], pynfpats),
527 ('python', r'.*\.(py|cgi)$', r'^#!.*python', [], pynfpats),
528 ('python', r'.*hgext.*\.py$', '', [], pyextnfpats),
528 ('python', r'.*hgext.*\.py$', '', [], pyextnfpats),
529 ('python 3', r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
529 ('python 3', r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
530 '', pyfilters, py3pats),
530 '', pyfilters, py3pats),
531 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
531 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
532 ('c', r'.*\.[ch]$', '', cfilters, cpats),
532 ('c', r'.*\.[ch]$', '', cfilters, cpats),
533 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
533 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
534 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
534 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
535 pyfilters, inrevlogpats),
535 pyfilters, inrevlogpats),
536 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
536 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
537 inutilpats),
537 inutilpats),
538 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
538 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
539 ('web template', r'mercurial/templates/.*\.tmpl', '',
539 ('web template', r'mercurial/templates/.*\.tmpl', '',
540 webtemplatefilters, webtemplatepats),
540 webtemplatefilters, webtemplatepats),
541 ('all except for .po', r'.*(?<!\.po)$', '',
541 ('all except for .po', r'.*(?<!\.po)$', '',
542 allfilesfilters, allfilespats),
542 allfilesfilters, allfilespats),
543 ]
543 ]
544
544
545 def _preparepats():
545 def _preparepats():
546 for c in checks:
546 for c in checks:
547 failandwarn = c[-1]
547 failandwarn = c[-1]
548 for pats in failandwarn:
548 for pats in failandwarn:
549 for i, pseq in enumerate(pats):
549 for i, pseq in enumerate(pats):
550 # fix-up regexes for multi-line searches
550 # fix-up regexes for multi-line searches
551 p = pseq[0]
551 p = pseq[0]
552 # \s doesn't match \n
552 # \s doesn't match \n
553 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
553 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
554 # [^...] doesn't match newline
554 # [^...] doesn't match newline
555 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
555 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
556
556
557 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
557 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
558 filters = c[3]
558 filters = c[3]
559 for i, flt in enumerate(filters):
559 for i, flt in enumerate(filters):
560 filters[i] = re.compile(flt[0]), flt[1]
560 filters[i] = re.compile(flt[0]), flt[1]
561
561
562 class norepeatlogger(object):
562 class norepeatlogger(object):
563 def __init__(self):
563 def __init__(self):
564 self._lastseen = None
564 self._lastseen = None
565
565
566 def log(self, fname, lineno, line, msg, blame):
566 def log(self, fname, lineno, line, msg, blame):
567 """print error related a to given line of a given file.
567 """print error related a to given line of a given file.
568
568
569 The faulty line will also be printed but only once in the case
569 The faulty line will also be printed but only once in the case
570 of multiple errors.
570 of multiple errors.
571
571
572 :fname: filename
572 :fname: filename
573 :lineno: line number
573 :lineno: line number
574 :line: actual content of the line
574 :line: actual content of the line
575 :msg: error message
575 :msg: error message
576 """
576 """
577 msgid = fname, lineno, line
577 msgid = fname, lineno, line
578 if msgid != self._lastseen:
578 if msgid != self._lastseen:
579 if blame:
579 if blame:
580 print("%s:%d (%s):" % (fname, lineno, blame))
580 print("%s:%d (%s):" % (fname, lineno, blame))
581 else:
581 else:
582 print("%s:%d:" % (fname, lineno))
582 print("%s:%d:" % (fname, lineno))
583 print(" > %s" % line)
583 print(" > %s" % line)
584 self._lastseen = msgid
584 self._lastseen = msgid
585 print(" " + msg)
585 print(" " + msg)
586
586
587 _defaultlogger = norepeatlogger()
587 _defaultlogger = norepeatlogger()
588
588
589 def getblame(f):
589 def getblame(f):
590 lines = []
590 lines = []
591 for l in os.popen('hg annotate -un %s' % f):
591 for l in os.popen('hg annotate -un %s' % f):
592 start, line = l.split(':', 1)
592 start, line = l.split(':', 1)
593 user, rev = start.split()
593 user, rev = start.split()
594 lines.append((line[1:-1], user, rev))
594 lines.append((line[1:-1], user, rev))
595 return lines
595 return lines
596
596
597 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
597 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
598 blame=False, debug=False, lineno=True):
598 blame=False, debug=False, lineno=True):
599 """checks style and portability of a given file
599 """checks style and portability of a given file
600
600
601 :f: filepath
601 :f: filepath
602 :logfunc: function used to report error
602 :logfunc: function used to report error
603 logfunc(filename, linenumber, linecontent, errormessage)
603 logfunc(filename, linenumber, linecontent, errormessage)
604 :maxerr: number of error to display before aborting.
604 :maxerr: number of error to display before aborting.
605 Set to false (default) to report all errors
605 Set to false (default) to report all errors
606
606
607 return True if no error is found, False otherwise.
607 return True if no error is found, False otherwise.
608 """
608 """
609 blamecache = None
609 blamecache = None
610 result = True
610 result = True
611
611
612 try:
612 try:
613 with opentext(f) as fp:
613 with opentext(f) as fp:
614 try:
614 try:
615 pre = post = fp.read()
615 pre = post = fp.read()
616 except UnicodeDecodeError as e:
616 except UnicodeDecodeError as e:
617 print("%s while reading %s" % (e, f))
617 print("%s while reading %s" % (e, f))
618 return result
618 return result
619 except IOError as e:
619 except IOError as e:
620 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
620 print("Skipping %s, %s" % (f, str(e).split(':', 1)[0]))
621 return result
621 return result
622
622
623 for name, match, magic, filters, pats in checks:
623 for name, match, magic, filters, pats in checks:
624 post = pre # discard filtering result of previous check
624 post = pre # discard filtering result of previous check
625 if debug:
625 if debug:
626 print(name, f)
626 print(name, f)
627 fc = 0
627 fc = 0
628 if not (re.match(match, f) or (magic and re.search(magic, pre))):
628 if not (re.match(match, f) or (magic and re.search(magic, pre))):
629 if debug:
629 if debug:
630 print("Skipping %s for %s it doesn't match %s" % (
630 print("Skipping %s for %s it doesn't match %s" % (
631 name, match, f))
631 name, match, f))
632 continue
632 continue
633 if "no-" "check-code" in pre:
633 if "no-" "check-code" in pre:
634 # If you're looking at this line, it's because a file has:
634 # If you're looking at this line, it's because a file has:
635 # no- check- code
635 # no- check- code
636 # but the reason to output skipping is to make life for
636 # but the reason to output skipping is to make life for
637 # tests easier. So, instead of writing it with a normal
637 # tests easier. So, instead of writing it with a normal
638 # spelling, we write it with the expected spelling from
638 # spelling, we write it with the expected spelling from
639 # tests/test-check-code.t
639 # tests/test-check-code.t
640 print("Skipping %s it has no-che?k-code (glob)" % f)
640 print("Skipping %s it has no-che?k-code (glob)" % f)
641 return "Skip" # skip checking this file
641 return "Skip" # skip checking this file
642 for p, r in filters:
642 for p, r in filters:
643 post = re.sub(p, r, post)
643 post = re.sub(p, r, post)
644 nerrs = len(pats[0]) # nerr elements are errors
644 nerrs = len(pats[0]) # nerr elements are errors
645 if warnings:
645 if warnings:
646 pats = pats[0] + pats[1]
646 pats = pats[0] + pats[1]
647 else:
647 else:
648 pats = pats[0]
648 pats = pats[0]
649 # print post # uncomment to show filtered version
649 # print post # uncomment to show filtered version
650
650
651 if debug:
651 if debug:
652 print("Checking %s for %s" % (name, f))
652 print("Checking %s for %s" % (name, f))
653
653
654 prelines = None
654 prelines = None
655 errors = []
655 errors = []
656 for i, pat in enumerate(pats):
656 for i, pat in enumerate(pats):
657 if len(pat) == 3:
657 if len(pat) == 3:
658 p, msg, ignore = pat
658 p, msg, ignore = pat
659 else:
659 else:
660 p, msg = pat
660 p, msg = pat
661 ignore = None
661 ignore = None
662 if i >= nerrs:
662 if i >= nerrs:
663 msg = "warning: " + msg
663 msg = "warning: " + msg
664
664
665 pos = 0
665 pos = 0
666 n = 0
666 n = 0
667 for m in p.finditer(post):
667 for m in p.finditer(post):
668 if prelines is None:
668 if prelines is None:
669 prelines = pre.splitlines()
669 prelines = pre.splitlines()
670 postlines = post.splitlines(True)
670 postlines = post.splitlines(True)
671
671
672 start = m.start()
672 start = m.start()
673 while n < len(postlines):
673 while n < len(postlines):
674 step = len(postlines[n])
674 step = len(postlines[n])
675 if pos + step > start:
675 if pos + step > start:
676 break
676 break
677 pos += step
677 pos += step
678 n += 1
678 n += 1
679 l = prelines[n]
679 l = prelines[n]
680
680
681 if ignore and re.search(ignore, l, re.MULTILINE):
681 if ignore and re.search(ignore, l, re.MULTILINE):
682 if debug:
682 if debug:
683 print("Skipping %s for %s:%s (ignore pattern)" % (
683 print("Skipping %s for %s:%s (ignore pattern)" % (
684 name, f, n))
684 name, f, n))
685 continue
685 continue
686 bd = ""
686 bd = ""
687 if blame:
687 if blame:
688 bd = 'working directory'
688 bd = 'working directory'
689 if not blamecache:
689 if not blamecache:
690 blamecache = getblame(f)
690 blamecache = getblame(f)
691 if n < len(blamecache):
691 if n < len(blamecache):
692 bl, bu, br = blamecache[n]
692 bl, bu, br = blamecache[n]
693 if bl == l:
693 if bl == l:
694 bd = '%s@%s' % (bu, br)
694 bd = '%s@%s' % (bu, br)
695
695
696 errors.append((f, lineno and n + 1, l, msg, bd))
696 errors.append((f, lineno and n + 1, l, msg, bd))
697 result = False
697 result = False
698
698
699 errors.sort()
699 errors.sort()
700 for e in errors:
700 for e in errors:
701 logfunc(*e)
701 logfunc(*e)
702 fc += 1
702 fc += 1
703 if maxerr and fc >= maxerr:
703 if maxerr and fc >= maxerr:
704 print(" (too many errors, giving up)")
704 print(" (too many errors, giving up)")
705 break
705 break
706
706
707 return result
707 return result
708
708
709 def main():
709 def main():
710 parser = optparse.OptionParser("%prog [options] [files | -]")
710 parser = optparse.OptionParser("%prog [options] [files | -]")
711 parser.add_option("-w", "--warnings", action="store_true",
711 parser.add_option("-w", "--warnings", action="store_true",
712 help="include warning-level checks")
712 help="include warning-level checks")
713 parser.add_option("-p", "--per-file", type="int",
713 parser.add_option("-p", "--per-file", type="int",
714 help="max warnings per file")
714 help="max warnings per file")
715 parser.add_option("-b", "--blame", action="store_true",
715 parser.add_option("-b", "--blame", action="store_true",
716 help="use annotate to generate blame info")
716 help="use annotate to generate blame info")
717 parser.add_option("", "--debug", action="store_true",
717 parser.add_option("", "--debug", action="store_true",
718 help="show debug information")
718 help="show debug information")
719 parser.add_option("", "--nolineno", action="store_false",
719 parser.add_option("", "--nolineno", action="store_false",
720 dest='lineno', help="don't show line numbers")
720 dest='lineno', help="don't show line numbers")
721
721
722 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
722 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
723 lineno=True)
723 lineno=True)
724 (options, args) = parser.parse_args()
724 (options, args) = parser.parse_args()
725
725
726 if len(args) == 0:
726 if len(args) == 0:
727 check = glob.glob("*")
727 check = glob.glob("*")
728 elif args == ['-']:
728 elif args == ['-']:
729 # read file list from stdin
729 # read file list from stdin
730 check = sys.stdin.read().splitlines()
730 check = sys.stdin.read().splitlines()
731 else:
731 else:
732 check = args
732 check = args
733
733
734 _preparepats()
734 _preparepats()
735
735
736 ret = 0
736 ret = 0
737 for f in check:
737 for f in check:
738 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
738 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
739 blame=options.blame, debug=options.debug,
739 blame=options.blame, debug=options.debug,
740 lineno=options.lineno):
740 lineno=options.lineno):
741 ret = 1
741 ret = 1
742 return ret
742 return ret
743
743
744 if __name__ == "__main__":
744 if __name__ == "__main__":
745 sys.exit(main())
745 sys.exit(main())
General Comments 0
You need to be logged in to leave comments. Login now