# Copyright (c) 2003-2016 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # 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 """Looks for code which can be refactored.""" import collections import astroid from pylint.interfaces import IAstroidChecker from pylint.checkers import BaseChecker from pylint.checkers.utils import check_messages, is_builtin_object, safe_infer class RefactoringChecker(BaseChecker): """Looks for code which can be refactored.""" __implements__ = (IAstroidChecker,) name = 'refactoring' msgs = { 'R1701': ( "Consider merging these isinstance calls to isinstance(%s, (%s))", "consider-merging-isinstance", "Usen when multiple consecutive isinstance calls can be merged into one."), } priority = 0 @staticmethod def _duplicated_isinstance_types(node): """Get the duplicated types from the underlying isinstance calls. :param astroid.BoolOp node: Node which should contain a bunch of isinstance calls. :returns: Dictionary of the comparison objects from the isinstance calls, to duplicate values from consecutive calls. :rtype: dict """ duplicated_objects = set() all_types = collections.defaultdict(set) for call in node.values: if not isinstance(call, astroid.Call) or len(call.args) != 2: continue inferred = safe_infer(call.func) if not inferred or not is_builtin_object(inferred): continue if inferred.name != 'isinstance': continue isinstance_object = call.args[0].as_string() isinstance_types = call.args[1] if isinstance_object in all_types: duplicated_objects.add(isinstance_object) if isinstance(isinstance_types, astroid.Tuple): elems = [class_type.as_string() for class_type in isinstance_types.itered()] else: elems = [isinstance_types.as_string()] all_types[isinstance_object].update(elems) # Remove all keys which not duplicated return {key: value for key, value in all_types.items() if key in duplicated_objects} @check_messages('consider-merging-isinstance') def visit_boolop(self, node): '''Check isinstance calls which can be merged together.''' if node.op != 'or': return first_args = self._duplicated_isinstance_types(node) for duplicated_name, class_names in first_args.items(): names = sorted(name for name in class_names) self.add_message('consider-merging-isinstance', node=node, args=(duplicated_name, ', '.join(names))) def register(linter): """Required method to auto register this checker.""" linter.register_checker(RefactoringChecker(linter))