| from __future__ import unicode_literals |
| |
| import argparse |
| import ast |
| import collections |
| import sys |
| from typing import List |
| from typing import Optional |
| from typing import Sequence |
| from typing import Set |
| |
| |
| BUILTIN_TYPES = { |
| 'complex': '0j', |
| 'dict': '{}', |
| 'float': '0.0', |
| 'int': '0', |
| 'list': '[]', |
| 'str': "''", |
| 'tuple': '()', |
| } |
| |
| |
| BuiltinTypeCall = collections.namedtuple('BuiltinTypeCall', ['name', 'line', 'column']) |
| |
| |
| class BuiltinTypeVisitor(ast.NodeVisitor): |
| def __init__(self, ignore=None, allow_dict_kwargs=True): |
| # type: (Optional[Sequence[str]], bool) -> None |
| self.builtin_type_calls = [] # type: List[BuiltinTypeCall] |
| self.ignore = set(ignore) if ignore else set() |
| self.allow_dict_kwargs = allow_dict_kwargs |
| |
| def _check_dict_call(self, node): # type: (ast.Call) -> bool |
| |
| return self.allow_dict_kwargs and (getattr(node, 'kwargs', None) or getattr(node, 'keywords', None)) |
| |
| def visit_Call(self, node): # type: (ast.Call) -> None |
| |
| if not isinstance(node.func, ast.Name): |
| # Ignore functions that are object attributes (`foo.bar()`). |
| # Assume that if the user calls `builtins.list()`, they know what |
| # they're doing. |
| return |
| if node.func.id not in set(BUILTIN_TYPES).difference(self.ignore): |
| return |
| if node.func.id == 'dict' and self._check_dict_call(node): |
| return |
| elif node.args: |
| return |
| self.builtin_type_calls.append( |
| BuiltinTypeCall(node.func.id, node.lineno, node.col_offset), |
| ) |
| |
| |
| def check_file_for_builtin_type_constructors(filename, ignore=None, allow_dict_kwargs=True): |
| # type: (str, Optional[Sequence[str]], bool) -> List[BuiltinTypeCall] |
| with open(filename, 'rb') as f: |
| tree = ast.parse(f.read(), filename=filename) |
| visitor = BuiltinTypeVisitor(ignore=ignore, allow_dict_kwargs=allow_dict_kwargs) |
| visitor.visit(tree) |
| return visitor.builtin_type_calls |
| |
| |
| def parse_ignore(value): # type: (str) -> Set[str] |
| return set(value.split(',')) |
| |
| |
| def main(argv=None): # type: (Optional[Sequence[str]]) -> int |
| parser = argparse.ArgumentParser() |
| parser.add_argument('filenames', nargs='*') |
| parser.add_argument('--ignore', type=parse_ignore, default=set()) |
| |
| mutex = parser.add_mutually_exclusive_group(required=False) |
| mutex.add_argument('--allow-dict-kwargs', action='store_true') |
| mutex.add_argument('--no-allow-dict-kwargs', dest='allow_dict_kwargs', action='store_false') |
| mutex.set_defaults(allow_dict_kwargs=True) |
| |
| args = parser.parse_args(argv) |
| |
| rc = 0 |
| for filename in args.filenames: |
| calls = check_file_for_builtin_type_constructors( |
| filename, |
| ignore=args.ignore, |
| allow_dict_kwargs=args.allow_dict_kwargs, |
| ) |
| if calls: |
| rc = rc or 1 |
| for call in calls: |
| print( |
| '{filename}:{call.line}:{call.column} - Replace {call.name}() with {replacement}'.format( |
| filename=filename, |
| call=call, |
| replacement=BUILTIN_TYPES[call.name], |
| ), |
| ) |
| return rc |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |