Left: | ||
Right: |
LEFT | RIGHT |
---|---|
1 # This file is part of Adblock Plus <https://adblockplus.org/>, | 1 # This file is part of Adblock Plus <https://adblockplus.org/>, |
2 # Copyright (C) 2006-2016 Eyeo GmbH | 2 # Copyright (C) 2006-2016 Eyeo GmbH |
3 # | 3 # |
4 # Adblock Plus is free software: you can redistribute it and/or modify | 4 # Adblock Plus is free software: you can redistribute it and/or modify |
5 # it under the terms of the GNU General Public License version 3 as | 5 # it under the terms of the GNU General Public License version 3 as |
6 # published by the Free Software Foundation. | 6 # published by the Free Software Foundation. |
7 # | 7 # |
8 # Adblock Plus is distributed in the hope that it will be useful, | 8 # Adblock Plus is distributed in the hope that it will be useful, |
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of | 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 # GNU General Public License for more details. | 11 # GNU General Public License for more details. |
12 # | 12 # |
13 # You should have received a copy of the GNU General Public License | 13 # You should have received a copy of the GNU General Public License |
14 # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. | 14 # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 |
16 import ast | 16 import ast |
17 import re | 17 import re |
18 import tokenize | 18 import tokenize |
19 import sys | 19 import sys |
20 import collections | 20 import collections |
21 | 21 |
22 try: | 22 try: |
23 import builtins | 23 import builtins |
24 except ImportError: | 24 except ImportError: |
25 import __builtin__ as builtins | 25 import __builtin__ as builtins |
26 | 26 |
27 import pkg_resources | 27 import pkg_resources |
28 | 28 |
29 try: | |
30 ascii | |
31 except NameError: | |
32 ascii = repr | |
33 | |
29 __version__ = pkg_resources.get_distribution('flake8-abp').version | 34 __version__ = pkg_resources.get_distribution('flake8-abp').version |
30 | 35 |
31 DISCOURAGED_APIS = { | 36 DISCOURAGED_APIS = { |
32 're.match': 're.search', | 37 're.match': 're.search', |
33 'codecs.open': 'io.open', | 38 'codecs.open': 'io.open', |
34 } | 39 } |
35 | 40 |
36 ESSENTIAL_BUILTINS = set(dir(builtins)) - {'apply', 'basestring', 'buffer', | 41 ESSENTIAL_BUILTINS = set(dir(builtins)) - {'apply', 'buffer', 'coerce', |
Vasily Kuznetsov
2016/05/09 13:39:51
What were the criteria for deciding what is an ess
Sebastian Noack
2016/05/09 16:49:11
The names explicitly excluded were the builtins th
Vasily Kuznetsov
2016/05/09 17:37:32
Yep, makes sense. It would be good to record it so
Sebastian Noack
2016/05/11 10:50:09
I think the general rule for the style guide shoul
Vasily Kuznetsov
2016/05/11 14:09:17
Ok, I think saying that builtins should be overrid
| |
37 'cmp', 'coerce', 'execfile', | 42 'intern', 'file'} |
38 'file', 'intern', 'long', | |
39 'raw_input', 'reduce', 'reload', | |
40 'unichr', 'unicode', 'xrange'} | |
41 | 43 |
42 LEAVE_BLOCK = (ast.Return, ast.Raise, ast.Continue, ast.Break) | 44 LEAVE_BLOCK = (ast.Return, ast.Raise, ast.Continue, ast.Break) |
43 VOLATILE = object() | 45 VOLATILE = object() |
44 | 46 |
45 | 47 |
46 def evaluate(node): | 48 def evaluate(node): |
47 try: | 49 try: |
48 return eval(compile(ast.Expression(node), '', 'eval'), {}) | 50 return eval(compile(ast.Expression(node), '', 'eval'), {}) |
49 except Exception: | 51 except Exception: |
Vasily Kuznetsov
2016/05/09 13:39:51
As I understand this exception would be thrown whe
Sebastian Noack
2016/05/09 16:49:11
Yes, it can be one of a wide range (if not any) ex
Vasily Kuznetsov
2016/05/09 17:37:32
I see. I just got a bit confused by this being cal
| |
50 return VOLATILE | 52 return VOLATILE |
51 | 53 |
52 | 54 |
53 def is_const(node): | 55 def is_const(node): |
54 return evaluate(node) is not VOLATILE | 56 return evaluate(node) is not VOLATILE |
55 | 57 |
56 | 58 |
57 def get_identifier(node): | 59 def get_identifier(node): |
58 if isinstance(node, ast.Name): | 60 if isinstance(node, ast.Name): |
59 return node.id | 61 return node.id |
60 if isinstance(node, ast.Attribute) and isinstance(node.value, ast.Name): | 62 if isinstance(node, ast.Attribute) and isinstance(node.value, ast.Name): |
61 return '{}.{}'.format(node.value.id, node.attr) | 63 return '{}.{}'.format(node.value.id, node.attr) |
62 | 64 |
63 | 65 |
64 def get_statement(node): | 66 def get_statement(node): |
65 return type(node).__name__.lower() | 67 return type(node).__name__.lower() |
66 | 68 |
67 | 69 |
68 class TreeVisitor(ast.NodeVisitor): | 70 class TreeVisitor(ast.NodeVisitor): |
69 Scope = collections.namedtuple('Scope', ['node', 'names', 'globals']) | 71 Scope = collections.namedtuple('Scope', ['node', 'names', 'globals']) |
70 | 72 |
71 def __init__(self): | 73 def __init__(self): |
72 self.errors = [] | 74 self.errors = [] |
73 self.scope_stack = [] | 75 self.scope_stack = [] |
74 | 76 |
75 def _visit_block(self, nodes, block_required=False, | 77 def _visit_block(self, nodes, block_required=False, |
76 nodes_required=True, docstring=False): | 78 nodes_required=True, docstring=False, |
79 can_have_unused_expr=False): | |
77 pass_node = None | 80 pass_node = None |
78 has_non_pass = False | 81 has_non_pass = False |
79 leave_node = None | 82 leave_node = None |
80 dead_code = False | 83 dead_code = False |
81 | 84 |
82 for i, node in enumerate(nodes): | 85 for i, node in enumerate(nodes): |
83 if isinstance(node, ast.Pass): | 86 if isinstance(node, ast.Pass): |
84 pass_node = node | 87 pass_node = node |
85 else: | 88 else: |
86 has_non_pass = True | 89 has_non_pass = True |
87 | 90 |
88 if leave_node and not dead_code: | 91 if leave_node and not dead_code: |
89 dead_code = True | 92 dead_code = True |
90 statement = get_statement(leave_node) | 93 statement = get_statement(leave_node) |
91 self.errors.append((node, 'A202 dead code after ' | 94 self.errors.append((node, 'A202 dead code after ' |
92 '{}'.format(statement))) | 95 '{}'.format(statement))) |
93 | 96 |
94 if isinstance(node, LEAVE_BLOCK): | 97 if isinstance(node, LEAVE_BLOCK): |
95 leave_node = node | 98 leave_node = node |
96 | 99 |
97 if not isinstance(node, ast.Expr): | 100 if can_have_unused_expr or not isinstance(node, ast.Expr): |
98 continue | 101 continue |
99 if docstring and i == 0 and isinstance(node.value, ast.Str): | 102 if docstring and i == 0 and isinstance(node.value, ast.Str): |
100 continue | 103 continue |
101 if isinstance(node.value, (ast.Call, ast.Yield)): | 104 if isinstance(node.value, (ast.Call, ast.Yield)): |
102 continue | 105 continue |
103 | 106 |
104 self.errors.append((node, 'A203 unused expression')) | 107 self.errors.append((node, 'A203 unused expression')) |
105 | 108 |
106 if pass_node: | 109 if pass_node: |
107 if not nodes_required or len(nodes) > 1: | 110 if not nodes_required or len(nodes) > 1: |
(...skipping 10 matching lines...) Expand all Loading... | |
118 for handler in handlers: | 121 for handler in handlers: |
119 for child in handler.body: | 122 for child in handler.body: |
120 if isinstance(child, LEAVE_BLOCK): | 123 if isinstance(child, LEAVE_BLOCK): |
121 leave_node = child | 124 leave_node = child |
122 break | 125 break |
123 else: | 126 else: |
124 return | 127 return |
125 | 128 |
126 statement = get_statement(leave_node) | 129 statement = get_statement(leave_node) |
127 self.errors.append((node.orelse[0], | 130 self.errors.append((node.orelse[0], |
128 'A206 redundant else statement after {} ' | 131 'A206 Extraneous else statement after {} ' |
129 'in {}-clause'.format(statement, clause))) | 132 'in {}-clause'.format(statement, clause))) |
130 | 133 |
131 def visit_If(self, node): | 134 def visit_If(self, node): |
132 self._visit_block(node.body, block_required=bool(node.orelse)) | 135 self._visit_block(node.body, block_required=bool(node.orelse)) |
133 self._visit_block(node.orelse) | 136 self._visit_block(node.orelse) |
134 self._check_redundant_else(node, [node], 'if') | 137 self._check_redundant_else(node, [node], 'if') |
135 self.generic_visit(node) | 138 self.generic_visit(node) |
136 | 139 |
137 def visit_Try(self, node): | 140 def visit_Try(self, node): |
138 self._visit_block(node.body) | 141 self._visit_block(node.body, can_have_unused_expr=bool(node.handlers)) |
139 self._visit_block(node.orelse) | 142 self._visit_block(node.orelse) |
140 self._visit_block(node.finalbody) | 143 self._visit_block(node.finalbody) |
141 self._check_redundant_else(node, node.handlers, 'except') | 144 self._check_redundant_else(node, node.handlers, 'except') |
142 self.generic_visit(node) | 145 self.generic_visit(node) |
143 | 146 |
144 def visit_TryExcept(self, node): | 147 def visit_TryExcept(self, node): |
145 self._visit_block(node.body) | 148 self._visit_block(node.body, can_have_unused_expr=True) |
146 self._visit_block(node.orelse) | 149 self._visit_block(node.orelse) |
147 self._check_redundant_else(node, node.handlers, 'except') | 150 self._check_redundant_else(node, node.handlers, 'except') |
148 self.generic_visit(node) | 151 self.generic_visit(node) |
149 | 152 |
150 def visit_TryFinally(self, node): | 153 def visit_TryFinally(self, node): |
151 self._visit_block(node.body) | 154 self._visit_block(node.body) |
152 self._visit_block(node.finalbody) | 155 self._visit_block(node.finalbody) |
153 self.generic_visit(node) | 156 self.generic_visit(node) |
154 | 157 |
155 def visit_ExceptHandler(self, node): | 158 def visit_ExceptHandler(self, node): |
156 self._visit_block(node.body, block_required=True) | 159 self._visit_block(node.body, block_required=True) |
157 self.generic_visit(node) | 160 self.generic_visit(node) |
158 | 161 |
159 def _visit_stored_name(self, node, name): | 162 def _visit_stored_name(self, node, name): |
160 scope = self.scope_stack[-1] | 163 scope = self.scope_stack[-1] |
161 scope.names.add(name) | 164 scope.names.add(name) |
162 | 165 |
163 if name in ESSENTIAL_BUILTINS and not isinstance(scope.node, | 166 if name in ESSENTIAL_BUILTINS and isinstance(scope.node, |
164 ast.ClassDef): | 167 ast.FunctionDef): |
165 self.errors.append((node, 'A302 redefined built-in ' + name)) | 168 self.errors.append((node, 'A302 redefined built-in ' + name)) |
166 | 169 |
167 def visit_Name(self, node): | 170 def visit_Name(self, node): |
168 if isinstance(node.ctx, ast.Store): | 171 if isinstance(node.ctx, (ast.Store, ast.Param)): |
169 self._visit_stored_name(node, node.id) | 172 self._visit_stored_name(node, node.id) |
173 | |
174 def visit_arg(self, node): | |
175 self._visit_stored_name(node, node.arg) | |
170 | 176 |
171 def _visit_with_scope(self, node): | 177 def _visit_with_scope(self, node): |
172 scope = self.Scope(node, names=set(), globals=[]) | 178 scope = self.Scope(node, names=set(), globals=[]) |
173 self.scope_stack.append(scope) | 179 self.scope_stack.append(scope) |
174 self.generic_visit(node) | 180 self.generic_visit(node) |
175 del self.scope_stack[-1] | 181 del self.scope_stack[-1] |
176 return scope | 182 return scope |
177 | 183 |
178 def visit_Module(self, node): | 184 def visit_Module(self, node): |
179 self._visit_block(node.body, block_required=True, | 185 self._visit_block(node.body, block_required=True, |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
236 '% operator for string formatting')) | 242 '% operator for string formatting')) |
237 | 243 |
238 multi_addition = (isinstance(node.op, ast.Add) and | 244 multi_addition = (isinstance(node.op, ast.Add) and |
239 isinstance(node.left, ast.BinOp) and | 245 isinstance(node.left, ast.BinOp) and |
240 isinstance(node.left.op, ast.Add)) | 246 isinstance(node.left.op, ast.Add)) |
241 if multi_addition and (isinstance(node.left.left, ast.Str) or | 247 if multi_addition and (isinstance(node.left.left, ast.Str) or |
242 isinstance(node.left.right, ast.Str) or | 248 isinstance(node.left.right, ast.Str) or |
243 isinstance(node.right, ast.Str)): | 249 isinstance(node.right, ast.Str)): |
244 self.errors.append((node, 'A108 use format() instead of ' | 250 self.errors.append((node, 'A108 use format() instead of ' |
245 '+ operator when concatenating ' | 251 '+ operator when concatenating ' |
246 '>2 strings')) | 252 'more than two strings')) |
Vasily Kuznetsov
2016/05/09 13:39:51
Maybe write it out: "more than two strings"? It's
Sebastian Noack
2016/05/09 16:49:11
I had it spelled out initially, but the message is
Vasily Kuznetsov
2016/05/09 17:37:32
I see. I don't feel too strongly about it, so up t
| |
247 | 253 |
248 self.generic_visit(node) | 254 self.generic_visit(node) |
249 | 255 |
250 def visit_Compare(self, node): | 256 def visit_Compare(self, node): |
251 left = node.left | 257 left = node.left |
252 single = len(node.ops) == 1 | 258 single = len(node.ops) == 1 |
253 | 259 |
254 for op, right in zip(node.ops, node.comparators): | 260 for op, right in zip(node.ops, node.comparators): |
255 membership = isinstance(op, (ast.In, ast.NotIn)) | 261 membership = isinstance(op, (ast.In, ast.NotIn)) |
256 symmetric = isinstance(op, (ast.Eq, ast.NotEq, ast.Is, ast.IsNot)) | 262 symmetric = isinstance(op, (ast.Eq, ast.NotEq, ast.Is, ast.IsNot)) |
257 | 263 |
258 if membership and isinstance(right, (ast.Tuple, ast.List)): | 264 if membership and isinstance(right, (ast.Tuple, ast.List)): |
259 self.errors.append((right, 'A102 use sets for distinct ' | 265 self.errors.append((right, 'A102 use sets for distinct ' |
260 'unordered data')) | 266 'unordered data')) |
261 | 267 |
262 consts_first = single and not membership or symmetric | 268 consts_first = single and not membership or symmetric |
263 if consts_first and is_const(left) and not is_const(right): | 269 if consts_first and is_const(left) and not is_const(right): |
264 self.errors.append((left, 'A103 yoda condition')) | 270 self.errors.append((left, 'A103 yoda condition')) |
265 | 271 |
266 left = right | 272 left = right |
267 | 273 |
268 self.generic_visit(node) | 274 self.generic_visit(node) |
269 | 275 |
270 def _check_deprecated(self, node, name): | 276 def _check_deprecated(self, node, name): |
271 substitute = DISCOURAGED_APIS.get(name) | 277 substitute = DISCOURAGED_APIS.get(name) |
272 if substitute: | 278 if substitute: |
273 self.errors.append((node, 'A301 use {}() instead ' | 279 self.errors.append((node, 'A301 use {}() instead of ' |
Vasily Kuznetsov
2016/05/09 13:39:51
instead -> instead of
Sebastian Noack
2016/05/09 16:49:12
Done.
| |
274 '{}()'.format(substitute, name))) | 280 '{}()'.format(substitute, name))) |
275 | 281 |
276 def visit_Call(self, node): | 282 def visit_Call(self, node): |
277 func = get_identifier(node.func) | 283 func = get_identifier(node.func) |
278 arg = next(iter(node.args), None) | 284 arg = next(iter(node.args), None) |
279 redundant_literal = False | 285 redundant_literal = False |
280 | 286 |
281 if isinstance(arg, ast.Lambda) and func in {'map', 'filter', | 287 if isinstance(arg, ast.Lambda) and func in {'map', 'filter', |
282 'imap', 'ifilter', | 288 'imap', 'ifilter', |
283 'itertools.imap', | 289 'itertools.imap', |
284 'itertools.ifilter'}: | 290 'itertools.ifilter'}: |
285 self.errors.append((node, 'A104 use a comprehension ' | 291 self.errors.append((node, 'A104 use a comprehension ' |
286 'instead calling {}() with ' | 292 'instead of calling {}() with ' |
Vasily Kuznetsov
2016/05/09 13:39:50
instead -> instead of
Sebastian Noack
2016/05/09 16:49:11
Done.
| |
287 'lambda function'.format(func))) | 293 'lambda function'.format(func))) |
288 elif isinstance(arg, (ast.List, ast.Tuple)): | 294 elif isinstance(arg, (ast.List, ast.Tuple)): |
289 if func == 'dict': | 295 if func == 'dict': |
290 redundant_literal = all(isinstance(elt, (ast.Tuple, ast.List)) | 296 redundant_literal = all(isinstance(elt, (ast.Tuple, ast.List)) |
291 for elt in arg.elts) | 297 for elt in arg.elts) |
292 else: | 298 else: |
293 redundant_literal = func in {'list', 'set', 'tuple'} | 299 redundant_literal = func in {'list', 'set', 'tuple'} |
294 elif isinstance(arg, (ast.ListComp, ast.GeneratorExp)): | 300 elif isinstance(arg, (ast.ListComp, ast.GeneratorExp)): |
295 if func == 'dict': | 301 if func == 'dict': |
296 redundant_literal = isinstance(arg.elt, (ast.Tuple, ast.List)) | 302 redundant_literal = isinstance(arg.elt, (ast.Tuple, ast.List)) |
297 else: | 303 else: |
298 redundant_literal = func in {'list', 'set'} | 304 redundant_literal = func in {'list', 'set'} |
299 | 305 |
300 if redundant_literal: | 306 if redundant_literal: |
301 self.errors.append((node, 'A105 use a {0} literal or ' | 307 self.errors.append((node, 'A105 use a {0} literal or ' |
302 'comprehension instead calling ' | 308 'comprehension instead of calling ' |
Vasily Kuznetsov
2016/05/09 13:39:51
instead -> instead of
Sebastian Noack
2016/05/09 16:49:11
Done.
| |
303 '{0}()'.format(func))) | 309 '{0}()'.format(func))) |
304 | 310 |
305 self._check_deprecated(node, func) | 311 self._check_deprecated(node, func) |
306 self.generic_visit(node) | 312 self.generic_visit(node) |
307 | 313 |
308 def visit_Import(self, node): | 314 def visit_Import(self, node): |
309 for alias in node.names: | 315 for alias in node.names: |
310 self._visit_stored_name(node, alias.asname or alias.name) | 316 self._visit_stored_name(node, alias.asname or alias.name) |
311 | 317 |
312 if hasattr(node, 'module'): | 318 if hasattr(node, 'module'): |
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
391 yield (start, "A109 don't use u'', b'' " | 397 yield (start, "A109 don't use u'', b'' " |
392 "or r'' for doc strings") | 398 "or r'' for doc strings") |
393 elif start[0] == end[0]: | 399 elif start[0] == end[0]: |
394 if is_raw: | 400 if is_raw: |
395 literal = re.sub(r'\\(?!{})'.format(literal[0]), | 401 literal = re.sub(r'\\(?!{})'.format(literal[0]), |
396 '\\\\\\\\', literal) | 402 '\\\\\\\\', literal) |
397 | 403 |
398 if sys.version_info[0] >= 3: | 404 if sys.version_info[0] >= 3: |
399 if is_bytes: | 405 if is_bytes: |
400 literal = 'b' + literal | 406 literal = 'b' + literal |
401 else: | |
402 literal = re.sub(r'(?<!\\)\\x(?!a[0d])([a-f][0-9a-f])', | |
403 lambda m: chr(int(m.group(1), 16)), | |
404 literal) | |
405 elif is_unicode: | 407 elif is_unicode: |
406 literal = 'u' + literal | 408 literal = 'u' + literal |
407 | 409 |
408 if repr(eval(literal)) != literal: | 410 if ascii(eval(literal)) != literal: |
409 yield (start, "A110 string literal doesn't match repr()") | 411 yield (start, "A110 string literal doesn't match" |
Vasily Kuznetsov
2016/05/09 13:39:51
Maybe a bit clearer: "string literal doesn't match
Sebastian Noack
2016/05/09 16:49:12
I think that would be inaccurate. A string (litera
Vasily Kuznetsov
2016/05/09 17:37:32
So is it about the difference between a string and
Sebastian Noack
2016/05/11 10:50:09
Yeah, I disagree. But also note that on Python 3 t
Vasily Kuznetsov
2016/05/11 14:09:17
Yeah, I agree with you that the proposed Python 3
| |
412 '{}()'.format(ascii.__name__)) | |
410 | 413 |
411 first_token = False | 414 first_token = False |
412 | 415 |
413 check_quotes.name = 'abp-quotes' | 416 check_quotes.name = 'abp-quotes' |
414 check_quotes.version = __version__ | 417 check_quotes.version = __version__ |
415 | 418 |
416 | 419 |
417 def check_redundant_parenthesis(logical_line, tokens): | 420 def check_redundant_parenthesis(logical_line, tokens): |
418 start_line = tokens[0][2][0] | 421 start_line = tokens[0][2][0] |
419 level = 0 | 422 level = 0 |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
459 if tokens[i + 1][:2] != (tokenize.OP, ':'): | 462 if tokens[i + 1][:2] != (tokenize.OP, ':'): |
460 break | 463 break |
461 | 464 |
462 return [(pos, 'A111 redundant parenthesis for {} ' | 465 return [(pos, 'A111 redundant parenthesis for {} ' |
463 'statement'.format(statement))] | 466 'statement'.format(statement))] |
464 | 467 |
465 return [] | 468 return [] |
466 | 469 |
467 check_redundant_parenthesis.name = 'abp-redundant-parenthesis' | 470 check_redundant_parenthesis.name = 'abp-redundant-parenthesis' |
468 check_redundant_parenthesis.version = __version__ | 471 check_redundant_parenthesis.version = __version__ |
LEFT | RIGHT |