Mercurial > public > mercurial-scm > hg
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()) |