Esempio n. 1
0
 def visit_classdef(self, node: nodes.ClassDef) -> None:
     locals_and_methods = set(node.locals).union(x.name
                                                 for x in node.mymethods())
     if "__eq__" in locals_and_methods and "__hash__" not in locals_and_methods:
         self.add_message("eq-without-hash",
                          node=node,
                          confidence=interfaces.HIGH)
Esempio n. 2
0
 def _visit_class_functions(self, node: nodes.ClassDef,
                            matches: list[TypeHintMatch]) -> None:
     for match in matches:
         for function_node in node.mymethods():
             function_name: str | None = function_node.name
             if match.function_name == function_name:
                 self._check_function(function_node, match)
Esempio n. 3
0
def _count_methods_in_class(node: nodes.ClassDef) -> int:
    all_methods = sum(1 for method in node.methods()
                      if not method.name.startswith("_"))
    # Special methods count towards the number of public methods,
    # but don't count towards there being too many methods.
    for method in node.mymethods():
        if SPECIAL_OBJ.search(method.name) and method.name != "__init__":
            all_methods += 1
    return all_methods
Esempio n. 4
0
    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),
            )
Esempio n. 5
0
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
Esempio n. 6
0
def infer_enum_class(node: nodes.ClassDef) -> nodes.ClassDef:
    """Specific inference for enums."""
    for basename in (b for cls in node.mro() for b in cls.basenames):
        if node.root().name == "enum":
            # Skip if the class is directly from enum module.
            break
        dunder_members = {}
        target_names = set()
        for local, values in node.locals.items():
            if any(not isinstance(value, nodes.AssignName)
                   for value in values):
                continue

            stmt = values[0].statement(future=True)
            if isinstance(stmt, nodes.Assign):
                if isinstance(stmt.targets[0], nodes.Tuple):
                    targets = stmt.targets[0].itered()
                else:
                    targets = stmt.targets
            elif isinstance(stmt, nodes.AnnAssign):
                targets = [stmt.target]
            else:
                continue

            inferred_return_value = None
            if stmt.value is not None:
                if isinstance(stmt.value, nodes.Const):
                    if isinstance(stmt.value.value, str):
                        inferred_return_value = repr(stmt.value.value)
                    else:
                        inferred_return_value = stmt.value.value
                else:
                    inferred_return_value = stmt.value.as_string()

            new_targets = []
            for target in targets:
                if isinstance(target, nodes.Starred):
                    continue
                target_names.add(target.name)
                # Replace all the assignments with our mocked class.
                classdef = dedent("""
                class {name}({types}):
                    @property
                    def value(self):
                        return {return_value}
                    @property
                    def name(self):
                        return "{name}"
                """.format(
                    name=target.name,
                    types=", ".join(node.basenames),
                    return_value=inferred_return_value,
                ))
                if "IntFlag" in basename:
                    # Alright, we need to add some additional methods.
                    # Unfortunately we still can't infer the resulting objects as
                    # Enum members, but once we'll be able to do that, the following
                    # should result in some nice symbolic execution
                    classdef += INT_FLAG_ADDITION_METHODS.format(
                        name=target.name)

                fake = AstroidBuilder(
                    AstroidManager(),
                    apply_transforms=False).string_build(classdef)[target.name]
                fake.parent = target.parent
                for method in node.mymethods():
                    fake.locals[method.name] = [method]
                new_targets.append(fake.instantiate_class())
                dunder_members[local] = fake
            node.locals[local] = new_targets
        members = nodes.Dict(parent=node)
        members.postinit([(nodes.Const(k, parent=members),
                           nodes.Name(v.name, parent=members))
                          for k, v in dunder_members.items()])
        node.locals["__members__"] = [members]
        # The enum.Enum class itself defines two @DynamicClassAttribute data-descriptors
        # "name" and "value" (which we override in the mocked class for each enum member
        # above). When dealing with inference of an arbitrary instance of the enum
        # class, e.g. in a method defined in the class body like:
        #     class SomeEnum(enum.Enum):
        #         def method(self):
        #             self.name  # <- here
        # In the absence of an enum member called "name" or "value", these attributes
        # should resolve to the descriptor on that particular instance, i.e. enum member.
        # For "value", we have no idea what that should be, but for "name", we at least
        # know that it should be a string, so infer that as a guess.
        if "name" not in target_names:
            code = dedent("""
            @property
            def name(self):
                return ''
            """)
            name_dynamicclassattr = AstroidBuilder(
                AstroidManager()).string_build(code)["name"]
            node.locals["name"] = [name_dynamicclassattr]
        break
    return node