def _ensure_super_context(self, node: ast.Call) -> None: parent_context = nodes.get_context(node) if isinstance(parent_context, FunctionNodes): grand_context = nodes.get_context(parent_context) if isinstance(grand_context, ast.ClassDef): return self.add_violation( WrongSuperCallViolation(node, text='not inside method'), )
def _ensure_super_context(self, node: ast.Call) -> None: parent_context = nodes.get_context(node) if isinstance(parent_context, (ast.FunctionDef, ast.AsyncFunctionDef)): grand_context = nodes.get_context(parent_context) if isinstance(grand_context, ast.ClassDef): return self.add_violation( IncorrectSuperCallViolation(node, text='not inside method'), )
def _check_bound_methods(self, node: types.AnyFunctionDef) -> None: node_context = nodes.get_context(node) if not isinstance(node_context, ast.ClassDef): return if not functions.get_all_arguments(node): self.add_violation( oop.MethodWithoutArgumentsViolation(node, text=node.name), ) if node.name in constants.MAGIC_METHODS_BLACKLIST: self.add_violation( oop.BadMagicMethodViolation(node, text=node.name), ) is_async = isinstance(node, ast.AsyncFunctionDef) if is_async and access.is_magic(node.name): if node.name in constants.ASYNC_MAGIC_METHODS_BLACKLIST: self.add_violation( oop.AsyncMagicMethodViolation(node, text=node.name), ) self._check_useless_overwritten_methods( node, class_name=node_context.name, )
def _ensure_super_context(self, node: ast.Call) -> None: parent_context = nodes.get_context(node) parent_node = nodes.get_parent(node) attr = getattr(parent_node, 'attr', None) parent_name = getattr(parent_context, 'name', None) if attr and parent_name and attr != parent_name: self.add_violation(oop.WrongSuperCallAccessViolation(node), ) if isinstance(parent_context, FunctionNodes): grand_context = nodes.get_context(parent_context) if isinstance(grand_context, ast.ClassDef): return self.add_violation( oop.WrongSuperCallViolation(node, text='not inside method'), )
def find_getters_and_setters(node: ast.ClassDef) -> Iterable[AnyFunctionDef]: """Returns nodes of all getter or setter methods.""" for sub in ast.walk(node): is_correct_context = nodes.get_context(sub) is node if isinstance(sub, FunctionNodes) and is_correct_context: if sub.name[:GETTER_LENGTH] in _GetterSetterPrefixes: yield sub
def _check_variable_usage(self, node: ast.Name) -> None: context = cast(ast.AST, get_context(node)) blocks = self._block_variables[context][node.id] if all(is_contained_by(node, block) for block in blocks): return self.add_violation( ControlVarUsedAfterBlockViolation(node, text=node.id), )
def _get_annotated_class_attribute( node: ast.ClassDef, subnode: ast.AST, ) -> Optional[AnyAssign]: return subnode if ( nodes.get_context(subnode) is node and (getattr(subnode, 'value', None) and isinstance(subnode, AssignNodes)) or isinstance(subnode, ast.AnnAssign)) else None
def _is_correct_return_node( self, node: AnyFunctionDef, sub_node: ast.AST, ) -> bool: if get_context(sub_node) != node: return False return isinstance(sub_node, self._checking_nodes)
def _check_magic_module_functions(self, node: ast.FunctionDef) -> None: if self.options.i_control_code: if not isinstance(get_context(node), ast.Module): return if node.name in constants.MAGIC_MODULE_NAMES_BLACKLIST: self.add_violation( BadMagicModuleFunctionViolation(node, text=node.name), )
def _check_nested_function(self, node: AnyFunctionDef) -> None: is_inside_function = isinstance(get_context(node), FunctionNodes) is_direct = isinstance(get_parent(node), FunctionNodes) is_bad = is_direct and node.name not in NESTED_FUNCTIONS_WHITELIST if is_bad or (is_inside_function and not is_direct): self.add_violation(NestedFunctionViolation(node, text=node.name))
def _get_class_attribute( node: ast.ClassDef, subnode: ast.AST, ) -> Optional[AnyAssign]: return subnode if ( isinstance(subnode, AssignNodes) and nodes.get_context(subnode) == node and getattr(subnode, 'value', None) ) else None
def has_recursive_calls(func: AnyFunctionDef) -> bool: """ Determines whether function has recursive calls or not. Does not work for methods. """ if isinstance(get_context(func), ClassDef): return _check_method_recursion(func) return _check_function_recursion(func)
def is_class_context(node: ast.AST) -> bool: """ Detects if a node is inside a class context. We use this predicate because classes have quite complex DSL to be created: like django-orm, attrs, and dataclasses. And these DSLs are built using attributes and calls. """ return isinstance(nodes.get_context(node), ast.ClassDef)
def _is_reassignment_edge_case(self, node: AnyAssign) -> bool: # This is not a variable, but a class property if isinstance(nodes.get_context(node), ast.ClassDef): return True # It means that someone probably modifies original value # of the variable using some unary operation, e.g. `a = not a` # See #2189 return isinstance(node.value, ast.UnaryOp)
def check_attribute_name(self, node: ast.ClassDef) -> None: top_level_assigns = [ sub for sub in ast.walk(node) if isinstance(sub, AssignNodes) and nodes.get_context(sub) is node ] for assignment in top_level_assigns: for target in get_assign_targets(assignment): self._ensure_case(target)
def _check_implicit_yield_from(self, node: AnyFor) -> None: if isinstance(nodes.get_context(node), ast.AsyncFunctionDef): # Python does not support 'yield from' inside async functions return is_implicit_yield_from = (len(node.body) == 1 and isinstance(node.body[0], ast.Expr) and isinstance(node.body[0].value, ast.Yield)) if is_implicit_yield_from: self.add_violation(ImplicitYieldFromViolation(node))
def _check_nested_classes(self, node: ast.ClassDef) -> None: parent_context = get_context(node) is_inside_class = isinstance(parent_context, ast.ClassDef) is_bad = is_inside_class and node.name not in NESTED_CLASSES_WHITELIST is_inside_function = isinstance(parent_context, FunctionNodes) if is_bad or is_inside_function: self.add_violation(NestedClassViolation(node, text=node.name))
def _check_bound_methods(self, node: types.AnyFunctionDef) -> None: node_context = get_context(node) if not isinstance(node_context, ast.ClassDef): return if not get_all_arguments(node): self.add_violation( MethodWithoutArgumentsViolation(node, text=node.name), ) if node.name in constants.MAGIC_METHODS_BLACKLIST: self.add_violation(BadMagicMethodViolation(node, text=node.name))
def _check_if_needs_except(self, node: ast.Try) -> None: if not node.finalbody or node.handlers: return context = nodes.get_context(node) if isinstance(context, FunctionNodes) and context.decorator_list: # This is used inside a decorated function, it might be the only # way to handle this situation. Eg: ``@contextmanager`` return self.add_violation(UselessFinallyViolation(node))
def _build_outer_context(self) -> Set[str]: outer_names: Set[str] = set() context = self._context while True: context = cast(ContextNodes, get_context(context)) outer_names = outer_names.union(self._scopes[context]) if not context: break return outer_names
def _check_nested_classes(self, node: ast.ClassDef) -> None: parent_context = get_context(node) is_inside_class = isinstance(parent_context, ast.ClassDef) is_whitelisted = node.name in self.options.nested_classes_whitelist is_bad = is_inside_class and not is_whitelisted is_inside_function = isinstance(parent_context, FunctionNodes) if is_bad or is_inside_function: self.add_violation(NestedClassViolation(node, text=node.name))
def _check_method_order(self, node: ast.ClassDef) -> None: method_nodes: List[str] = [] for subnode in ast.walk(node): if isinstance(subnode, FunctionNodes): if nodes.get_context(subnode) == node: method_nodes.append(subnode.name) ideal = sorted(method_nodes, key=self._ideal_order, reverse=True) for existing_order, ideal_order in zip(method_nodes, ideal): if existing_order != ideal_order: self.add_violation(consistency.WrongMethodOrderViolation(node)) return
def _check_slots(self, node: types.AnyAssign) -> None: if not isinstance(nodes.get_context(node), ast.ClassDef): return if not self._contains_slots_assign(node): return if not isinstance(node.value, self._whitelisted_slots_nodes): self.add_violation(oop.WrongSlotsViolation(node)) return if isinstance(node.value, ast.Tuple): self._count_slots_items(node, node.value)
def _check_slots(self, node: AnyAssign) -> None: if not isinstance(get_context(node), ast.ClassDef): return if not self._contains_slots_assign(node): return if isinstance(node.value, self._blacklisted_slots_nodes): self.add_violation(IncorrectSlotsViolation(node)) return if isinstance(node.value, ast.Tuple): self._count_slots_items(node, node.value)
def _check_mutable_constant(self, node: AnyAssign) -> None: if not isinstance(get_context(node), ast.Module): return targets = ([node.target] if isinstance(node, ast.AnnAssign) else node.targets) for target in targets: if not isinstance(target, ast.Name) or not is_constant(target.id): continue if isinstance(node.value, self._mutable_nodes): self.add_violation(MutableModuleConstantViolation(target))
def does_shadow_builtin(node: ast.AST) -> bool: """ We allow attributes and class-level builtin overrides. Like: ``self.list = []`` or ``def map(self, function):`` Why? Because they cannot harm you since they do not shadow the real builtin. """ return ( not isinstance(node, ast.Attribute) and not isinstance(nodes.get_context(node), ast.ClassDef) )
def returning_nodes( node: ast.AST, returning_type: Union[Type[ast.Return], Type[ast.Yield]], ) -> Tuple[_ReturningNodes, bool]: """Returns ``return`` or ``yield`` nodes with values.""" returns: _ReturningNodes = [] has_values = False for sub_node in ast.walk(node): context_node = get_context(sub_node) if isinstance(sub_node, returning_type) and context_node == node: if sub_node.value: # type: ignore has_values = True returns.append(sub_node) # type: ignore return returns, has_values
def returning_nodes( node: ast.AST, returning_type, ) -> Tuple[List[ast.Return], bool]: """Returns ``return`` or ``yield`` nodes with values.""" returns: List[ast.Return] = [] has_values = False for sub_node in ast.walk(node): context_node = get_context(sub_node) if isinstance(sub_node, returning_type) and context_node == node: if sub_node.value: has_values = True returns.append(sub_node) return returns, has_values
def _get_attributes( self, node: ast.ClassDef, ) -> Tuple[List[AnyAssign], List[ast.Attribute]]: class_attributes = [] instance_attributes = [] for child in ast.walk(node): if isinstance(child, ast.Attribute): if isinstance(child.ctx, ast.Store): instance_attributes.append(child) if isinstance(child, AssignNodes) and get_context(child) == node: if child.value is not None: # Not: `a: int` class_attributes.append(child) return class_attributes, instance_attributes
def _check_variable_usage(self, node: ast.Name) -> None: context = cast(ast.AST, get_context(node)) blocks = self._block_variables[context][node.id] is_contained_block_var = any( is_contained_by(node, block) for block in blocks) # Restrict the use of block variables with the same name to # the same type of block - either `for` or `with`. is_same_type_block = all( isinstance(block, ForNodes) for block in blocks) or all( isinstance(block, WithNodes) for block in blocks) # Return if not a block variable or a contained block variable. if not blocks or (is_contained_block_var and is_same_type_block): return self.add_violation( ControlVarUsedAfterBlockViolation(node, text=node.id), )