2022-01-16 08:24:05 +08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2017-11-26 08:17:47 +08:00
|
|
|
import argparse
|
|
|
|
import ast
|
2024-10-12 07:30:07 +08:00
|
|
|
from collections.abc import Sequence
|
2020-02-06 03:10:42 +08:00
|
|
|
from typing import NamedTuple
|
2017-11-26 08:17:47 +08:00
|
|
|
|
|
|
|
|
|
|
|
BUILTIN_TYPES = {
|
|
|
|
'complex': '0j',
|
|
|
|
'dict': '{}',
|
|
|
|
'float': '0.0',
|
|
|
|
'int': '0',
|
|
|
|
'list': '[]',
|
|
|
|
'str': "''",
|
|
|
|
'tuple': '()',
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-02-06 03:10:42 +08:00
|
|
|
class Call(NamedTuple):
|
|
|
|
name: str
|
|
|
|
line: int
|
|
|
|
column: int
|
2017-11-26 08:17:47 +08:00
|
|
|
|
|
|
|
|
2019-02-12 11:56:15 +08:00
|
|
|
class Visitor(ast.NodeVisitor):
|
2020-02-06 03:10:42 +08:00
|
|
|
def __init__(
|
|
|
|
self,
|
2022-01-16 08:24:05 +08:00
|
|
|
ignore: Sequence[str] | None = None,
|
2020-02-06 03:10:42 +08:00
|
|
|
allow_dict_kwargs: bool = True,
|
|
|
|
) -> None:
|
2022-01-16 08:24:05 +08:00
|
|
|
self.builtin_type_calls: list[Call] = []
|
2017-11-26 08:17:47 +08:00
|
|
|
self.ignore = set(ignore) if ignore else set()
|
|
|
|
self.allow_dict_kwargs = allow_dict_kwargs
|
|
|
|
|
2020-02-06 03:10:42 +08:00
|
|
|
def _check_dict_call(self, node: ast.Call) -> bool:
|
|
|
|
return self.allow_dict_kwargs and bool(node.keywords)
|
2017-11-26 08:17:47 +08:00
|
|
|
|
2020-02-06 03:10:42 +08:00
|
|
|
def visit_Call(self, node: ast.Call) -> None:
|
2018-05-18 08:14:25 +08:00
|
|
|
if not isinstance(node.func, ast.Name):
|
2017-12-01 02:27:16 +08:00
|
|
|
# Ignore functions that are object attributes (`foo.bar()`).
|
|
|
|
# Assume that if the user calls `builtins.list()`, they know what
|
|
|
|
# they're doing.
|
|
|
|
return
|
2017-11-26 08:17:47 +08:00
|
|
|
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(
|
2019-02-12 11:56:15 +08:00
|
|
|
Call(node.func.id, node.lineno, node.col_offset),
|
2017-11-26 08:17:47 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2020-02-06 03:10:42 +08:00
|
|
|
def check_file(
|
|
|
|
filename: str,
|
2022-01-16 08:24:05 +08:00
|
|
|
ignore: Sequence[str] | None = None,
|
2020-02-06 03:10:42 +08:00
|
|
|
allow_dict_kwargs: bool = True,
|
2022-01-16 08:24:05 +08:00
|
|
|
) -> list[Call]:
|
2018-06-18 15:00:38 +08:00
|
|
|
with open(filename, 'rb') as f:
|
|
|
|
tree = ast.parse(f.read(), filename=filename)
|
2019-02-12 11:56:15 +08:00
|
|
|
visitor = Visitor(ignore=ignore, allow_dict_kwargs=allow_dict_kwargs)
|
2017-11-26 08:17:47 +08:00
|
|
|
visitor.visit(tree)
|
|
|
|
return visitor.builtin_type_calls
|
|
|
|
|
|
|
|
|
2022-01-16 08:24:05 +08:00
|
|
|
def parse_ignore(value: str) -> set[str]:
|
2019-02-01 11:19:10 +08:00
|
|
|
return set(value.split(','))
|
2017-11-26 08:17:47 +08:00
|
|
|
|
2019-02-01 11:19:10 +08:00
|
|
|
|
2022-01-16 08:24:05 +08:00
|
|
|
def main(argv: Sequence[str] | None = None) -> int:
|
2017-11-26 08:17:47 +08:00
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
parser.add_argument('filenames', nargs='*')
|
|
|
|
parser.add_argument('--ignore', type=parse_ignore, default=set())
|
|
|
|
|
2019-02-01 11:19:10 +08:00
|
|
|
mutex = parser.add_mutually_exclusive_group(required=False)
|
|
|
|
mutex.add_argument('--allow-dict-kwargs', action='store_true')
|
2019-02-12 11:56:15 +08:00
|
|
|
mutex.add_argument(
|
|
|
|
'--no-allow-dict-kwargs',
|
|
|
|
dest='allow_dict_kwargs', action='store_false',
|
|
|
|
)
|
2019-02-01 11:19:10 +08:00
|
|
|
mutex.set_defaults(allow_dict_kwargs=True)
|
2017-11-26 08:17:47 +08:00
|
|
|
|
2019-02-01 11:19:10 +08:00
|
|
|
args = parser.parse_args(argv)
|
2017-11-26 08:17:47 +08:00
|
|
|
|
|
|
|
rc = 0
|
|
|
|
for filename in args.filenames:
|
2019-02-12 11:56:15 +08:00
|
|
|
calls = check_file(
|
2017-11-26 08:17:47 +08:00
|
|
|
filename,
|
|
|
|
ignore=args.ignore,
|
|
|
|
allow_dict_kwargs=args.allow_dict_kwargs,
|
|
|
|
)
|
|
|
|
if calls:
|
|
|
|
rc = rc or 1
|
|
|
|
for call in calls:
|
|
|
|
print(
|
2020-02-06 03:10:42 +08:00
|
|
|
f'{filename}:{call.line}:{call.column}: '
|
|
|
|
f'replace {call.name}() with {BUILTIN_TYPES[call.name]}',
|
2017-11-26 08:17:47 +08:00
|
|
|
)
|
|
|
|
return rc
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2021-10-24 01:23:50 +08:00
|
|
|
raise SystemExit(main())
|