From d4583dcad7d68d3c1503b04ec0d3364809304807 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 30 Jan 2022 12:02:58 +0100 Subject: Adding upstream version 4.5.0+dfsg. Signed-off-by: Daniel Baumann --- pre_commit_hooks/check_builtin_literals.py | 105 +++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 pre_commit_hooks/check_builtin_literals.py (limited to 'pre_commit_hooks/check_builtin_literals.py') diff --git a/pre_commit_hooks/check_builtin_literals.py b/pre_commit_hooks/check_builtin_literals.py new file mode 100644 index 0000000..d3054aa --- /dev/null +++ b/pre_commit_hooks/check_builtin_literals.py @@ -0,0 +1,105 @@ +from __future__ import annotations + +import argparse +import ast +from typing import NamedTuple +from typing import Sequence + + +BUILTIN_TYPES = { + 'complex': '0j', + 'dict': '{}', + 'float': '0.0', + 'int': '0', + 'list': '[]', + 'str': "''", + 'tuple': '()', +} + + +class Call(NamedTuple): + name: str + line: int + column: int + + +class Visitor(ast.NodeVisitor): + def __init__( + self, + ignore: Sequence[str] | None = None, + allow_dict_kwargs: bool = True, + ) -> None: + self.builtin_type_calls: list[Call] = [] + self.ignore = set(ignore) if ignore else set() + self.allow_dict_kwargs = allow_dict_kwargs + + def _check_dict_call(self, node: ast.Call) -> bool: + return self.allow_dict_kwargs and bool(node.keywords) + + def visit_Call(self, node: 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( + Call(node.func.id, node.lineno, node.col_offset), + ) + + +def check_file( + filename: str, + ignore: Sequence[str] | None = None, + allow_dict_kwargs: bool = True, +) -> list[Call]: + with open(filename, 'rb') as f: + tree = ast.parse(f.read(), filename=filename) + visitor = Visitor(ignore=ignore, allow_dict_kwargs=allow_dict_kwargs) + visitor.visit(tree) + return visitor.builtin_type_calls + + +def parse_ignore(value: str) -> set[str]: + return set(value.split(',')) + + +def main(argv: Sequence[str] | None = None) -> 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( + filename, + ignore=args.ignore, + allow_dict_kwargs=args.allow_dict_kwargs, + ) + if calls: + rc = rc or 1 + for call in calls: + print( + f'{filename}:{call.line}:{call.column}: ' + f'replace {call.name}() with {BUILTIN_TYPES[call.name]}', + ) + return rc + + +if __name__ == '__main__': + raise SystemExit(main()) -- cgit v1.2.3