qubes-installer-qubes-os/anaconda/tests/pylint/pointless-override.py
Marek Marczykowski-Górecki 701ced5ddb anaconda: update to 22.20.13-1
Apply diff anaconda-21.48.21-1..anaconda-22.20.13-1
2016-03-22 02:27:17 +13:00

253 lines
8.8 KiB
Python

# Pylint checker for pointless class attributes overrides.
#
# Copyright (C) 2014 Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details. You should have received a copy of the
# GNU General Public License along with this program; if not, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
# source code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
# Red Hat Author(s): Anne Mulhern <amulhern@redhat.com>
#
import abc
from six import add_metaclass
import astroid
from pylint.checkers import BaseChecker
from pylint.checkers.utils import check_messages
from pylint.interfaces import IAstroidChecker
@add_metaclass(abc.ABCMeta)
class PointlessData(object):
_DEF_CLASS = abc.abstractproperty(doc="Class of interesting definitions.")
message_id = abc.abstractproperty(doc="Pylint message identifier.")
@classmethod
@abc.abstractmethod
def _retain_node(cls, node, restrict=True):
""" Determines whether to retain a node for the analysis.
:param node: an AST node
:type node: astroid.Class
:param restrict bool: True if results returned should be restricted
:returns: True if the node should be kept, otherwise False
:rtype: bool
Restricted nodes are candidates for being marked as overridden.
Only restricted nodes are put into the initial pool of candidates.
"""
raise NotImplementedError()
@staticmethod
@abc.abstractmethod
def _extract_value(node):
""" Return the node that contains the assignment's value.
:param node: an AST node
:type node: astroid.Class
:returns: the node corresponding to the value
:rtype: bool
"""
raise NotImplementedError()
@staticmethod
@abc.abstractmethod
def _extract_targets(node):
""" Generates the names being assigned to.
:param node: an AST node
:type node: astroid.Class
:returns: a list of assignment target names
:rtype: generator of str
"""
raise NotImplementedError()
@classmethod
def get_data(cls, node, restrict=True):
""" Find relevant nodes for this analysis.
:param node: an AST node
:type node: astroid.Class
:param restrict bool: True if results returned should be restricted
:rtype: generator of astroid.Class
:returns: a generator of interesting nodes.
Note that all nodes returned are guaranteed to be instances of
some class in self._DEF_CLASS.
"""
nodes = (n for n in node.body if isinstance(n, cls._DEF_CLASS))
for n in nodes:
if cls._retain_node(n, restrict):
for name in cls._extract_targets(n):
yield (name, cls._extract_value(n))
@classmethod
@abc.abstractmethod
def check_equal(cls, node, other):
""" Check whether the two nodes are considered equal.
:param node: some ast node
:param other: some ast node
:rtype: bool
:returns: True if the nodes are considered equal, otherwise False
If the method returns True, the nodes are actually equal, but it
may return False when the nodes are equal.
"""
raise NotImplementedError()
class PointlessFunctionDefinition(PointlessData):
""" Looking for pointless function definitions. """
_DEF_CLASS = astroid.Function
message_id = "W9952"
@classmethod
def _retain_node(cls, node, restrict=True):
return not restrict or \
(len(node.body) == 1 and isinstance(node.body[0], astroid.Pass))
@classmethod
def check_equal(cls, node, other):
return len(node.body) == 1 and isinstance(node.body[0], astroid.Pass) and \
len(other.body) == 1 and isinstance(other.body[0], astroid.Pass)
@staticmethod
def _extract_value(node):
return node
@staticmethod
def _extract_targets(node):
yield node.name
class PointlessAssignment(PointlessData):
_DEF_CLASS = astroid.Assign
message_id = "W9951"
_VALUE_CLASSES = (
astroid.Const,
astroid.Dict,
astroid.List,
astroid.Tuple
)
@classmethod
def _retain_node(cls, node, restrict=True):
return not restrict or isinstance(node.value, cls._VALUE_CLASSES)
@classmethod
def check_equal(cls, node, other):
if type(node) != type(other):
return False
if isinstance(node, astroid.Const):
return node.value == other.value
if isinstance(node, (astroid.List, astroid.Tuple)):
return len(node.elts) == len(other.elts) and \
all(cls.check_equal(n, o) for (n, o) in zip(node.elts, other.elts))
if isinstance(node, astroid.Dict):
return len(node.items) == len(other.items)
return False
@staticmethod
def _extract_value(node):
return node.value
@staticmethod
def _extract_targets(node):
for target in node.targets:
yield target.name
class PointlessClassAttributeOverrideChecker(BaseChecker):
""" If the nearest definition of the class attribute in the MRO assigns
it the same value, then the overriding definition is said to be
pointless.
The algorithm for detecting a pointless attribute override is the following.
* For each class, C:
- For each attribute assignment,
name_1 = name_2 ... name_n = l (where l is a literal):
* For each n in (n_1, n_2):
- Traverse the linearization of the MRO until the first
matching assignment n = l' is identified. If l is equal to l',
then consider that the assignment to l in C is a
pointless override.
The algorithm for detecting a pointless method override has the same
general structure, and the same defects discussed below.
Note that this analysis is neither sound nor complete. It is unsound
under multiple inheritance. Consider the following class hierarchy::
class A(object):
_attrib = False
class B(A):
_attrib = False
class C(A):
_attrib = True
class D(B,C):
pass
In this case, starting from B, B._attrib = False would be considered
pointless. However, for D the MRO is B, C, A, and removing the assignment
B._attrib = False would change the inherited value of D._attrib from
False to True.
The analysis is incomplete because it will find some values unequal when
actually they are equal.
The analysis is both incomplete and unsound because it expects that
assignments will always be made by means of the same syntax.
"""
__implements__ = (IAstroidChecker,)
name = "pointless class attribute override checker"
msgs = {
"W9951":
(
"Assignment to class attribute %s overrides identical assignment in ancestor.",
"pointless-class-attribute-override",
"Assignment to class attribute that overrides assignment in ancestor that assigns identical value has no effect."
),
"W9952":
(
"definition of %s method overrides identical method definition in ancestor",
"pointless-method-definition-override",
"Overriding empty method definition with another empty method definition has no effect."
)
}
@check_messages("W9951", "W9952")
def visit_class(self, node):
for checker in (PointlessAssignment, PointlessFunctionDefinition):
for (name, value) in checker.get_data(node):
for a in node.ancestors():
match = next((v for (n, v) in checker.get_data(a, False) if n == name), None)
if match is not None:
if checker.check_equal(value, match):
self.add_message(checker.message_id, node=value, args=(name,))
break
def register(linter):
linter.register_checker(PointlessClassAttributeOverrideChecker(linter))