def _check_range_len(self, node: ast.Call) -> None: if not isinstance(nodes.get_parent(node), ForNodes): return if not functions.given_function_called(node, {'range'}): return args_len = len(node.args) is_one_arg_range = ( args_len == 1 and isinstance(node.args[0], ast.Call) and functions.given_function_called(node.args[0], {'len'}) ) is_two_args_range = ( self._is_multiple_args_range_with_len(node) and args_len == 2 ) # for three args add violation # only if `step` arg do not equals 1 or -1 step_arg = args_len == 3 and operators.unwrap_unary_node(node.args[2]) is_three_args_range = ( self._is_multiple_args_range_with_len(node) and args_len == 3 and isinstance(step_arg, ast.Num) and abs(step_arg.n) == 1 ) if any([is_one_arg_range, is_two_args_range, is_three_args_range]): self.add_violation(ImplicitEnumerateViolation(node))
def fix_line_number(tree: ast.AST) -> ast.AST: """ Adjusts line number for some nodes. They are set incorrectly for some collections. It might be either a bug or a feature. We do several checks here, to be sure that we won't get an incorrect line number. But, we basically check if there's a parent, so we can compare and adjust. Example:: print(( # should start from here 1, 2, 3, # actually starts from here )) """ affected = (ast.Tuple,) for node in ast.walk(tree): if isinstance(node, affected): parent_lineno = getattr(get_parent(node), 'lineno', None) if parent_lineno and parent_lineno < node.lineno: node.lineno = node.lineno - 1 return tree
def _check_last_return_in_function(self, node: ast.Return) -> None: parent = get_parent(node) if not isinstance(parent, FunctionNodes): return returns = len(tuple(filter( lambda return_node: return_node.value is not None, walk.get_subnodes_by_type(parent, ast.Return), ))) last_value_return = ( len(parent.body) > 1 and returns < 2 and isinstance(node.value, ast.NameConstant) and node.value.value is None ) one_return_with_none = ( returns == 1 and isinstance(node.value, ast.NameConstant) and node.value.value is None ) if node.value is None or last_value_return or one_return_with_none: self.add_violation(InconsistentReturnViolation(node))
def _check_method(self, node: AnyFunctionDef) -> None: if decorators.has_overload_decorator(node): return # we don't count `@overload` methods parent = get_parent(node) if isinstance(parent, ast.ClassDef): self._methods[parent] += 1
def _check_type_compare(self, node: ast.Call) -> None: function_name = functions.given_function_called(node, {'type'}) if not function_name: return if isinstance(nodes.get_parent(node), ast.Compare): self.add_violation(TypeCompareViolation(node))
def _check_last_return_in_function(self, node: ast.Return) -> None: parent = get_parent(node) if not isinstance(parent, (ast.FunctionDef, ast.AsyncFunctionDef)): return if node is parent.body[-1] and node.value is None: self.add_violation(InconsistentReturnViolation(node))
def _check_variable_definitions(self, node: ast.withitem) -> None: if node.optional_vars is None: return if not is_valid_block_variable_definition(node.optional_vars): self.add_violation( ContextManagerVariableDefinitionViolation(get_parent(node)), )
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 visit_withitem(self, node: ast.withitem) -> None: """Visits ``with`` and ``async with`` declarations.""" if node.optional_vars: parent = cast(AnyWith, get_parent(node)) names = defs.extract_names(node.optional_vars) self._scope(parent, names, is_local=False) self._outer_scope(parent, names) self.generic_visit(node)
def visit_withitem(self, node: ast.withitem) -> None: """Visits ``with`` and ``async with`` declarations.""" if node.optional_vars: self._add_to_scope( cast(AnyWith, get_parent(node)), defs.extract_names(node.optional_vars), ) self.generic_visit(node)
def visit_JoinedStr(self, node: ast.JoinedStr) -> None: """Forbids use of ``f`` strings and too complex ``f`` strings.""" if not isinstance(nodes.get_parent(node), ast.FormattedValue): # We don't allow `f` strings by default, # But, we need this condition to make sure that this # is not a part of complex string format like `f"Count={count:,}"`: self._check_complex_formatted_string(node) self.add_violation(consistency.FormattedStringViolation(node)) self.generic_visit(node)
def _check_string_constant(self, node: ast.Str) -> None: parent = nodes.get_parent(node) if isinstance(parent, _AnnNodes) and parent.annotation == node: return # it is argument or variable annotation if isinstance(parent, FunctionNodes) and parent.returns == node: return # it is return annotation self._string_constants[node.s] += 1
def _check_nested_classes(self, node: ast.ClassDef) -> None: parent = get_parent(node) is_inside_class = isinstance(parent, ast.ClassDef) is_inside_function = isinstance(parent, self._function_nodes) if is_inside_class and node.name not in NESTED_CLASSES_WHITELIST: self.add_violation(NestedClassViolation(node, text=node.name)) elif is_inside_function: self.add_violation(NestedClassViolation(node, text=node.name))
def visit_withitem(self, node: ast.withitem) -> None: """Checks that we cannot create explicit unused context variables.""" if node.optional_vars: self._check_assign_unused( cast(ast.AST, nodes.get_parent(node)), name_nodes.get_variables_from_node(node.optional_vars), is_local=True, ) self.generic_visit(node)
def _check_metadata(self, node: AnyAssign) -> None: if not isinstance(nodes.get_parent(node), ast.Module): return targets = get_assign_targets(node) for target_node in targets: target_node_id = _get_name_from_node(target_node) if target_node_id in MODULE_METADATA_VARIABLES_BLACKLIST: self.add_violation( WrongModuleMetadataViolation(node, text=target_node_id), )
def _check_simplifiable_returning_parent(self, node: ast.If) -> None: parent = get_parent(node) if isinstance(parent, _ELSE_NODES): body = parent.body + parent.orelse else: body = getattr(parent, 'body', [node]) next_index_in_parent = body.index(node) + 1 if keywords.next_node_returns_bool(body, next_index_in_parent): self.add_violation(SimplifiableReturningIfViolation(node))
def is_same_try_except_cases(node: ast.AST, names: Set[str]) -> bool: """Same names in different ``except`` blocks are not counted.""" if not isinstance(node, ast.ExceptHandler): return False for except_handler in getattr(get_parent(node), 'handlers', []): if except_handler.name and except_handler.name == node.name: if except_handler is not node: return True return False
def _is_type_annotation(self, node: ast.BinOp) -> bool: parent_node = get_parent(node) if isinstance(parent_node, (ast.AnnAssign, ast.arg)): return node is parent_node.annotation if isinstance(parent_node, ast.FunctionDef): return node is parent_node.returns return False
def _check_open_call_context(self, node: ast.Call) -> None: function_name = functions.given_function_called(node, {'open'}) if not function_name: return if isinstance(nodes.get_parent(node), ast.withitem): # We do not care about `with` or `async with` - both are fine. return self.add_violation(OpenWithoutContextManagerViolation(node))
def _check_modulo_patterns( self, node: AnyText, text_data: Optional[str], ) -> None: parent = nodes.get_parent(node) if parent and strings.is_doc_string(parent): return # we allow `%s` in docstrings: they cannot be formatted. if self._modulo_string_pattern.search(text_data): self.add_violation(consistency.ModuloStringFormatViolation(node))
def _check_members_count(self, node: _ModuleMembers) -> None: """This method increases the number of module members.""" if functions.is_method(getattr(node, 'function_type', '')): return if isinstance(node, FunctionNodes): if decorators.has_overload_decorator(node): return # We don't count `@overload` defs as real defs if isinstance(get_parent(node), ast.Module): self._public_items_count += 1
def count_unary_operator( node: ast.AST, operator: AnyUnaryOp, amount: int = 0, ) -> int: """Returns amount of unary operators matching input.""" parent = get_parent(node) if parent is None or not isinstance(parent, ast.UnaryOp): return amount if isinstance(parent.op, operator): return count_unary_operator(parent, operator, amount + 1) return count_unary_operator(parent, operator, amount)
def _check_descriptor_decorators(self, node: AnyFunctionDef) -> None: if isinstance(nodes.get_parent(node), ast.ClassDef): return # classes can contain descriptors descriptor_decorators = [ decorator.id in self._descriptor_decorators for decorator in node.decorator_list if isinstance(decorator, ast.Name) ] if any(descriptor_decorators): self.add_violation(oop.WrongDescriptorDecoratorViolation(node), )
def visit_alias(self, node: ast.alias) -> None: """ Visits aliases from ``import`` and ``from ... import`` block nodes. Raises: BlockAndLocalOverlapViolation """ parent = cast(AnyImport, get_parent(node)) import_name = {node.asname} if node.asname else {node.name} self._scope(parent, import_name, is_local=False) self._outer_scope(parent, import_name) self.generic_visit(node)
def _check_expression( self, node: ast.Expr, is_first: bool = False, ) -> None: if isinstance(node.value, self._have_effect): return if is_first and strings.is_doc_string(node): if isinstance(nodes.get_parent(node), self._have_doc_strings): return self.add_violation(StatementHasNoEffectViolation(node))
def _post_visit(self) -> None: previous_line: Optional[int] = None previous_parent: Optional[ast.AST] = None for line, node in self._yield_locations.items(): parent = get_parent(node) if previous_line is not None: if line - 1 == previous_line and previous_parent == parent: self.add_violation(ConsecutiveYieldsViolation(node.value)) break previous_line = line previous_parent = parent
def visit_alias(self, node: ast.alias) -> None: """ Visits aliases from ``import`` and ``from ... import`` block nodes. Raises: BlockAndLocalOverlapViolation """ import_name = node.asname if node.asname else node.name self._scope( cast(AnyImport, get_parent(node)), {import_name}, is_local=False, ) self.generic_visit(node)
def visit_withitem(self, node: ast.withitem) -> None: """ Visits ``with`` and ``async with`` declarations. Raises: BlockAndLocalOverlapViolation """ if node.optional_vars: self._scope( cast(AnyWith, get_parent(node)), _extract_names(node.optional_vars), is_local=False, ) self.generic_visit(node)
def get_parent_ignoring_unary(node: ast.AST) -> Optional[ast.AST]: """ Returns real parent ignoring proxy unary parent level. What can go wrong? 1. Number can be negative: ``x = -1``, so ``1`` has ``UnaryOp`` as parent, but should return ``Assign`` 2. Some values can be negated: ``x = --some``, so ``some`` has ``UnaryOp`` as parent, but should return ``Assign`` """ parent = get_parent(node) if parent is None or not isinstance(parent, ast.UnaryOp): return parent return get_parent_ignoring_unary(parent)
def _find_context( node: ast.AST, contexts: Tuple[Type[ast.AST], ...], ) -> Optional[ast.AST]: """ We changed how we find and assign contexts in 0.8.1 version. It happened because of the bug #520 See: https://github.com/wemake-services/wemake-python-styleguide/issues/520 """ parent = get_parent(node) if parent is None: return None elif isinstance(parent, contexts): return parent return _find_context(parent, contexts)