comparison contrib/check-code.py @ 43076:2372284d9457

formatting: blacken the codebase This is using my patch to black (https://github.com/psf/black/pull/826) so we don't un-wrap collection literals. Done with: hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**"' | xargs black -S # skip-blame mass-reformatting only # no-check-commit reformats foo_bar functions Differential Revision: https://phab.mercurial-scm.org/D6971
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:45:02 -0400
parents 9bfd083bc6ee
children e65e7290041e
comparison
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
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
29 if sys.version_info[0] < 3: 30 if sys.version_info[0] < 3:
30 opentext = open 31 opentext = open
31 else: 32 else:
33
32 def opentext(f): 34 def opentext(f):
33 return open(f, encoding='latin1') 35 return open(f, encoding='latin1')
36
37
34 try: 38 try:
35 xrange 39 xrange
36 except NameError: 40 except NameError:
37 xrange = range 41 xrange = range
38 try: 42 try:
39 import re2 43 import re2
40 except ImportError: 44 except ImportError:
41 re2 = None 45 re2 = None
42 46
43 import testparseutil 47 import testparseutil
48
44 49
45 def compilere(pat, multiline=False): 50 def compilere(pat, multiline=False):
46 if multiline: 51 if multiline:
47 pat = '(?m)' + pat 52 pat = '(?m)' + pat
48 if re2: 53 if re2:
50 return re2.compile(pat) 55 return re2.compile(pat)
51 except re2.error: 56 except re2.error:
52 pass 57 pass
53 return re.compile(pat) 58 return re.compile(pat)
54 59
60
55 # check "rules depending on implementation of repquote()" in each 61 # check "rules depending on implementation of repquote()" in each
56 # patterns (especially pypats), before changing around repquote() 62 # patterns (especially pypats), before changing around repquote()
57 _repquotefixedmap = {' ': ' ', '\n': '\n', '.': 'p', ':': 'q', 63 _repquotefixedmap = {
58 '%': '%', '\\': 'b', '*': 'A', '+': 'P', '-': 'M'} 64 ' ': ' ',
65 '\n': '\n',
66 '.': 'p',
67 ':': 'q',
68 '%': '%',
69 '\\': 'b',
70 '*': 'A',
71 '+': 'P',
72 '-': 'M',
73 }
74
75
59 def _repquoteencodechr(i): 76 def _repquoteencodechr(i):
60 if i > 255: 77 if i > 255:
61 return 'u' 78 return 'u'
62 c = chr(i) 79 c = chr(i)
63 if c in _repquotefixedmap: 80 if c in _repquotefixedmap:
65 if c.isalpha(): 82 if c.isalpha():
66 return 'x' 83 return 'x'
67 if c.isdigit(): 84 if c.isdigit():
68 return 'n' 85 return 'n'
69 return 'o' 86 return 'o'
87
88
70 _repquotett = ''.join(_repquoteencodechr(i) for i in xrange(256)) 89 _repquotett = ''.join(_repquoteencodechr(i) for i in xrange(256))
90
71 91
72 def repquote(m): 92 def repquote(m):
73 t = m.group('text') 93 t = m.group('text')
74 t = t.translate(_repquotett) 94 t = t.translate(_repquotett)
75 return m.group('quote') + t + m.group('quote') 95 return m.group('quote') + t + m.group('quote')
96
76 97
77 def reppython(m): 98 def reppython(m):
78 comment = m.group('comment') 99 comment = m.group('comment')
79 if comment: 100 if comment:
80 l = len(comment.rstrip()) 101 l = len(comment.rstrip())
81 return "#" * l + comment[l:] 102 return "#" * l + comment[l:]
82 return repquote(m) 103 return repquote(m)
83 104
105
84 def repcomment(m): 106 def repcomment(m):
85 return m.group(1) + "#" * len(m.group(2)) 107 return m.group(1) + "#" * len(m.group(2))
108
86 109
87 def repccomment(m): 110 def repccomment(m):
88 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2)) 111 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
89 return m.group(1) + t + "*/" 112 return m.group(1) + t + "*/"
90 113
114
91 def repcallspaces(m): 115 def repcallspaces(m):
92 t = re.sub(r"\n\s+", "\n", m.group(2)) 116 t = re.sub(r"\n\s+", "\n", m.group(2))
93 return m.group(1) + t 117 return m.group(1) + t
94 118
119
95 def repinclude(m): 120 def repinclude(m):
96 return m.group(1) + "<foo>" 121 return m.group(1) + "<foo>"
122
97 123
98 def rephere(m): 124 def rephere(m):
99 t = re.sub(r"\S", "x", m.group(2)) 125 t = re.sub(r"\S", "x", m.group(2))
100 return m.group(1) + t 126 return m.group(1) + t
101 127
102 128
103 testpats = [ 129 testpats = [
104 [ 130 [
105 (r'\b(push|pop)d\b', "don't use 'pushd' or 'popd', use 'cd'"), 131 (r'\b(push|pop)d\b', "don't use 'pushd' or 'popd', use 'cd'"),
106 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"), 132 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
107 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"), 133 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
108 (r'(?<!hg )grep.* -a', "don't use 'grep -a', use in-line python"), 134 (r'(?<!hg )grep.* -a', "don't use 'grep -a', use in-line python"),
109 (r'sed.*-i', "don't use 'sed -i', use a temporary file"), 135 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
110 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"), 136 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
111 (r'echo -n', "don't use 'echo -n', use printf"), 137 (r'echo -n', "don't use 'echo -n', use printf"),
112 (r'(^|\|\s*)\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"), 138 (r'(^|\|\s*)\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"),
113 (r'head -c', "don't use 'head -c', use 'dd'"), 139 (r'head -c', "don't use 'head -c', use 'dd'"),
114 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"), 140 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
115 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"), 141 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
116 (r'\bls\b.*-\w*R', "don't use 'ls -R', use 'find'"), 142 (r'\bls\b.*-\w*R', "don't use 'ls -R', use 'find'"),
117 (r'printf.*[^\\]\\([1-9]|0\d)', r"don't use 'printf \NNN', use Python"), 143 (r'printf.*[^\\]\\([1-9]|0\d)', r"don't use 'printf \NNN', use Python"),
118 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"), 144 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
119 (r'rm -rf \*', "don't use naked rm -rf, target a directory"), 145 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
120 (r'\[[^\]]+==', '[ foo == bar ] is a bashism, use [ foo = bar ] instead'), 146 (
121 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w', 147 r'\[[^\]]+==',
122 "use egrep for extended grep syntax"), 148 '[ foo == bar ] is a bashism, use [ foo = bar ] instead',
123 (r'(^|\|\s*)e?grep .*\\S', "don't use \\S in regular expression"), 149 ),
124 (r'(?<!!)/bin/', "don't use explicit paths for tools"), 150 (
125 (r'#!.*/bash', "don't use bash in shebang, use sh"), 151 r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
126 (r'[^\n]\Z', "no trailing newline"), 152 "use egrep for extended grep syntax",
127 (r'export .*=', "don't export and assign at once"), 153 ),
128 (r'^source\b', "don't use 'source', use '.'"), 154 (r'(^|\|\s*)e?grep .*\\S', "don't use \\S in regular expression"),
129 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"), 155 (r'(?<!!)/bin/', "don't use explicit paths for tools"),
130 (r'\bls +[^|\n-]+ +-', "options to 'ls' must come before filenames"), 156 (r'#!.*/bash', "don't use bash in shebang, use sh"),
131 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"), 157 (r'[^\n]\Z', "no trailing newline"),
132 (r'^stop\(\)', "don't use 'stop' as a shell function name"), 158 (r'export .*=', "don't export and assign at once"),
133 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"), 159 (r'^source\b', "don't use 'source', use '.'"),
134 (r'\[\[\s+[^\]]*\]\]', "don't use '[[ ]]', use '[ ]'"), 160 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
135 (r'^alias\b.*=', "don't use alias, use a function"), 161 (r'\bls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
136 (r'if\s*!', "don't use '!' to negate exit status"), 162 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
137 (r'/dev/u?random', "don't use entropy, use /dev/zero"), 163 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
138 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"), 164 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
139 (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)', 165 (r'\[\[\s+[^\]]*\]\]', "don't use '[[ ]]', use '[ ]'"),
140 "put a backslash-escaped newline after sed 'i' command"), 166 (r'^alias\b.*=', "don't use alias, use a function"),
141 (r'^diff *-\w*[uU].*$\n(^ \$ |^$)', "prefix diff -u/-U with cmp"), 167 (r'if\s*!', "don't use '!' to negate exit status"),
142 (r'^\s+(if)? diff *-\w*[uU]', "prefix diff -u/-U with cmp"), 168 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
143 (r'[\s="`\']python\s(?!bindings)', "don't use 'python', use '$PYTHON'"), 169 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
144 (r'seq ', "don't use 'seq', use $TESTDIR/seq.py"), 170 (
145 (r'\butil\.Abort\b', "directly use error.Abort"), 171 r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
146 (r'\|&', "don't use |&, use 2>&1"), 172 "put a backslash-escaped newline after sed 'i' command",
147 (r'\w = +\w', "only one space after = allowed"), 173 ),
148 (r'\bsed\b.*[^\\]\\n', "don't use 'sed ... \\n', use a \\ and a newline"), 174 (r'^diff *-\w*[uU].*$\n(^ \$ |^$)', "prefix diff -u/-U with cmp"),
149 (r'env.*-u', "don't use 'env -u VAR', use 'unset VAR'"), 175 (r'^\s+(if)? diff *-\w*[uU]', "prefix diff -u/-U with cmp"),
150 (r'cp.* -r ', "don't use 'cp -r', use 'cp -R'"), 176 (r'[\s="`\']python\s(?!bindings)', "don't use 'python', use '$PYTHON'"),
151 (r'grep.* -[ABC]', "don't use grep's context flags"), 177 (r'seq ', "don't use 'seq', use $TESTDIR/seq.py"),
152 (r'find.*-printf', 178 (r'\butil\.Abort\b', "directly use error.Abort"),
153 "don't use 'find -printf', it doesn't exist on BSD find(1)"), 179 (r'\|&', "don't use |&, use 2>&1"),
154 (r'\$RANDOM ', "don't use bash-only $RANDOM to generate random values"), 180 (r'\w = +\w', "only one space after = allowed"),
155 ], 181 (
156 # warnings 182 r'\bsed\b.*[^\\]\\n',
157 [ 183 "don't use 'sed ... \\n', use a \\ and a newline",
158 (r'^function', "don't use 'function', use old style"), 184 ),
159 (r'^diff.*-\w*N', "don't use 'diff -N'"), 185 (r'env.*-u', "don't use 'env -u VAR', use 'unset VAR'"),
160 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"), 186 (r'cp.* -r ', "don't use 'cp -r', use 'cp -R'"),
161 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"), 187 (r'grep.* -[ABC]', "don't use grep's context flags"),
162 (r'kill (`|\$\()', "don't use kill, use killdaemons.py") 188 (
163 ] 189 r'find.*-printf',
190 "don't use 'find -printf', it doesn't exist on BSD find(1)",
191 ),
192 (r'\$RANDOM ', "don't use bash-only $RANDOM to generate random values"),
193 ],
194 # warnings
195 [
196 (r'^function', "don't use 'function', use old style"),
197 (r'^diff.*-\w*N', "don't use 'diff -N'"),
198 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
199 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
200 (r'kill (`|\$\()', "don't use kill, use killdaemons.py"),
201 ],
164 ] 202 ]
165 203
166 testfilters = [ 204 testfilters = [
167 (r"( *)(#([^!][^\n]*\S)?)", repcomment), 205 (r"( *)(#([^!][^\n]*\S)?)", repcomment),
168 (r"<<(\S+)((.|\n)*?\n\1)", rephere), 206 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
169 ] 207 ]
170 208
171 uprefix = r"^ \$ " 209 uprefix = r"^ \$ "
172 utestpats = [ 210 utestpats = [
173 [ 211 [
174 (r'^(\S.*|| [$>] \S.*)[ \t]\n', "trailing whitespace on non-output"), 212 (r'^(\S.*|| [$>] \S.*)[ \t]\n', "trailing whitespace on non-output"),
175 (uprefix + r'.*\|\s*sed[^|>\n]*\n', 213 (
176 "use regex test output patterns instead of sed"), 214 uprefix + r'.*\|\s*sed[^|>\n]*\n',
177 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"), 215 "use regex test output patterns instead of sed",
178 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"), 216 ),
179 (uprefix + r'.*\|\| echo.*(fail|error)', 217 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
180 "explicit exit code checks unnecessary"), 218 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
181 (uprefix + r'set -e', "don't use set -e"), 219 (
182 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"), 220 uprefix + r'.*\|\| echo.*(fail|error)',
183 (uprefix + r'.*:\.\S*/', "x:.y in a path does not work on msys, rewrite " 221 "explicit exit code checks unnecessary",
184 "as x://.y, or see `hg log -k msys` for alternatives", r'-\S+:\.|' #-Rxxx 222 ),
185 '# no-msys'), # in test-pull.t which is skipped on windows 223 (uprefix + r'set -e', "don't use set -e"),
186 (r'^ [^$>].*27\.0\.0\.1', 224 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
187 'use $LOCALIP not an explicit loopback address'), 225 (
188 (r'^ (?![>$] ).*\$LOCALIP.*[^)]$', 226 uprefix + r'.*:\.\S*/',
189 'mark $LOCALIP output lines with (glob) to help tests in BSD jails'), 227 "x:.y in a path does not work on msys, rewrite "
190 (r'^ (cat|find): .*: \$ENOENT\$', 228 "as x://.y, or see `hg log -k msys` for alternatives",
191 'use test -f to test for file existence'), 229 r'-\S+:\.|' '# no-msys', # -Rxxx
192 (r'^ diff -[^ -]*p', 230 ), # in test-pull.t which is skipped on windows
193 "don't use (external) diff with -p for portability"), 231 (
194 (r' readlink ', 'use readlink.py instead of readlink'), 232 r'^ [^$>].*27\.0\.0\.1',
195 (r'^ [-+][-+][-+] .* [-+]0000 \(glob\)', 233 'use $LOCALIP not an explicit loopback address',
196 "glob timezone field in diff output for portability"), 234 ),
197 (r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@', 235 (
198 "use '@@ -N* +N,n @@ (glob)' style chunk header for portability"), 236 r'^ (?![>$] ).*\$LOCALIP.*[^)]$',
199 (r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@', 237 'mark $LOCALIP output lines with (glob) to help tests in BSD jails',
200 "use '@@ -N,n +N* @@ (glob)' style chunk header for portability"), 238 ),
201 (r'^ @@ -[0-9]+ [+][0-9]+ @@', 239 (
202 "use '@@ -N* +N* @@ (glob)' style chunk header for portability"), 240 r'^ (cat|find): .*: \$ENOENT\$',
203 (uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff' 241 'use test -f to test for file existence',
204 r'( +(-[^ po-]+|--(?!program|option)[^ ]+|[^-][^ ]*))*$', 242 ),
205 "use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)"), 243 (
206 ], 244 r'^ diff -[^ -]*p',
207 # warnings 245 "don't use (external) diff with -p for portability",
208 [ 246 ),
209 (r'^ (?!.*\$LOCALIP)[^*?/\n]* \(glob\)$', 247 (r' readlink ', 'use readlink.py instead of readlink'),
210 "glob match with no glob string (?, *, /, and $LOCALIP)"), 248 (
211 ] 249 r'^ [-+][-+][-+] .* [-+]0000 \(glob\)',
250 "glob timezone field in diff output for portability",
251 ),
252 (
253 r'^ @@ -[0-9]+ [+][0-9]+,[0-9]+ @@',
254 "use '@@ -N* +N,n @@ (glob)' style chunk header for portability",
255 ),
256 (
257 r'^ @@ -[0-9]+,[0-9]+ [+][0-9]+ @@',
258 "use '@@ -N,n +N* @@ (glob)' style chunk header for portability",
259 ),
260 (
261 r'^ @@ -[0-9]+ [+][0-9]+ @@',
262 "use '@@ -N* +N* @@ (glob)' style chunk header for portability",
263 ),
264 (
265 uprefix + r'hg( +-[^ ]+( +[^ ]+)?)* +extdiff'
266 r'( +(-[^ po-]+|--(?!program|option)[^ ]+|[^-][^ ]*))*$',
267 "use $RUNTESTDIR/pdiff via extdiff (or -o/-p for false-positives)",
268 ),
269 ],
270 # warnings
271 [
272 (
273 r'^ (?!.*\$LOCALIP)[^*?/\n]* \(glob\)$',
274 "glob match with no glob string (?, *, /, and $LOCALIP)",
275 ),
276 ],
212 ] 277 ]
213 278
214 # transform plain test rules to unified test's 279 # transform plain test rules to unified test's
215 for i in [0, 1]: 280 for i in [0, 1]:
216 for tp in testpats[i]: 281 for tp in testpats[i]:
232 (r"( +)(#([^!][^\n]*\S)?)", repcomment), 297 (r"( +)(#([^!][^\n]*\S)?)", repcomment),
233 ] 298 ]
234 299
235 # common patterns to check *.py 300 # common patterns to check *.py
236 commonpypats = [ 301 commonpypats = [
237 [ 302 [
238 (r'\\$', 'Use () to wrap long lines in Python, not \\'), 303 (r'\\$', 'Use () to wrap long lines in Python, not \\'),
239 (r'^\s*def\s*\w+\s*\(.*,\s*\(', 304 (
240 "tuple parameter unpacking not available in Python 3+"), 305 r'^\s*def\s*\w+\s*\(.*,\s*\(',
241 (r'lambda\s*\(.*,.*\)', 306 "tuple parameter unpacking not available in Python 3+",
242 "tuple parameter unpacking not available in Python 3+"), 307 ),
243 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"), 308 (
244 (r'(?<!\.)\breduce\s*\(.*', "reduce is not available in Python 3+"), 309 r'lambda\s*\(.*,.*\)',
245 (r'\bdict\(.*=', 'dict() is different in Py2 and 3 and is slower than {}', 310 "tuple parameter unpacking not available in Python 3+",
246 'dict-from-generator'), 311 ),
247 (r'\.has_key\b', "dict.has_key is not available in Python 3+"), 312 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
248 (r'\s<>\s', '<> operator is not available in Python 3+, use !='), 313 (r'(?<!\.)\breduce\s*\(.*', "reduce is not available in Python 3+"),
249 (r'^\s*\t', "don't use tabs"), 314 (
250 (r'\S;\s*\n', "semicolon"), 315 r'\bdict\(.*=',
251 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"), 316 'dict() is different in Py2 and 3 and is slower than {}',
252 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"), 317 'dict-from-generator',
253 (r'(\w|\)),\w', "missing whitespace after ,"), 318 ),
254 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"), 319 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
255 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="), 320 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
256 (( 321 (r'^\s*\t', "don't use tabs"),
257 # a line ending with a colon, potentially with trailing comments 322 (r'\S;\s*\n', "semicolon"),
258 r':([ \t]*#[^\n]*)?\n' 323 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
259 # one that is not a pass and not only a comment 324 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
260 r'(?P<indent>[ \t]+)[^#][^\n]+\n' 325 (r'(\w|\)),\w', "missing whitespace after ,"),
261 # more lines at the same indent level 326 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
262 r'((?P=indent)[^\n]+\n)*' 327 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
263 # a pass at the same indent level, which is bogus 328 (
264 r'(?P=indent)pass[ \t\n#]' 329 (
265 ), 'omit superfluous pass'), 330 # a line ending with a colon, potentially with trailing comments
266 (r'[^\n]\Z', "no trailing newline"), 331 r':([ \t]*#[^\n]*)?\n'
267 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"), 332 # one that is not a pass and not only a comment
268 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=', 333 r'(?P<indent>[ \t]+)[^#][^\n]+\n'
269 # "don't use underbars in identifiers"), 334 # more lines at the same indent level
270 (r'^\s+(self\.)?[A-Za-z][a-z0-9]+[A-Z]\w* = ', 335 r'((?P=indent)[^\n]+\n)*'
271 "don't use camelcase in identifiers", r'#.*camelcase-required'), 336 # a pass at the same indent level, which is bogus
272 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+', 337 r'(?P=indent)pass[ \t\n#]'
273 "linebreak after :"), 338 ),
274 (r'class\s[^( \n]+:', "old-style class, use class foo(object)", 339 'omit superfluous pass',
275 r'#.*old-style'), 340 ),
276 (r'class\s[^( \n]+\(\):', 341 (r'[^\n]\Z', "no trailing newline"),
277 "class foo() creates old style object, use class foo(object)", 342 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
278 r'#.*old-style'), 343 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
279 (r'\b(%s)\(' % '|'.join(k for k in keyword.kwlist 344 # "don't use underbars in identifiers"),
280 if k not in ('print', 'exec')), 345 (
281 "Python keyword is not a function"), 346 r'^\s+(self\.)?[A-Za-z][a-z0-9]+[A-Z]\w* = ',
282 # (r'class\s[A-Z][^\(]*\((?!Exception)', 347 "don't use camelcase in identifiers",
283 # "don't capitalize non-exception classes"), 348 r'#.*camelcase-required',
284 # (r'in range\(', "use xrange"), 349 ),
285 # (r'^\s*print\s+', "avoid using print in core and extensions"), 350 (
286 (r'[\x80-\xff]', "non-ASCII character literal"), 351 r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
287 (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"), 352 "linebreak after :",
288 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"), 353 ),
289 # (r'\s\s=', "gratuitous whitespace before ="), 354 (
290 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S', 355 r'class\s[^( \n]+:',
291 "missing whitespace around operator"), 356 "old-style class, use class foo(object)",
292 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s', 357 r'#.*old-style',
293 "missing whitespace around operator"), 358 ),
294 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S', 359 (
295 "missing whitespace around operator"), 360 r'class\s[^( \n]+\(\):',
296 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]', 361 "class foo() creates old style object, use class foo(object)",
297 "wrong whitespace around ="), 362 r'#.*old-style',
298 (r'\([^()]*( =[^=]|[^<>!=]= )', 363 ),
299 "no whitespace around = for named parameters"), 364 (
300 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$', 365 r'\b(%s)\('
301 "don't use old-style two-argument raise, use Exception(message)"), 366 % '|'.join(k for k in keyword.kwlist if k not in ('print', 'exec')),
302 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"), 367 "Python keyword is not a function",
303 (r' [=!]=\s+(True|False|None)', 368 ),
304 "comparison with singleton, use 'is' or 'is not' instead"), 369 # (r'class\s[A-Z][^\(]*\((?!Exception)',
305 (r'^\s*(while|if) [01]:', 370 # "don't capitalize non-exception classes"),
306 "use True/False for constant Boolean expression"), 371 # (r'in range\(', "use xrange"),
307 (r'^\s*if False(:| +and)', 'Remove code instead of using `if False`'), 372 # (r'^\s*print\s+', "avoid using print in core and extensions"),
308 (r'(?:(?<!def)\s+|\()hasattr\(', 373 (r'[\x80-\xff]', "non-ASCII character literal"),
309 'hasattr(foo, bar) is broken on py2, use util.safehasattr(foo, bar) ' 374 (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"),
310 'instead', r'#.*hasattr-py3-only'), 375 (
311 (r'opener\([^)]*\).read\(', 376 r'([\(\[][ \t]\S)|(\S[ \t][\)\]])',
312 "use opener.read() instead"), 377 "gratuitous whitespace in () or []",
313 (r'opener\([^)]*\).write\(', 378 ),
314 "use opener.write() instead"), 379 # (r'\s\s=', "gratuitous whitespace before ="),
315 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"), 380 (
316 (r'\.debug\(\_', "don't mark debug messages for translation"), 381 r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
317 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"), 382 "missing whitespace around operator",
318 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'), 383 ),
319 (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,', 384 (
320 'legacy exception syntax; use "as" instead of ","'), 385 r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
321 (r'release\(.*wlock, .*lock\)', "wrong lock release order"), 386 "missing whitespace around operator",
322 (r'\bdef\s+__bool__\b', "__bool__ should be __nonzero__ in Python 2"), 387 ),
323 (r'os\.path\.join\(.*, *(""|\'\')\)', 388 (
324 "use pathutil.normasprefix(path) instead of os.path.join(path, '')"), 389 r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
325 (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'), 390 "missing whitespace around operator",
326 # XXX only catch mutable arguments on the first line of the definition 391 ),
327 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"), 392 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]', "wrong whitespace around ="),
328 (r'\butil\.Abort\b', "directly use error.Abort"), 393 (
329 (r'^@(\w*\.)?cachefunc', "module-level @cachefunc is risky, please avoid"), 394 r'\([^()]*( =[^=]|[^<>!=]= )',
330 (r'^import Queue', "don't use Queue, use pycompat.queue.Queue + " 395 "no whitespace around = for named parameters",
331 "pycompat.queue.Empty"), 396 ),
332 (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"), 397 (
333 (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"), 398 r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
334 (r'^import SocketServer', "don't use SockerServer, use util.socketserver"), 399 "don't use old-style two-argument raise, use Exception(message)",
335 (r'^import urlparse', "don't use urlparse, use util.urlreq"), 400 ),
336 (r'^import xmlrpclib', "don't use xmlrpclib, use util.xmlrpclib"), 401 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
337 (r'^import cPickle', "don't use cPickle, use util.pickle"), 402 (
338 (r'^import pickle', "don't use pickle, use util.pickle"), 403 r' [=!]=\s+(True|False|None)',
339 (r'^import httplib', "don't use httplib, use util.httplib"), 404 "comparison with singleton, use 'is' or 'is not' instead",
340 (r'^import BaseHTTPServer', "use util.httpserver instead"), 405 ),
341 (r'^(from|import) mercurial\.(cext|pure|cffi)', 406 (
342 "use mercurial.policy.importmod instead"), 407 r'^\s*(while|if) [01]:',
343 (r'\.next\(\)', "don't use .next(), use next(...)"), 408 "use True/False for constant Boolean expression",
344 (r'([a-z]*).revision\(\1\.node\(', 409 ),
345 "don't convert rev to node before passing to revision(nodeorrev)"), 410 (r'^\s*if False(:| +and)', 'Remove code instead of using `if False`'),
346 (r'platform\.system\(\)', "don't use platform.system(), use pycompat"), 411 (
347 412 r'(?:(?<!def)\s+|\()hasattr\(',
348 ], 413 'hasattr(foo, bar) is broken on py2, use util.safehasattr(foo, bar) '
349 # warnings 414 'instead',
350 [ 415 r'#.*hasattr-py3-only',
351 ] 416 ),
417 (r'opener\([^)]*\).read\(', "use opener.read() instead"),
418 (r'opener\([^)]*\).write\(', "use opener.write() instead"),
419 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
420 (r'\.debug\(\_', "don't mark debug messages for translation"),
421 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
422 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
423 (
424 r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
425 'legacy exception syntax; use "as" instead of ","',
426 ),
427 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
428 (r'\bdef\s+__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
429 (
430 r'os\.path\.join\(.*, *(""|\'\')\)',
431 "use pathutil.normasprefix(path) instead of os.path.join(path, '')",
432 ),
433 (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
434 # XXX only catch mutable arguments on the first line of the definition
435 (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
436 (r'\butil\.Abort\b', "directly use error.Abort"),
437 (
438 r'^@(\w*\.)?cachefunc',
439 "module-level @cachefunc is risky, please avoid",
440 ),
441 (
442 r'^import Queue',
443 "don't use Queue, use pycompat.queue.Queue + "
444 "pycompat.queue.Empty",
445 ),
446 (
447 r'^import cStringIO',
448 "don't use cStringIO.StringIO, use util.stringio",
449 ),
450 (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
451 (
452 r'^import SocketServer',
453 "don't use SockerServer, use util.socketserver",
454 ),
455 (r'^import urlparse', "don't use urlparse, use util.urlreq"),
456 (r'^import xmlrpclib', "don't use xmlrpclib, use util.xmlrpclib"),
457 (r'^import cPickle', "don't use cPickle, use util.pickle"),
458 (r'^import pickle', "don't use pickle, use util.pickle"),
459 (r'^import httplib', "don't use httplib, use util.httplib"),
460 (r'^import BaseHTTPServer', "use util.httpserver instead"),
461 (
462 r'^(from|import) mercurial\.(cext|pure|cffi)',
463 "use mercurial.policy.importmod instead",
464 ),
465 (r'\.next\(\)', "don't use .next(), use next(...)"),
466 (
467 r'([a-z]*).revision\(\1\.node\(',
468 "don't convert rev to node before passing to revision(nodeorrev)",
469 ),
470 (r'platform\.system\(\)', "don't use platform.system(), use pycompat"),
471 ],
472 # warnings
473 [],
352 ] 474 ]
353 475
354 # patterns to check normal *.py files 476 # patterns to check normal *.py files
355 pypats = [ 477 pypats = [
356 [ 478 [
357 # Ideally, these should be placed in "commonpypats" for 479 # Ideally, these should be placed in "commonpypats" for
358 # consistency of coding rules in Mercurial source tree. 480 # consistency of coding rules in Mercurial source tree.
359 # But on the other hand, these are not so seriously required for 481 # But on the other hand, these are not so seriously required for
360 # python code fragments embedded in test scripts. Fixing test 482 # python code fragments embedded in test scripts. Fixing test
361 # scripts for these patterns requires many changes, and has less 483 # scripts for these patterns requires many changes, and has less
362 # profit than effort. 484 # profit than effort.
363 (r'raise Exception', "don't raise generic exceptions"), 485 (r'raise Exception', "don't raise generic exceptions"),
364 (r'[\s\(](open|file)\([^)]*\)\.read\(', 486 (r'[\s\(](open|file)\([^)]*\)\.read\(', "use util.readfile() instead"),
365 "use util.readfile() instead"), 487 (
366 (r'[\s\(](open|file)\([^)]*\)\.write\(', 488 r'[\s\(](open|file)\([^)]*\)\.write\(',
367 "use util.writefile() instead"), 489 "use util.writefile() instead",
368 (r'^[\s\(]*(open(er)?|file)\([^)]*\)(?!\.close\(\))', 490 ),
369 "always assign an opened file to a variable, and close it afterwards"), 491 (
370 (r'[\s\(](open|file)\([^)]*\)\.(?!close\(\))', 492 r'^[\s\(]*(open(er)?|file)\([^)]*\)(?!\.close\(\))',
371 "always assign an opened file to a variable, and close it afterwards"), 493 "always assign an opened file to a variable, and close it afterwards",
372 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"), 494 ),
373 (r'^import atexit', "don't use atexit, use ui.atexit"), 495 (
374 496 r'[\s\(](open|file)\([^)]*\)\.(?!close\(\))',
375 # rules depending on implementation of repquote() 497 "always assign an opened file to a variable, and close it afterwards",
376 (r' x+[xpqo%APM][\'"]\n\s+[\'"]x', 498 ),
377 'string join across lines with no space'), 499 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
378 (r'''(?x)ui\.(status|progress|write|note|warn)\( 500 (r'^import atexit', "don't use atexit, use ui.atexit"),
501 # rules depending on implementation of repquote()
502 (
503 r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
504 'string join across lines with no space',
505 ),
506 (
507 r'''(?x)ui\.(status|progress|write|note|warn)\(
379 [ \t\n#]* 508 [ \t\n#]*
380 (?# any strings/comments might precede a string, which 509 (?# any strings/comments might precede a string, which
381 # contains translatable message) 510 # contains translatable message)
382 ((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)* 511 ((['"]|\'\'\'|""")[ \npq%bAPMxno]*(['"]|\'\'\'|""")[ \t\n#]+)*
383 (?# sequence consisting of below might precede translatable message 512 (?# sequence consisting of below might precede translatable message
387 (['"]|\'\'\'|""") 516 (['"]|\'\'\'|""")
388 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x 517 ((%([ n]?[PM]?([np]+|A))?x)|%%|b[bnx]|[ \nnpqAPMo])*x
389 (?# this regexp can't use [^...] style, 518 (?# this regexp can't use [^...] style,
390 # because _preparepats forcibly adds "\n" into [^...], 519 # because _preparepats forcibly adds "\n" into [^...],
391 # even though this regexp wants match it against "\n")''', 520 # even though this regexp wants match it against "\n")''',
392 "missing _() in ui message (use () to hide false-positives)"), 521 "missing _() in ui message (use () to hide false-positives)",
393 ] + commonpypats[0], 522 ),
394 # warnings 523 ]
395 [ 524 + commonpypats[0],
396 # rules depending on implementation of repquote() 525 # warnings
397 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"), 526 [
398 ] + commonpypats[1] 527 # rules depending on implementation of repquote()
528 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
529 ]
530 + commonpypats[1],
399 ] 531 ]
400 532
401 # patterns to check *.py for embedded ones in test script 533 # patterns to check *.py for embedded ones in test script
402 embeddedpypats = [ 534 embeddedpypats = [
403 [ 535 [] + commonpypats[0],
404 ] + commonpypats[0], 536 # warnings
405 # warnings 537 [] + commonpypats[1],
406 [
407 ] + commonpypats[1]
408 ] 538 ]
409 539
410 # common filters to convert *.py 540 # common filters to convert *.py
411 commonpyfilters = [ 541 commonpyfilters = [
412 (r"""(?msx)(?P<comment>\#.*?$)| 542 (
543 r"""(?msx)(?P<comment>\#.*?$)|
413 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!"))) 544 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
414 (?P<text>(([^\\]|\\.)*?)) 545 (?P<text>(([^\\]|\\.)*?))
415 (?P=quote))""", reppython), 546 (?P=quote))""",
547 reppython,
548 ),
416 ] 549 ]
417 550
418 # filters to convert normal *.py files 551 # filters to convert normal *.py files
419 pyfilters = [ 552 pyfilters = [] + commonpyfilters
420 ] + commonpyfilters
421 553
422 # non-filter patterns 554 # non-filter patterns
423 pynfpats = [ 555 pynfpats = [
424 [ 556 [
425 (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"), 557 (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"),
426 (r'pycompat\.osname\s*[=!]=\s*[\'"]posix[\'"]', "use pycompat.isposix"), 558 (r'pycompat\.osname\s*[=!]=\s*[\'"]posix[\'"]', "use pycompat.isposix"),
427 (r'pycompat\.sysplatform\s*[!=]=\s*[\'"]darwin[\'"]', 559 (
428 "use pycompat.isdarwin"), 560 r'pycompat\.sysplatform\s*[!=]=\s*[\'"]darwin[\'"]',
561 "use pycompat.isdarwin",
562 ),
429 ], 563 ],
430 # warnings 564 # warnings
431 [], 565 [],
432 ] 566 ]
433 567
434 # filters to convert *.py for embedded ones in test script 568 # filters to convert *.py for embedded ones in test script
435 embeddedpyfilters = [ 569 embeddedpyfilters = [] + commonpyfilters
436 ] + commonpyfilters
437 570
438 # extension non-filter patterns 571 # extension non-filter patterns
439 pyextnfpats = [ 572 pyextnfpats = [
440 [(r'^"""\n?[A-Z]', "don't capitalize docstring title")], 573 [(r'^"""\n?[A-Z]', "don't capitalize docstring title")],
441 # warnings 574 # warnings
443 ] 576 ]
444 577
445 txtfilters = [] 578 txtfilters = []
446 579
447 txtpats = [ 580 txtpats = [
448 [ 581 [
449 (r'\s$', 'trailing whitespace'), 582 (r'\s$', 'trailing whitespace'),
450 ('.. note::[ \n][^\n]', 'add two newlines after note::') 583 ('.. note::[ \n][^\n]', 'add two newlines after note::'),
451 ], 584 ],
452 [] 585 [],
453 ] 586 ]
454 587
455 cpats = [ 588 cpats = [
456 [ 589 [
457 (r'//', "don't use //-style comments"), 590 (r'//', "don't use //-style comments"),
458 (r'\S\t', "don't use tabs except for indent"), 591 (r'\S\t', "don't use tabs except for indent"),
459 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"), 592 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
460 (r'(while|if|do|for)\(', "use space after while/if/do/for"), 593 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
461 (r'return\(', "return is not a function"), 594 (r'return\(', "return is not a function"),
462 (r' ;', "no space before ;"), 595 (r' ;', "no space before ;"),
463 (r'[^;] \)', "no space before )"), 596 (r'[^;] \)', "no space before )"),
464 (r'[)][{]', "space between ) and {"), 597 (r'[)][{]', "space between ) and {"),
465 (r'\w+\* \w+', "use int *foo, not int* foo"), 598 (r'\w+\* \w+', "use int *foo, not int* foo"),
466 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"), 599 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
467 (r'\w+ (\+\+|--)', "use foo++, not foo ++"), 600 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
468 (r'\w,\w', "missing whitespace after ,"), 601 (r'\w,\w', "missing whitespace after ,"),
469 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"), 602 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
470 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="), 603 (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
471 (r'^#\s+\w', "use #foo, not # foo"), 604 (r'^#\s+\w', "use #foo, not # foo"),
472 (r'[^\n]\Z', "no trailing newline"), 605 (r'[^\n]\Z', "no trailing newline"),
473 (r'^\s*#import\b', "use only #include in standard C code"), 606 (r'^\s*#import\b', "use only #include in standard C code"),
474 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"), 607 (r'strcpy\(', "don't use strcpy, use strlcpy or memcpy"),
475 (r'strcat\(', "don't use strcat"), 608 (r'strcat\(', "don't use strcat"),
476 609 # rules depending on implementation of repquote()
477 # rules depending on implementation of repquote() 610 ],
478 ], 611 # warnings
479 # warnings 612 [
480 [ 613 # rules depending on implementation of repquote()
481 # rules depending on implementation of repquote() 614 ],
482 ]
483 ] 615 ]
484 616
485 cfilters = [ 617 cfilters = [
486 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment), 618 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
487 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote), 619 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
488 (r'''(#\s*include\s+<)([^>]+)>''', repinclude), 620 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
489 (r'(\()([^)]+\))', repcallspaces), 621 (r'(\()([^)]+\))', repcallspaces),
490 ] 622 ]
491 623
492 inutilpats = [ 624 inutilpats = [
493 [ 625 [(r'\bui\.', "don't use ui in util"),],
494 (r'\bui\.', "don't use ui in util"), 626 # warnings
495 ], 627 [],
496 # warnings
497 []
498 ] 628 ]
499 629
500 inrevlogpats = [ 630 inrevlogpats = [
501 [ 631 [(r'\brepo\.', "don't use repo in revlog"),],
502 (r'\brepo\.', "don't use repo in revlog"), 632 # warnings
503 ], 633 [],
504 # warnings
505 []
506 ] 634 ]
507 635
508 webtemplatefilters = [] 636 webtemplatefilters = []
509 637
510 webtemplatepats = [ 638 webtemplatepats = [
511 [], 639 [],
512 [ 640 [
513 (r'{desc(\|(?!websub|firstline)[^\|]*)+}', 641 (
514 'follow desc keyword with either firstline or websub'), 642 r'{desc(\|(?!websub|firstline)[^\|]*)+}',
515 ] 643 'follow desc keyword with either firstline or websub',
644 ),
645 ],
516 ] 646 ]
517 647
518 allfilesfilters = [] 648 allfilesfilters = []
519 649
520 allfilespats = [ 650 allfilespats = [
521 [ 651 [
522 (r'(http|https)://[a-zA-Z0-9./]*selenic.com/', 652 (
523 'use mercurial-scm.org domain URL'), 653 r'(http|https)://[a-zA-Z0-9./]*selenic.com/',
524 (r'mercurial@selenic\.com', 654 'use mercurial-scm.org domain URL',
525 'use mercurial-scm.org domain for mercurial ML address'), 655 ),
526 (r'mercurial-devel@selenic\.com', 656 (
527 'use mercurial-scm.org domain for mercurial-devel ML address'), 657 r'mercurial@selenic\.com',
528 ], 658 'use mercurial-scm.org domain for mercurial ML address',
529 # warnings 659 ),
530 [], 660 (
661 r'mercurial-devel@selenic\.com',
662 'use mercurial-scm.org domain for mercurial-devel ML address',
663 ),
664 ],
665 # warnings
666 [],
531 ] 667 ]
532 668
533 py3pats = [ 669 py3pats = [
534 [ 670 [
535 (r'os\.environ', "use encoding.environ instead (py3)", r'#.*re-exports'), 671 (
536 (r'os\.name', "use pycompat.osname instead (py3)"), 672 r'os\.environ',
537 (r'os\.getcwd', "use encoding.getcwd instead (py3)", r'#.*re-exports'), 673 "use encoding.environ instead (py3)",
538 (r'os\.sep', "use pycompat.ossep instead (py3)"), 674 r'#.*re-exports',
539 (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"), 675 ),
540 (r'os\.altsep', "use pycompat.osaltsep instead (py3)"), 676 (r'os\.name', "use pycompat.osname instead (py3)"),
541 (r'sys\.platform', "use pycompat.sysplatform instead (py3)"), 677 (r'os\.getcwd', "use encoding.getcwd instead (py3)", r'#.*re-exports'),
542 (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"), 678 (r'os\.sep', "use pycompat.ossep instead (py3)"),
543 (r'os\.getenv', "use encoding.environ.get instead"), 679 (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"),
544 (r'os\.setenv', "modifying the environ dict is not preferred"), 680 (r'os\.altsep', "use pycompat.osaltsep instead (py3)"),
545 (r'(?<!pycompat\.)xrange', "use pycompat.xrange instead (py3)"), 681 (r'sys\.platform', "use pycompat.sysplatform instead (py3)"),
546 ], 682 (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"),
547 # warnings 683 (r'os\.getenv', "use encoding.environ.get instead"),
548 [], 684 (r'os\.setenv', "modifying the environ dict is not preferred"),
685 (r'(?<!pycompat\.)xrange', "use pycompat.xrange instead (py3)"),
686 ],
687 # warnings
688 [],
549 ] 689 ]
550 690
551 checks = [ 691 checks = [
552 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats), 692 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
553 ('python', r'.*\.(py|cgi)$', r'^#!.*python', [], pynfpats), 693 ('python', r'.*\.(py|cgi)$', r'^#!.*python', [], pynfpats),
554 ('python', r'.*hgext.*\.py$', '', [], pyextnfpats), 694 ('python', r'.*hgext.*\.py$', '', [], pyextnfpats),
555 ('python 3', r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py', 695 (
556 '', pyfilters, py3pats), 696 'python 3',
697 r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
698 '',
699 pyfilters,
700 py3pats,
701 ),
557 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats), 702 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
558 ('c', r'.*\.[ch]$', '', cfilters, cpats), 703 ('c', r'.*\.[ch]$', '', cfilters, cpats),
559 ('unified test', r'.*\.t$', '', utestfilters, utestpats), 704 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
560 ('layering violation repo in revlog', r'mercurial/revlog\.py', '', 705 (
561 pyfilters, inrevlogpats), 706 'layering violation repo in revlog',
562 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters, 707 r'mercurial/revlog\.py',
563 inutilpats), 708 '',
709 pyfilters,
710 inrevlogpats,
711 ),
712 (
713 'layering violation ui in util',
714 r'mercurial/util\.py',
715 '',
716 pyfilters,
717 inutilpats,
718 ),
564 ('txt', r'.*\.txt$', '', txtfilters, txtpats), 719 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
565 ('web template', r'mercurial/templates/.*\.tmpl', '', 720 (
566 webtemplatefilters, webtemplatepats), 721 'web template',
567 ('all except for .po', r'.*(?<!\.po)$', '', 722 r'mercurial/templates/.*\.tmpl',
568 allfilesfilters, allfilespats), 723 '',
724 webtemplatefilters,
725 webtemplatepats,
726 ),
727 ('all except for .po', r'.*(?<!\.po)$', '', allfilesfilters, allfilespats),
569 ] 728 ]
570 729
571 # (desc, 730 # (desc,
572 # func to pick up embedded code fragments, 731 # func to pick up embedded code fragments,
573 # list of patterns to convert target files 732 # list of patterns to convert target files
574 # list of patterns to detect errors/warnings) 733 # list of patterns to detect errors/warnings)
575 embeddedchecks = [ 734 embeddedchecks = [
576 ('embedded python', 735 (
577 testparseutil.pyembedded, embeddedpyfilters, embeddedpypats) 736 'embedded python',
578 ] 737 testparseutil.pyembedded,
738 embeddedpyfilters,
739 embeddedpypats,
740 )
741 ]
742
579 743
580 def _preparepats(): 744 def _preparepats():
581 def preparefailandwarn(failandwarn): 745 def preparefailandwarn(failandwarn):
582 for pats in failandwarn: 746 for pats in failandwarn:
583 for i, pseq in enumerate(pats): 747 for i, pseq in enumerate(pats):
602 failandwarn = c[-1] 766 failandwarn = c[-1]
603 preparefailandwarn(failandwarn) 767 preparefailandwarn(failandwarn)
604 768
605 filters = c[-2] 769 filters = c[-2]
606 preparefilters(filters) 770 preparefilters(filters)
771
607 772
608 class norepeatlogger(object): 773 class norepeatlogger(object):
609 def __init__(self): 774 def __init__(self):
610 self._lastseen = None 775 self._lastseen = None
611 776
628 print("%s:%d:" % (fname, lineno)) 793 print("%s:%d:" % (fname, lineno))
629 print(" > %s" % line) 794 print(" > %s" % line)
630 self._lastseen = msgid 795 self._lastseen = msgid
631 print(" " + msg) 796 print(" " + msg)
632 797
798
633 _defaultlogger = norepeatlogger() 799 _defaultlogger = norepeatlogger()
800
634 801
635 def getblame(f): 802 def getblame(f):
636 lines = [] 803 lines = []
637 for l in os.popen('hg annotate -un %s' % f): 804 for l in os.popen('hg annotate -un %s' % f):
638 start, line = l.split(':', 1) 805 start, line = l.split(':', 1)
639 user, rev = start.split() 806 user, rev = start.split()
640 lines.append((line[1:-1], user, rev)) 807 lines.append((line[1:-1], user, rev))
641 return lines 808 return lines
642 809
643 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False, 810
644 blame=False, debug=False, lineno=True): 811 def checkfile(
812 f,
813 logfunc=_defaultlogger.log,
814 maxerr=None,
815 warnings=False,
816 blame=False,
817 debug=False,
818 lineno=True,
819 ):
645 """checks style and portability of a given file 820 """checks style and portability of a given file
646 821
647 :f: filepath 822 :f: filepath
648 :logfunc: function used to report error 823 :logfunc: function used to report error
649 logfunc(filename, linenumber, linecontent, errormessage) 824 logfunc(filename, linenumber, linecontent, errormessage)
671 for name, match, magic, filters, pats in checks: 846 for name, match, magic, filters, pats in checks:
672 if debug: 847 if debug:
673 print(name, f) 848 print(name, f)
674 if not (re.match(match, f) or (magic and re.search(magic, pre))): 849 if not (re.match(match, f) or (magic and re.search(magic, pre))):
675 if debug: 850 if debug:
676 print("Skipping %s for %s it doesn't match %s" % ( 851 print(
677 name, match, f)) 852 "Skipping %s for %s it doesn't match %s" % (name, match, f)
853 )
678 continue 854 continue
679 if "no-" "check-code" in pre: 855 if "no-" "check-code" in pre:
680 # If you're looking at this line, it's because a file has: 856 # If you're looking at this line, it's because a file has:
681 # no- check- code 857 # no- check- code
682 # but the reason to output skipping is to make life for 858 # but the reason to output skipping is to make life for
683 # tests easier. So, instead of writing it with a normal 859 # tests easier. So, instead of writing it with a normal
684 # spelling, we write it with the expected spelling from 860 # spelling, we write it with the expected spelling from
685 # tests/test-check-code.t 861 # tests/test-check-code.t
686 print("Skipping %s it has no-che?k-code (glob)" % f) 862 print("Skipping %s it has no-che?k-code (glob)" % f)
687 return "Skip" # skip checking this file 863 return "Skip" # skip checking this file
688 864
689 fc = _checkfiledata(name, f, pre, filters, pats, context, 865 fc = _checkfiledata(
690 logfunc, maxerr, warnings, blame, debug, lineno) 866 name,
867 f,
868 pre,
869 filters,
870 pats,
871 context,
872 logfunc,
873 maxerr,
874 warnings,
875 blame,
876 debug,
877 lineno,
878 )
691 if fc: 879 if fc:
692 result = False 880 result = False
693 881
694 if f.endswith('.t') and "no-" "check-code" not in pre: 882 if f.endswith('.t') and "no-" "check-code" not in pre:
695 if debug: 883 if debug:
696 print("Checking embedded code in %s" % (f)) 884 print("Checking embedded code in %s" % f)
697 885
698 prelines = pre.splitlines() 886 prelines = pre.splitlines()
699 embeddederros = [] 887 embeddederros = []
700 for name, embedded, filters, pats in embeddedchecks: 888 for name, embedded, filters, pats in embeddedchecks:
701 # "reset curmax at each repetition" treats maxerr as "max 889 # "reset curmax at each repetition" treats maxerr as "max
703 # (embedded)checks" 891 # (embedded)checks"
704 curmaxerr = maxerr 892 curmaxerr = maxerr
705 893
706 for found in embedded(f, prelines, embeddederros): 894 for found in embedded(f, prelines, embeddederros):
707 filename, starts, ends, code = found 895 filename, starts, ends, code = found
708 fc = _checkfiledata(name, f, code, filters, pats, context, 896 fc = _checkfiledata(
709 logfunc, curmaxerr, warnings, blame, debug, 897 name,
710 lineno, offset=starts - 1) 898 f,
899 code,
900 filters,
901 pats,
902 context,
903 logfunc,
904 curmaxerr,
905 warnings,
906 blame,
907 debug,
908 lineno,
909 offset=starts - 1,
910 )
711 if fc: 911 if fc:
712 result = False 912 result = False
713 if curmaxerr: 913 if curmaxerr:
714 if fc >= curmaxerr: 914 if fc >= curmaxerr:
715 break 915 break
716 curmaxerr -= fc 916 curmaxerr -= fc
717 917
718 return result 918 return result
719 919
720 def _checkfiledata(name, f, filedata, filters, pats, context, 920
721 logfunc, maxerr, warnings, blame, debug, lineno, 921 def _checkfiledata(
722 offset=None): 922 name,
923 f,
924 filedata,
925 filters,
926 pats,
927 context,
928 logfunc,
929 maxerr,
930 warnings,
931 blame,
932 debug,
933 lineno,
934 offset=None,
935 ):
723 """Execute actual error check for file data 936 """Execute actual error check for file data
724 937
725 :name: of the checking category 938 :name: of the checking category
726 :f: filepath 939 :f: filepath
727 :filedata: content of a file 940 :filedata: content of a file
750 lineoffset = offset 963 lineoffset = offset
751 964
752 fc = 0 965 fc = 0
753 pre = post = filedata 966 pre = post = filedata
754 967
755 if True: # TODO: get rid of this redundant 'if' block 968 if True: # TODO: get rid of this redundant 'if' block
756 for p, r in filters: 969 for p, r in filters:
757 post = re.sub(p, r, post) 970 post = re.sub(p, r, post)
758 nerrs = len(pats[0]) # nerr elements are errors 971 nerrs = len(pats[0]) # nerr elements are errors
759 if warnings: 972 if warnings:
760 pats = pats[0] + pats[1] 973 pats = pats[0] + pats[1]
761 else: 974 else:
762 pats = pats[0] 975 pats = pats[0]
763 # print post # uncomment to show filtered version 976 # print post # uncomment to show filtered version
792 n += 1 1005 n += 1
793 l = prelines[n] 1006 l = prelines[n]
794 1007
795 if ignore and re.search(ignore, l, re.MULTILINE): 1008 if ignore and re.search(ignore, l, re.MULTILINE):
796 if debug: 1009 if debug:
797 print("Skipping %s for %s:%s (ignore pattern)" % ( 1010 print(
798 name, f, (n + lineoffset))) 1011 "Skipping %s for %s:%s (ignore pattern)"
1012 % (name, f, (n + lineoffset))
1013 )
799 continue 1014 continue
800 bd = "" 1015 bd = ""
801 if blame: 1016 if blame:
802 bd = 'working directory' 1017 bd = 'working directory'
803 if blamecache is None: 1018 if blamecache is None:
828 print(" (too many errors, giving up)") 1043 print(" (too many errors, giving up)")
829 break 1044 break
830 1045
831 return fc 1046 return fc
832 1047
1048
833 def main(): 1049 def main():
834 parser = optparse.OptionParser("%prog [options] [files | -]") 1050 parser = optparse.OptionParser("%prog [options] [files | -]")
835 parser.add_option("-w", "--warnings", action="store_true", 1051 parser.add_option(
836 help="include warning-level checks") 1052 "-w",
837 parser.add_option("-p", "--per-file", type="int", 1053 "--warnings",
838 help="max warnings per file") 1054 action="store_true",
839 parser.add_option("-b", "--blame", action="store_true", 1055 help="include warning-level checks",
840 help="use annotate to generate blame info") 1056 )
841 parser.add_option("", "--debug", action="store_true", 1057 parser.add_option(
842 help="show debug information") 1058 "-p", "--per-file", type="int", help="max warnings per file"
843 parser.add_option("", "--nolineno", action="store_false", 1059 )
844 dest='lineno', help="don't show line numbers") 1060 parser.add_option(
845 1061 "-b",
846 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False, 1062 "--blame",
847 lineno=True) 1063 action="store_true",
1064 help="use annotate to generate blame info",
1065 )
1066 parser.add_option(
1067 "", "--debug", action="store_true", help="show debug information"
1068 )
1069 parser.add_option(
1070 "",
1071 "--nolineno",
1072 action="store_false",
1073 dest='lineno',
1074 help="don't show line numbers",
1075 )
1076
1077 parser.set_defaults(
1078 per_file=15, warnings=False, blame=False, debug=False, lineno=True
1079 )
848 (options, args) = parser.parse_args() 1080 (options, args) = parser.parse_args()
849 1081
850 if len(args) == 0: 1082 if len(args) == 0:
851 check = glob.glob("*") 1083 check = glob.glob("*")
852 elif args == ['-']: 1084 elif args == ['-']:
857 1089
858 _preparepats() 1090 _preparepats()
859 1091
860 ret = 0 1092 ret = 0
861 for f in check: 1093 for f in check:
862 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings, 1094 if not checkfile(
863 blame=options.blame, debug=options.debug, 1095 f,
864 lineno=options.lineno): 1096 maxerr=options.per_file,
1097 warnings=options.warnings,
1098 blame=options.blame,
1099 debug=options.debug,
1100 lineno=options.lineno,
1101 ):
865 ret = 1 1102 ret = 1
866 return ret 1103 return ret
867 1104
1105
868 if __name__ == "__main__": 1106 if __name__ == "__main__":
869 sys.exit(main()) 1107 sys.exit(main())