def _check_ignore_assignment_expr_suggestion(node: nodes.If, name: Optional[str]) -> bool: """Return True if suggestion for assignment expr should be ignored. E.g., in cases where a match statement would be a better fit (multiple conditions). """ if isinstance(node.test, nodes.Compare): next_if_node: Optional[nodes.If] = None next_sibling = node.next_sibling() if len(node.orelse) == 1 and isinstance(node.orelse[0], nodes.If): # elif block next_if_node = node.orelse[0] elif isinstance(next_sibling, nodes.If): # separate if block next_if_node = next_sibling if ( # pylint: disable=too-many-boolean-expressions next_if_node is not None and (isinstance(next_if_node.test, nodes.Compare) and isinstance(next_if_node.test.left, nodes.Name) and next_if_node.test.left.name == name or isinstance(next_if_node.test, nodes.Name) and next_if_node.test.name == name)): return True return False
def visit_if(self, node: nodes.If) -> None: self._current_block.add_statement(node.test) node.cfg_block = self._current_block old_curr = self._current_block # Handle "then" branch. then_block = self._current_cfg.create_block(old_curr) self._current_block = then_block for child in node.body: child.accept(self) end_if = self._current_block # Handle "else" branch. if node.orelse == []: end_else = old_curr else: else_block = self._current_cfg.create_block(old_curr) self._current_block = else_block for child in node.orelse: child.accept(self) end_else = self._current_block after_if_block = self._current_cfg.create_block() self._current_cfg.link_or_merge(end_if, after_if_block) self._current_cfg.link_or_merge(end_else, after_if_block) self._current_block = after_if_block
def _check_consider_using_assignment_expr(self, node: nodes.If) -> None: """Check if an assignment expression (walrus operator) can be used. For example if an assignment is directly followed by an if statement: >>> x = 2 >>> if x: >>> ... Can be replaced by: >>> if (x := 2): >>> ... Note: Assignment expressions were added in Python 3.8 """ # Check if `node.test` contains a `Name` node node_name: Optional[nodes.Name] = None if isinstance(node.test, nodes.Name): node_name = node.test elif (isinstance(node.test, nodes.UnaryOp) and node.test.op == "not" and isinstance(node.test.operand, nodes.Name)): node_name = node.test.operand elif (isinstance(node.test, nodes.Compare) and isinstance(node.test.left, nodes.Name) and len(node.test.ops) == 1): node_name = node.test.left else: return # Make sure the previous node is an assignment to the same name # used in `node.test`. Furthermore, ignore if assignment spans multiple lines. prev_sibling = node.previous_sibling() if CodeStyleChecker._check_prev_sibling_to_if_stmt( prev_sibling, node_name.name): # Check if match statement would be a better fit. # I.e. multiple ifs that test the same name. if CodeStyleChecker._check_ignore_assignment_expr_suggestion( node, node_name.name): return # Build suggestion string. Check length of suggestion # does not exceed max-line-length-suggestions test_str = node.test.as_string().replace( node_name.name, f"({node_name.name} := {prev_sibling.value.as_string()})", 1, ) suggestion = f"if {test_str}:" if (node.col_offset is not None and len(suggestion) + node.col_offset > self._max_length or len(suggestion) > self._max_length): return self.add_message( "consider-using-assignment-expr", node=node_name, args=(suggestion, ), )
def visit_if(self, node: nodes.If) -> None: body_ends_with_if = isinstance( node.body[-1], nodes.If ) and self._has_no_else_clause(node.body[-1]) if node.has_elif_block() and body_ends_with_if: self.add_message("confusing-consecutive-elif", node=node.orelse[0])