Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Delta Between Two Patch Sets: flake8-abp/flake8_abp.py

Issue 29340727: Noissue - Added flake8 extension accounting for our coding style and some other stuff (Closed)
Left Patch Set: Added more checks, tests, README and addressed comments Created May 7, 2016, 5:13 p.m.
Right Patch Set: Addressed comments, fixed two bugs, use ascii() Created May 9, 2016, 4:47 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « flake8-abp/README.md ('k') | flake8-abp/setup.py » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
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
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
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
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
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__
LEFTRIGHT

Powered by Google App Engine
This is Rietveld