1import ast 2import os 3import sys 4import tokenize 5 6 7class VultureInputException(Exception): 8 pass 9 10 11def _safe_eval(node, default): 12 """ 13 Safely evaluate the Boolean expression under the given AST node. 14 15 Substitute `default` for all sub-expressions that cannot be 16 evaluated (because variables or functions are undefined). 17 18 We could use eval() to evaluate more sub-expressions. However, this 19 function is not safe for arbitrary Python code. Even after 20 overwriting the "__builtins__" dictionary, the original dictionary 21 can be restored 22 (https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html). 23 24 """ 25 if isinstance(node, ast.BoolOp): 26 results = [_safe_eval(value, default) for value in node.values] 27 if isinstance(node.op, ast.And): 28 return all(results) 29 else: 30 return any(results) 31 elif isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.Not): 32 return not _safe_eval(node.operand, not default) 33 else: 34 try: 35 return ast.literal_eval(node) 36 except ValueError: 37 return default 38 39 40def condition_is_always_false(condition): 41 return not _safe_eval(condition, True) 42 43 44def condition_is_always_true(condition): 45 return _safe_eval(condition, False) 46 47 48def format_path(path): 49 try: 50 return path.relative_to(os.curdir) 51 except ValueError: 52 # Path is not below the current directory. 53 return path 54 55 56def get_decorator_name(decorator): 57 if isinstance(decorator, ast.Call): 58 decorator = decorator.func 59 parts = [] 60 while isinstance(decorator, ast.Attribute): 61 parts.append(decorator.attr) 62 decorator = decorator.value 63 parts.append(decorator.id) 64 return "@" + ".".join(reversed(parts)) 65 66 67def get_modules(paths): 68 """Retrieve Python files to check. 69 70 Loop over all given paths, abort if any ends with .pyc and add collect 71 the other given files (even those not ending with .py) and all .py 72 files under the given directories. 73 74 """ 75 modules = [] 76 for path in paths: 77 path = path.resolve() 78 if path.is_file(): 79 if path.suffix == ".pyc": 80 sys.exit(f"Error: *.pyc files are not supported: {path}") 81 else: 82 modules.append(path) 83 elif path.is_dir(): 84 modules.extend(path.rglob("*.py")) 85 else: 86 sys.exit(f"Error: {path} could not be found.") 87 return modules 88 89 90def read_file(filename): 91 try: 92 # Use encoding detected by tokenize.detect_encoding(). 93 with tokenize.open(filename) as f: 94 return f.read() 95 except (SyntaxError, UnicodeDecodeError) as err: 96 raise VultureInputException(err) 97 98 99class LoggingList(list): 100 def __init__(self, typ, verbose): 101 self.typ = typ 102 self._verbose = verbose 103 return list.__init__(self) 104 105 def append(self, item): 106 if self._verbose: 107 print(f'define {self.typ} "{item.name}"') 108 list.append(self, item) 109 110 111class LoggingSet(set): 112 def __init__(self, typ, verbose): 113 self.typ = typ 114 self._verbose = verbose 115 return set.__init__(self) 116 117 def add(self, name): 118 if self._verbose: 119 print(f'use {self.typ} "{name}"') 120 set.add(self, name) 121