Blob Blame History Raw
From 61b5a758a2a03d3e22cdcb7b9420e71c3f0a2d77 Mon Sep 17 00:00:00 2001
From: Caio Carrara <ccarrara@redhat.com>
Date: Tue, 21 Aug 2018 13:06:17 -0300
Subject: [PATCH]  Fix is_subclass_of when one param is not ClassDef

Currently the function `checkers.utils.is_subclass_of()` is raising a
`NoneType object has no attribute name` exception when only one of the
arguments (nodes) is `None`.

This change fix that updating the function to make sure all params are
an instance of `astroid.ClassDef` before the proper check
---
 CONTRIBUTORS.txt                       |  2 ++
 ChangeLog                              |  3 +++
 doc/whatsnew/2.2.rst                   |  2 +-
 pylint/checkers/utils.py               |  3 ++-
 pylint/test/unittest_checkers_utils.py | 36 ++++++++++++++++++++++++++
 5 files changed, 44 insertions(+), 2 deletions(-)

diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 1e4c47745..7cd2de4a7 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -219,3 +219,5 @@ contributors:
 * Marcus Näslund (naslundx): contributor 
 
 * Natalie Serebryakova: contributor
+
+* Caio Carrara: contributor
diff --git a/ChangeLog b/ChangeLog
index 8bb81581c..8439abf6a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -44,6 +44,9 @@ Release date: TBA
    * Consider tuples in exception handler for ``try-except-raise``.
      Close #2389
 
+   * Fix astroid.ClassDef check in checkers.utils.is_subclass_of 
+
+
 What's New in Pylint 2.1.1?
 ===========================
 
diff --git a/doc/whatsnew/2.2.rst b/doc/whatsnew/2.2.rst
index bd388ae4c..a2d5facc6 100644
--- a/doc/whatsnew/2.2.rst
+++ b/doc/whatsnew/2.2.rst
@@ -23,4 +23,4 @@ Other Changes
 
 * Fix false positive ``undefined-variable`` and ``used-before-assignment`` with nonlocal keyword usage.
 
-
+* Fix exceptions being raised when one of the params is not a ClassDef in checkers.utils.is_subclass_of.
diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py
index 2add31f67..8ac9b1cce 100644
--- a/pylint/checkers/utils.py
+++ b/pylint/checkers/utils.py
@@ -22,6 +22,7 @@
 # Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
 # Copyright (c) 2018 Ville Skyttä <ville.skytta@upcloud.com>
 # Copyright (c) 2018 Brian Shaginaw <brian.shaginaw@warbyparker.com>
+# Copyright (c) 2018 Caio Carrara <ccarrara@redhat.com>
 
 # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 # For details: https://github.com/PyCQA/pylint/blob/master/COPYING
@@ -1045,7 +1046,7 @@ def is_subclass_of(node_a: astroid.ClassDef,
     :returns: True if node_a is derived from node_b. False otherwise.
     :rtype: bool
     """
-    if not any(isinstance(node, astroid.ClassDef) for node in (node_a, node_b)):
+    if not all(isinstance(node, astroid.ClassDef) for node in (node_a, node_b)):
         return False
 
     return node_b.name in {base.name for base in node_a.bases}
diff --git a/pylint/test/unittest_checkers_utils.py b/pylint/test/unittest_checkers_utils.py
index 4d3f46a3e..b9af5ba16 100644
--- a/pylint/test/unittest_checkers_utils.py
+++ b/pylint/test/unittest_checkers_utils.py
@@ -4,6 +4,7 @@
 # Copyright (c) 2014 Arun Persaud <arun@nubati.net>
 # Copyright (c) 2015 Ionel Cristian Maries <contact@ionelmc.ro>
 # Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
+# Copyright (c) 2018 Caio Carrara <ccarrara@redhat.com>
 
 # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 # For details: https://github.com/PyCQA/pylint/blob/master/COPYING
@@ -102,3 +103,38 @@ def test_node_ignores_exception():
     assert not utils.node_ignores_exception(nodes[1], ZeroDivisionError)
     assert utils.node_ignores_exception(nodes[2], ZeroDivisionError)
     assert not utils.node_ignores_exception(nodes[3], ZeroDivisionError)
+
+
+def test_is_sublcass_of_node_b_derived_from_node_a():
+    nodes = astroid.extract_node("""
+    class Superclass: #@
+        pass
+
+    class Subclass(Superclass): #@
+        pass
+    """)
+    assert utils.is_subclass_of(nodes[1], nodes[0])
+
+
+def test_is_sublcass_of_node_b_not_derived_from_node_a():
+    nodes = astroid.extract_node("""
+    class OneClass: #@
+        pass
+
+    class AnotherClass: #@
+        pass
+    """)
+    assert not utils.is_subclass_of(nodes[1], nodes[0])
+
+
+def test_is_sublcass_of_one_param_is_not_classdef():
+    node = astroid.extract_node("""
+    class OneClass: #@
+        pass
+    """)
+    assert not utils.is_subclass_of(None, node)
+    assert not utils.is_subclass_of(node, None)
+
+
+def test_is_sublcass_of_both_params_are_not_classdef():
+    assert not utils.is_subclass_of(None, None)