Repository: pre-commit/pre-commit-hooks
Pull request #251 (2017-11-30 18:34:13)

check-builtin-literals: Ignore function attribute calls
Fixes an oversight in #249.

The AST differentiates between bare function calls (names) and attribute calls.


Patch:
diff --git a/README.md b/README.md
index ff596ac..d717137 100644
--- a/README.md
+++ b/README.md
@@ -32,6 +32,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):


Test patch:
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):