def visit_classdef(self, node: nodes.ClassDef) -> None: """Visit an astroid.Class node. * set the locals_type and instance_attrs_type mappings * set the implements list and build it * optionally tag the node with a unique id """ if hasattr(node, "locals_type"): return node.locals_type = collections.defaultdict(list) if self.tag: node.uid = self.generate_id() # resolve ancestors for baseobj in node.ancestors(recurs=False): specializations = getattr(baseobj, "specializations", []) specializations.append(node) baseobj.specializations = specializations # resolve instance attributes node.instance_attrs_type = collections.defaultdict(list) for assignattrs in node.instance_attrs.values(): for assignattr in assignattrs: if not isinstance(assignattr, nodes.Unknown): self.handle_assignattr_type(assignattr, node) # resolve implemented interface try: node.implements = list(interfaces(node, self.inherited_interfaces)) except astroid.InferenceError: node.implements = []
def _get_parents_iter( node: nodes.ClassDef, ignored_parents: FrozenSet[str]) -> Iterator[nodes.ClassDef]: r"""Get parents of ``node``, excluding ancestors of ``ignored_parents``. If we have the following inheritance diagram: F / D E \/ B C \/ A # class A(B, C): ... And ``ignored_parents`` is ``{"E"}``, then this function will return ``{A, B, C, D}`` -- both ``E`` and its ancestors are excluded. """ parents: Set[nodes.ClassDef] = set() to_explore = cast(List[nodes.ClassDef], list(node.ancestors(recurs=False))) while to_explore: parent = to_explore.pop() if parent.qname() in ignored_parents: continue if parent not in parents: # This guard might appear to be performing the same function as # adding the resolved parents to a set to eliminate duplicates # (legitimate due to diamond inheritance patterns), but its # additional purpose is to prevent cycles (not normally possible, # but potential due to inference) and thus guarantee termination # of the while-loop yield parent parents.add(parent) to_explore.extend(parent.ancestors(recurs=False))
def visit_classdef(self, node: nodes.ClassDef) -> None: """Called when a ClassDef node is visited.""" ancestor: nodes.ClassDef for ancestor in node.ancestors(): for class_matches in self._class_matchers: if ancestor.name == class_matches.base_class: self._visit_class_functions(node, class_matches.matches)
def visit_classdef(self, node: nodes.ClassDef): """check size of inheritance hierarchy and number of instance attributes""" nb_parents = sum( 1 for ancestor in node.ancestors() if ancestor.qname() not in STDLIB_CLASSES_IGNORE_ANCESTOR) if nb_parents > self.config.max_parents: self.add_message( "too-many-ancestors", node=node, args=(nb_parents, self.config.max_parents), ) if len(node.instance_attrs) > self.config.max_attributes: self.add_message( "too-many-instance-attributes", node=node, args=(len(node.instance_attrs), self.config.max_attributes), )
def leave_classdef(self, node: nodes.ClassDef) -> None: """check number of public methods""" my_methods = sum(1 for method in node.mymethods() if not method.name.startswith("_")) # Does the class contain less than n public methods ? # This checks only the methods defined in the current class, # since the user might not have control over the classes # from the ancestors. It avoids some false positives # for classes such as unittest.TestCase, which provides # a lot of assert methods. It doesn't make sense to warn # when the user subclasses TestCase to add his own tests. if my_methods > self.config.max_public_methods: self.add_message( "too-many-public-methods", node=node, args=(my_methods, self.config.max_public_methods), ) # Stop here if the class is excluded via configuration. if node.type == "class" and self._exclude_too_few_public_methods: for ancestor in node.ancestors(): if any( pattern.match(ancestor.qname()) for pattern in self._exclude_too_few_public_methods): return # Stop here for exception, metaclass, interface classes and other # classes for which we don't need to count the methods. if node.type != "class" or _is_exempt_from_public_methods(node): return # Does the class contain more than n public methods ? # This checks all the methods defined by ancestors and # by the current class. all_methods = _count_methods_in_class(node) if all_methods < self.config.min_public_methods: self.add_message( "too-few-public-methods", node=node, args=(all_methods, self.config.min_public_methods), )
def visit_classdef(self, node: nodes.ClassDef) -> None: """Visit an astroid.Class node. * set the locals_type and instance_attrs_type mappings * set the implements list and build it * optionally tag the node with a unique id """ if hasattr(node, "locals_type"): return node.locals_type = collections.defaultdict(list) if self.tag: node.uid = self.generate_id() # resolve ancestors for baseobj in node.ancestors(recurs=False): specializations = getattr(baseobj, "specializations", []) specializations.append(node) baseobj.specializations = specializations # resolve instance attributes node.instance_attrs_type = collections.defaultdict(list) for assignattrs in tuple(node.instance_attrs.values()): for assignattr in assignattrs: if not isinstance(assignattr, nodes.Unknown): self.handle_assignattr_type(assignattr, node) # resolve implemented interface try: ifaces = interfaces(node) if ifaces is not None: node.implements = list(ifaces) if node.implements: # TODO: 3.0: Remove support for __implements__ warnings.warn( "pyreverse will drop support for resolving and displaying implemented interfaces in pylint 3.0. " "The implementation relies on the '__implements__' attribute proposed in PEP 245, which was rejected " "in 2006.", DeprecationWarning, ) else: node.implements = [] except astroid.InferenceError: node.implements = []
def visit_classdef(self, ast: ast_node.ClassDef): self.write("class %s {" % ast.name) self.indent += 1 for instance_attrs in ast.instance_attrs.values(): for attr in instance_attrs: if isinstance(attr, ast_node.AssignAttr): attr.accept(self) break for local in ast.values(): local.accept(self) self.indent -= 1 self.write("}") for anc in ast.ancestors(recurs=False): cls = next(anc.infer()) if isinstance(cls, ast_node.ClassDef): module = cls.root().name if is_target_module(module): self.write("%s <|- %s.%s" % (ast.name, cls.root().name, cls.name))
def _process_class_member_and_attrs(cldef: ClassDef, pkgfiles: Set[str]) -> Set[str]: """ Determine the class attributes, methods and instance attributes defined by a class. The function will inspect and populate them from the parents while the parent is defined by a module file in the same mmpack package. This function is called recursively to populate a class. Args: cldef: the astroid node defining the class pkgfiles: set of files in the same mmpack package Returns: set of name corresponding to the class attributes and methods and instance attributes. """ syms = set() # add member and attributes from parent classes implemented in files of # the same package for base in cldef.ancestors(recurs=False): mod = base.root() if _is_module_packaged(mod, pkgfiles): syms.update(_process_class_member_and_attrs(base, pkgfiles)) # Add public class attributes for attr in cldef.locals: if (isinstance(cldef.locals[attr][-1], AssignName) and _is_public_sym(attr)): syms.add(attr) # Add public class methods and instance attributes syms.update({m.name for m in cldef.mymethods() if _is_public_sym(m.name)}) syms.update({a for a in cldef.instance_attrs if _is_public_sym(a)}) return syms
def base_classes_of_node(instance: nodes.ClassDef) -> List[nodes.Name]: """Return all the classes names that a ClassDef inherit from including 'object'.""" try: return [instance.name] + [x.name for x in instance.ancestors()] except TypeError: return [instance.name]