diff --git a/README.md b/README.md index 3961204..12c86fe 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ Add this to your `.pre-commit-config.yaml` - `check-ast` - Simply check whether files parse as valid python. - `check-builtin-literals` - Require literal syntax when initializing empty or zero Python builtin types. - Allows calling constructors with positional arguments (e.g., `list('abc')`). + - Allows calling constructors from the `builtins` (`__builtin__`) namespace (`builtins.list()`). - Ignore this requirement for specific builtin types with `--ignore=type1,type2,…`. - Forbid `dict` keyword syntax with `--no-allow-dict-kwargs`. - `check-byte-order-marker` - Forbid files which have a UTF-8 byte-order marker diff --git a/pre_commit_hooks/check_builtin_literals.py b/pre_commit_hooks/check_builtin_literals.py index 1213288..c4ac969 100644 --- a/pre_commit_hooks/check_builtin_literals.py +++ b/pre_commit_hooks/check_builtin_literals.py @@ -30,6 +30,11 @@ class BuiltinTypeVisitor(ast.NodeVisitor): return self.allow_dict_kwargs and (getattr(node, 'kwargs', None) or getattr(node, 'keywords', None)) def visit_Call(self, node): + if isinstance(node.func, ast.Attribute): + # 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): diff --git a/testing/resources/builtin_constructors.py b/testing/resources/builtin_constructors.py index 3fab056..174a9e8 100644 --- a/testing/resources/builtin_constructors.py +++ b/testing/resources/builtin_constructors.py @@ -1,3 +1,5 @@ +from six.moves import builtins + c1 = complex() d1 = dict() f1 = float() @@ -5,3 +7,11 @@ i1 = int() l1 = list() s1 = str() t1 = tuple() + +c2 = builtins.complex() +d2 = builtins.dict() +f2 = builtins.float() +i2 = builtins.int() +l2 = builtins.list() +s2 = builtins.str() +t2 = builtins.tuple() diff --git a/tests/check_builtin_literals_test.py b/tests/check_builtin_literals_test.py index a38e522..13f896a 100644 --- a/tests/check_builtin_literals_test.py +++ b/tests/check_builtin_literals_test.py @@ -21,31 +21,36 @@ def visitor(): ("complex()", [BuiltinTypeCall('complex', 1, 0)]), ("complex(0, 0)", []), ("complex('0+0j')", []), + ('builtins.complex()', []), # float ("0.0", []), ("float()", [BuiltinTypeCall('float', 1, 0)]), ("float('0.0')", []), + ('builtins.float()', []), # int ("0", []), ("int()", [BuiltinTypeCall('int', 1, 0)]), ("int('0')", []), + ('builtins.int()', []), # list ("[]", []), ("list()", [BuiltinTypeCall('list', 1, 0)]), ("list('abc')", []), ("list([c for c in 'abc'])", []), ("list(c for c in 'abc')", []), + ('builtins.list()', []), # str ("''", []), ("str()", [BuiltinTypeCall('str', 1, 0)]), ("str('0')", []), - ("[]", []), + ('builtins.str()', []), # tuple ("()", []), ("tuple()", [BuiltinTypeCall('tuple', 1, 0)]), ("tuple('abc')", []), ("tuple([c for c in 'abc'])", []), ("tuple(c for c in 'abc')", []), + ('builtins.tuple()', []), ], ) def test_non_dict_exprs(visitor, expression, calls): @@ -62,6 +67,7 @@ def test_non_dict_exprs(visitor, expression, calls): ("dict(**{'a': 1, 'b': 2, 'c': 3})", []), ("dict([(k, v) for k, v in [('a', 1), ('b', 2), ('c', 3)]])", []), ("dict((k, v) for k, v in [('a', 1), ('b', 2), ('c', 3)])", []), + ('builtins.dict()', []), ], ) def test_dict_allow_kwargs_exprs(visitor, expression, calls): @@ -75,6 +81,7 @@ def test_dict_allow_kwargs_exprs(visitor, expression, calls): ("dict()", [BuiltinTypeCall('dict', 1, 0)]), ("dict(a=1, b=2, c=3)", [BuiltinTypeCall('dict', 1, 0)]), ("dict(**{'a': 1, 'b': 2, 'c': 3})", [BuiltinTypeCall('dict', 1, 0)]), + ('builtins.dict()', []), ], ) def test_dict_no_allow_kwargs_exprs(expression, calls):