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

Delta Between Two Patch Sets: flake8-eyeo/flake8_eyeo.py

Issue 29565854: Noissue - Improved accuracy of evaluated expressions for A103 and A207 (Closed)
Left Patch Set: Created Oct. 5, 2017, 9:41 p.m.
Right Patch Set: Changed behavior of A207 Created Oct. 11, 2017, 6:25 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 | « no previous file | flake8-eyeo/tests/A103.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-present eyeo GmbH 2 # Copyright (C) 2006-present 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 import flake8
29 28
30 try: 29 try:
31 ascii 30 ascii
32 except NameError: 31 except NameError:
33 ascii = repr 32 ascii = repr
34 33
35 __version__ = pkg_resources.get_distribution('flake8-eyeo').version 34 __version__ = pkg_resources.get_distribution('flake8-eyeo').version
36 35
37 DISCOURAGED_APIS = { 36 DISCOURAGED_APIS = {
38 're.match': 're.search', 37 're.match': 're.search',
39 'codecs.open': 'io.open', 38 'codecs.open': 'io.open',
40 } 39 }
41 40
42 ESSENTIAL_BUILTINS = set(dir(builtins)) - {'apply', 'buffer', 'coerce', 41 ESSENTIAL_BUILTINS = set(dir(builtins)) - {'apply', 'buffer', 'coerce',
43 'intern', 'file'} 42 'intern', 'file'}
44 43
45 LEAVE_BLOCK = (ast.Return, ast.Raise, ast.Continue, ast.Break) 44 LEAVE_BLOCK = (ast.Return, ast.Raise, ast.Continue, ast.Break)
46 VOLATILE = object() 45 VOLATILE = object()
47 46
48 47
49 def evaluate(node): 48 def evaluate(node, namespace):
50 names = {'__builtins__': {'True': True, 'False': False, 'None': None}}
tlucas 2017/10/06 11:41:36 Why are these values necessary? From what i under
Sebastian Noack 2017/10/06 18:11:09 True, False and None are special. They are the onl
51 try: 49 try:
52 return eval(compile(ast.Expression(node), '', 'eval'), names) 50 return eval(compile(ast.Expression(node), '', 'eval'), namespace)
53 except Exception: 51 except Exception:
54 return VOLATILE 52 return VOLATILE
55 53
56 54
57 def is_const(node): 55 def is_const(node):
58 return evaluate(node) is not VOLATILE 56 namespace = {'__builtins__': {'True': True, 'False': False, 'None': None}}
57 return evaluate(node, namespace) is not VOLATILE
59 58
60 59
61 def get_identifier(node): 60 def get_identifier(node):
62 if isinstance(node, ast.Name): 61 if isinstance(node, ast.Name):
63 return node.id 62 return node.id
64 if isinstance(node, ast.Attribute) and isinstance(node.value, ast.Name): 63 if isinstance(node, ast.Attribute) and isinstance(node.value, ast.Name):
65 return '{}.{}'.format(node.value.id, node.attr) 64 return '{}.{}'.format(node.value.id, node.attr)
66 65
67 66
68 def get_statement(node): 67 def get_statement(node):
(...skipping 261 matching lines...) Expand 10 before | Expand all | Expand 10 after
330 left_is_target = (isinstance(target, ast.Name) and 329 left_is_target = (isinstance(target, ast.Name) and
331 isinstance(node.value.left, ast.Name) and 330 isinstance(node.value.left, ast.Name) and
332 target.id == node.value.left.id) 331 target.id == node.value.left.id)
333 if left_is_target: 332 if left_is_target:
334 self.errors.append((node, 'A106 use augment assignment, ' 333 self.errors.append((node, 'A106 use augment assignment, '
335 'e.g. x += y instead x = x + y')) 334 'e.g. x += y instead x = x + y'))
336 self.generic_visit(node) 335 self.generic_visit(node)
337 336
338 def _visit_hash_keys(self, nodes, what): 337 def _visit_hash_keys(self, nodes, what):
339 keys = [] 338 keys = []
339 namespace = collections.defaultdict(object, vars(builtins))
Vasily Kuznetsov 2017/10/11 19:29:02 This is clever!
340 for node in nodes: 340 for node in nodes:
341 key = evaluate(node) 341 key = evaluate(node, namespace)
342 if key is VOLATILE: 342 if key is VOLATILE:
343 continue 343 continue
344 344
345 if key in keys: 345 if key in keys:
346 self.errors.append((node, 'A207 duplicate ' + what)) 346 self.errors.append((node, 'A207 duplicate ' + what))
347 continue 347 continue
348 348
349 keys.append(key) 349 keys.append(key)
350 350
351 def visit_Dict(self, node): 351 def visit_Dict(self, node):
352 self._visit_hash_keys(node.keys, 'key in dict') 352 self._visit_hash_keys(node.keys, 'key in dict')
353 353
354 def visit_Set(self, node): 354 def visit_Set(self, node):
355 self._visit_hash_keys(node.elts, 'item in set') 355 self._visit_hash_keys(node.elts, 'item in set')
356 356
357 357
358 class ASTChecker(object): 358 def check_ast(tree):
359 def __init__(self, tree, filename): 359 visitor = TreeVisitor()
360 self.tree = tree 360 visitor.visit(tree)
361 361
362 def run(self): 362 for node, error in visitor.errors:
363 visitor = TreeVisitor() 363 yield (node.lineno, node.col_offset, error, None)
364 visitor.visit(self.tree)
365
366 for node, error in visitor.errors:
367 yield (node.lineno, node.col_offset, error, type(self))
368 364
369 365
370 def check_non_default_encoding(physical_line, line_number): 366 def check_non_default_encoding(physical_line, line_number):
371 if line_number <= 2 and re.search(r'^\s*#.*coding[:=]', physical_line): 367 if line_number <= 2 and re.search(r'^\s*#.*coding[:=]', physical_line):
372 return (0, 'A303 non-default file encoding') 368 return (0, 'A303 non-default file encoding')
373 369
374 370
375 def check_quotes(logical_line, tokens, previous_logical, checker_state): 371 def check_quotes(logical_line, tokens, previous_logical, checker_state):
376 first_token = True 372 first_token = True
377 373
(...skipping 13 matching lines...) Expand all
391 prefixes, quote, text = match.groups() 387 prefixes, quote, text = match.groups()
392 prefixes = prefixes.lower() 388 prefixes = prefixes.lower()
393 389
394 if 'u' in prefixes: 390 if 'u' in prefixes:
395 yield (start, 'A112 use "from __future__ import ' 391 yield (start, 'A112 use "from __future__ import '
396 'unicode_literals" instead of ' 392 'unicode_literals" instead of '
397 'prefixing literals with "u"') 393 'prefixing literals with "u"')
398 394
399 if first_token and re.search(r'^(?:(?:def|class)\s|$)', 395 if first_token and re.search(r'^(?:(?:def|class)\s|$)',
400 previous_logical): 396 previous_logical):
401 if quote != '"""': 397 pass # Ignore docstrings
402 yield (start, 'A109 use triple double '
403 'quotes for docstrings')
404 elif start[0] != end[0]: 398 elif start[0] != end[0]:
405 pass 399 pass # Ignore multiline strings
406 elif 'r' in prefixes: 400 elif 'r' in prefixes:
407 if quote != "'" and not (quote == '"' and "'" in text): 401 if quote != "'" and not (quote == '"' and "'" in text):
408 yield (start, 'A110 use single quotes for raw string') 402 yield (start, 'A110 use single quotes for raw string')
409 else: 403 else:
410 prefix = '' 404 prefix = ''
411 if sys.version_info[0] >= 3: 405 if sys.version_info[0] >= 3:
412 if 'b' in prefixes: 406 if 'b' in prefixes:
413 prefix = 'b' 407 prefix = 'b'
414 else: 408 else:
415 u_literals = checker_state.get('has_unicode_literals') 409 u_literals = checker_state.get('has_unicode_literals')
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
468 # outer parenthesis closed before end of expression 462 # outer parenthesis closed before end of expression
469 if tokens[i + 1][:2] != (tokenize.OP, ':'): 463 if tokens[i + 1][:2] != (tokenize.OP, ':'):
470 break 464 break
471 465
472 return [(pos, 'A111 redundant parenthesis for {} ' 466 return [(pos, 'A111 redundant parenthesis for {} '
473 'statement'.format(statement))] 467 'statement'.format(statement))]
474 468
475 return [] 469 return []
476 470
477 471
478 # With flake8 3, the way the entry points are register in setup.py, 472 for checker in [check_ast, check_non_default_encoding,
479 # they are recognized as a group, and the name and version is detected 473 check_quotes, check_redundant_parenthesis]:
480 # automatically. For compatibility with flake8 2, however, we need to 474 checker.name = 'eyeo'
481 # assign the name and version to each checker individually. 475 checker.version = __version__
482 if int(flake8.__version__.split('.')[0]) < 3:
483 for checker in [ASTChecker, check_non_default_encoding,
484 check_quotes, check_redundant_parenthesis]:
485 checker.name = 'eyeo'
486 checker.version = __version__
LEFTRIGHT

Powered by Google App Engine
This is Rietveld