Anthony Sottile | 8f61529 | 2022-01-15 19:24:05 -0500 | [diff] [blame] | 1 | from __future__ import annotations |
| 2 | |
Ben Webber | 35996b7 | 2017-11-26 00:17:47 +0000 | [diff] [blame] | 3 | import argparse |
| 4 | import ast |
Anthony Sottile | f5c42a0 | 2020-02-05 11:10:42 -0800 | [diff] [blame] | 5 | from typing import NamedTuple |
Anthony Sottile | 030bfac | 2019-01-31 19:19:10 -0800 | [diff] [blame] | 6 | from typing import Sequence |
Ben Webber | 35996b7 | 2017-11-26 00:17:47 +0000 | [diff] [blame] | 7 | |
| 8 | |
| 9 | BUILTIN_TYPES = { |
| 10 | 'complex': '0j', |
| 11 | 'dict': '{}', |
| 12 | 'float': '0.0', |
| 13 | 'int': '0', |
| 14 | 'list': '[]', |
| 15 | 'str': "''", |
| 16 | 'tuple': '()', |
| 17 | } |
| 18 | |
| 19 | |
Anthony Sottile | f5c42a0 | 2020-02-05 11:10:42 -0800 | [diff] [blame] | 20 | class Call(NamedTuple): |
| 21 | name: str |
| 22 | line: int |
| 23 | column: int |
Ben Webber | 35996b7 | 2017-11-26 00:17:47 +0000 | [diff] [blame] | 24 | |
| 25 | |
Anthony Sottile | 4575652 | 2019-02-11 19:56:15 -0800 | [diff] [blame] | 26 | class Visitor(ast.NodeVisitor): |
Anthony Sottile | f5c42a0 | 2020-02-05 11:10:42 -0800 | [diff] [blame] | 27 | def __init__( |
| 28 | self, |
Anthony Sottile | 8f61529 | 2022-01-15 19:24:05 -0500 | [diff] [blame] | 29 | ignore: Sequence[str] | None = None, |
Anthony Sottile | f5c42a0 | 2020-02-05 11:10:42 -0800 | [diff] [blame] | 30 | allow_dict_kwargs: bool = True, |
| 31 | ) -> None: |
Anthony Sottile | 8f61529 | 2022-01-15 19:24:05 -0500 | [diff] [blame] | 32 | self.builtin_type_calls: list[Call] = [] |
Ben Webber | 35996b7 | 2017-11-26 00:17:47 +0000 | [diff] [blame] | 33 | self.ignore = set(ignore) if ignore else set() |
| 34 | self.allow_dict_kwargs = allow_dict_kwargs |
| 35 | |
Anthony Sottile | f5c42a0 | 2020-02-05 11:10:42 -0800 | [diff] [blame] | 36 | def _check_dict_call(self, node: ast.Call) -> bool: |
| 37 | return self.allow_dict_kwargs and bool(node.keywords) |
Ben Webber | 35996b7 | 2017-11-26 00:17:47 +0000 | [diff] [blame] | 38 | |
Anthony Sottile | f5c42a0 | 2020-02-05 11:10:42 -0800 | [diff] [blame] | 39 | def visit_Call(self, node: ast.Call) -> None: |
Anthony Sottile | df93509 | 2018-05-17 17:14:25 -0700 | [diff] [blame] | 40 | if not isinstance(node.func, ast.Name): |
Ben Webber | 7758676 | 2017-11-30 18:27:16 +0000 | [diff] [blame] | 41 | # Ignore functions that are object attributes (`foo.bar()`). |
| 42 | # Assume that if the user calls `builtins.list()`, they know what |
| 43 | # they're doing. |
| 44 | return |
Ben Webber | 35996b7 | 2017-11-26 00:17:47 +0000 | [diff] [blame] | 45 | if node.func.id not in set(BUILTIN_TYPES).difference(self.ignore): |
| 46 | return |
| 47 | if node.func.id == 'dict' and self._check_dict_call(node): |
| 48 | return |
| 49 | elif node.args: |
| 50 | return |
| 51 | self.builtin_type_calls.append( |
Anthony Sottile | 4575652 | 2019-02-11 19:56:15 -0800 | [diff] [blame] | 52 | Call(node.func.id, node.lineno, node.col_offset), |
Ben Webber | 35996b7 | 2017-11-26 00:17:47 +0000 | [diff] [blame] | 53 | ) |
| 54 | |
| 55 | |
Anthony Sottile | f5c42a0 | 2020-02-05 11:10:42 -0800 | [diff] [blame] | 56 | def check_file( |
| 57 | filename: str, |
Anthony Sottile | 8f61529 | 2022-01-15 19:24:05 -0500 | [diff] [blame] | 58 | ignore: Sequence[str] | None = None, |
Anthony Sottile | f5c42a0 | 2020-02-05 11:10:42 -0800 | [diff] [blame] | 59 | allow_dict_kwargs: bool = True, |
Anthony Sottile | 8f61529 | 2022-01-15 19:24:05 -0500 | [diff] [blame] | 60 | ) -> list[Call]: |
Anthony Sottile | 5dc306b | 2018-06-18 00:00:38 -0700 | [diff] [blame] | 61 | with open(filename, 'rb') as f: |
| 62 | tree = ast.parse(f.read(), filename=filename) |
Anthony Sottile | 4575652 | 2019-02-11 19:56:15 -0800 | [diff] [blame] | 63 | visitor = Visitor(ignore=ignore, allow_dict_kwargs=allow_dict_kwargs) |
Ben Webber | 35996b7 | 2017-11-26 00:17:47 +0000 | [diff] [blame] | 64 | visitor.visit(tree) |
| 65 | return visitor.builtin_type_calls |
| 66 | |
| 67 | |
Anthony Sottile | 8f61529 | 2022-01-15 19:24:05 -0500 | [diff] [blame] | 68 | def parse_ignore(value: str) -> set[str]: |
Anthony Sottile | 030bfac | 2019-01-31 19:19:10 -0800 | [diff] [blame] | 69 | return set(value.split(',')) |
Ben Webber | 35996b7 | 2017-11-26 00:17:47 +0000 | [diff] [blame] | 70 | |
Anthony Sottile | 030bfac | 2019-01-31 19:19:10 -0800 | [diff] [blame] | 71 | |
Anthony Sottile | 8f61529 | 2022-01-15 19:24:05 -0500 | [diff] [blame] | 72 | def main(argv: Sequence[str] | None = None) -> int: |
Ben Webber | 35996b7 | 2017-11-26 00:17:47 +0000 | [diff] [blame] | 73 | parser = argparse.ArgumentParser() |
| 74 | parser.add_argument('filenames', nargs='*') |
| 75 | parser.add_argument('--ignore', type=parse_ignore, default=set()) |
| 76 | |
Anthony Sottile | 030bfac | 2019-01-31 19:19:10 -0800 | [diff] [blame] | 77 | mutex = parser.add_mutually_exclusive_group(required=False) |
| 78 | mutex.add_argument('--allow-dict-kwargs', action='store_true') |
Anthony Sottile | 4575652 | 2019-02-11 19:56:15 -0800 | [diff] [blame] | 79 | mutex.add_argument( |
| 80 | '--no-allow-dict-kwargs', |
| 81 | dest='allow_dict_kwargs', action='store_false', |
| 82 | ) |
Anthony Sottile | 030bfac | 2019-01-31 19:19:10 -0800 | [diff] [blame] | 83 | mutex.set_defaults(allow_dict_kwargs=True) |
Ben Webber | 35996b7 | 2017-11-26 00:17:47 +0000 | [diff] [blame] | 84 | |
Anthony Sottile | 030bfac | 2019-01-31 19:19:10 -0800 | [diff] [blame] | 85 | args = parser.parse_args(argv) |
Ben Webber | 35996b7 | 2017-11-26 00:17:47 +0000 | [diff] [blame] | 86 | |
Ben Webber | 35996b7 | 2017-11-26 00:17:47 +0000 | [diff] [blame] | 87 | rc = 0 |
| 88 | for filename in args.filenames: |
Anthony Sottile | 4575652 | 2019-02-11 19:56:15 -0800 | [diff] [blame] | 89 | calls = check_file( |
Ben Webber | 35996b7 | 2017-11-26 00:17:47 +0000 | [diff] [blame] | 90 | filename, |
| 91 | ignore=args.ignore, |
| 92 | allow_dict_kwargs=args.allow_dict_kwargs, |
| 93 | ) |
| 94 | if calls: |
| 95 | rc = rc or 1 |
| 96 | for call in calls: |
| 97 | print( |
Anthony Sottile | f5c42a0 | 2020-02-05 11:10:42 -0800 | [diff] [blame] | 98 | f'{filename}:{call.line}:{call.column}: ' |
| 99 | f'replace {call.name}() with {BUILTIN_TYPES[call.name]}', |
Ben Webber | 35996b7 | 2017-11-26 00:17:47 +0000 | [diff] [blame] | 100 | ) |
| 101 | return rc |
| 102 | |
| 103 | |
| 104 | if __name__ == '__main__': |
Anthony Sottile | 39ab2ed | 2021-10-23 13:23:50 -0400 | [diff] [blame] | 105 | raise SystemExit(main()) |