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