blob: d3054aa075569f89b84529ae6093888d247c4197 [file] [log] [blame]
Anthony Sottile8f615292022-01-15 19:24:05 -05001from __future__ import annotations
2
Ben Webber35996b72017-11-26 00:17:47 +00003import argparse
4import ast
Anthony Sottilef5c42a02020-02-05 11:10:42 -08005from typing import NamedTuple
Anthony Sottile030bfac2019-01-31 19:19:10 -08006from typing import Sequence
Ben Webber35996b72017-11-26 00:17:47 +00007
8
9BUILTIN_TYPES = {
10 'complex': '0j',
11 'dict': '{}',
12 'float': '0.0',
13 'int': '0',
14 'list': '[]',
15 'str': "''",
16 'tuple': '()',
17}
18
19
Anthony Sottilef5c42a02020-02-05 11:10:42 -080020class Call(NamedTuple):
21 name: str
22 line: int
23 column: int
Ben Webber35996b72017-11-26 00:17:47 +000024
25
Anthony Sottile45756522019-02-11 19:56:15 -080026class Visitor(ast.NodeVisitor):
Anthony Sottilef5c42a02020-02-05 11:10:42 -080027 def __init__(
28 self,
Anthony Sottile8f615292022-01-15 19:24:05 -050029 ignore: Sequence[str] | None = None,
Anthony Sottilef5c42a02020-02-05 11:10:42 -080030 allow_dict_kwargs: bool = True,
31 ) -> None:
Anthony Sottile8f615292022-01-15 19:24:05 -050032 self.builtin_type_calls: list[Call] = []
Ben Webber35996b72017-11-26 00:17:47 +000033 self.ignore = set(ignore) if ignore else set()
34 self.allow_dict_kwargs = allow_dict_kwargs
35
Anthony Sottilef5c42a02020-02-05 11:10:42 -080036 def _check_dict_call(self, node: ast.Call) -> bool:
37 return self.allow_dict_kwargs and bool(node.keywords)
Ben Webber35996b72017-11-26 00:17:47 +000038
Anthony Sottilef5c42a02020-02-05 11:10:42 -080039 def visit_Call(self, node: ast.Call) -> None:
Anthony Sottiledf935092018-05-17 17:14:25 -070040 if not isinstance(node.func, ast.Name):
Ben Webber77586762017-11-30 18:27:16 +000041 # 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 Webber35996b72017-11-26 00:17:47 +000045 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 Sottile45756522019-02-11 19:56:15 -080052 Call(node.func.id, node.lineno, node.col_offset),
Ben Webber35996b72017-11-26 00:17:47 +000053 )
54
55
Anthony Sottilef5c42a02020-02-05 11:10:42 -080056def check_file(
57 filename: str,
Anthony Sottile8f615292022-01-15 19:24:05 -050058 ignore: Sequence[str] | None = None,
Anthony Sottilef5c42a02020-02-05 11:10:42 -080059 allow_dict_kwargs: bool = True,
Anthony Sottile8f615292022-01-15 19:24:05 -050060) -> list[Call]:
Anthony Sottile5dc306b2018-06-18 00:00:38 -070061 with open(filename, 'rb') as f:
62 tree = ast.parse(f.read(), filename=filename)
Anthony Sottile45756522019-02-11 19:56:15 -080063 visitor = Visitor(ignore=ignore, allow_dict_kwargs=allow_dict_kwargs)
Ben Webber35996b72017-11-26 00:17:47 +000064 visitor.visit(tree)
65 return visitor.builtin_type_calls
66
67
Anthony Sottile8f615292022-01-15 19:24:05 -050068def parse_ignore(value: str) -> set[str]:
Anthony Sottile030bfac2019-01-31 19:19:10 -080069 return set(value.split(','))
Ben Webber35996b72017-11-26 00:17:47 +000070
Anthony Sottile030bfac2019-01-31 19:19:10 -080071
Anthony Sottile8f615292022-01-15 19:24:05 -050072def main(argv: Sequence[str] | None = None) -> int:
Ben Webber35996b72017-11-26 00:17:47 +000073 parser = argparse.ArgumentParser()
74 parser.add_argument('filenames', nargs='*')
75 parser.add_argument('--ignore', type=parse_ignore, default=set())
76
Anthony Sottile030bfac2019-01-31 19:19:10 -080077 mutex = parser.add_mutually_exclusive_group(required=False)
78 mutex.add_argument('--allow-dict-kwargs', action='store_true')
Anthony Sottile45756522019-02-11 19:56:15 -080079 mutex.add_argument(
80 '--no-allow-dict-kwargs',
81 dest='allow_dict_kwargs', action='store_false',
82 )
Anthony Sottile030bfac2019-01-31 19:19:10 -080083 mutex.set_defaults(allow_dict_kwargs=True)
Ben Webber35996b72017-11-26 00:17:47 +000084
Anthony Sottile030bfac2019-01-31 19:19:10 -080085 args = parser.parse_args(argv)
Ben Webber35996b72017-11-26 00:17:47 +000086
Ben Webber35996b72017-11-26 00:17:47 +000087 rc = 0
88 for filename in args.filenames:
Anthony Sottile45756522019-02-11 19:56:15 -080089 calls = check_file(
Ben Webber35996b72017-11-26 00:17:47 +000090 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 Sottilef5c42a02020-02-05 11:10:42 -080098 f'{filename}:{call.line}:{call.column}: '
99 f'replace {call.name}() with {BUILTIN_TYPES[call.name]}',
Ben Webber35996b72017-11-26 00:17:47 +0000100 )
101 return rc
102
103
104if __name__ == '__main__':
Anthony Sottile39ab2ed2021-10-23 13:23:50 -0400105 raise SystemExit(main())