def is_same_slice( iterable: str, target: str, node: ast.Subscript, ) -> bool: """Used to tell when slice is identical to some pair of name/index.""" return (source.node_to_string(node.value) == iterable and source.node_to_string(get_slice_expr(node)) == target)
def is_same_slice( iterable: str, target: str, node: ast.Subscript, ) -> bool: """Used to tell when slice is identical to some pair of name/index.""" return (source.node_to_string(node.value) == iterable and isinstance(node.slice, ast.Index) and # mypy is unhappy source.node_to_string(node.slice.value) == target)
def _check_implicit_items(self, node: ast.For) -> None: iterable = source.node_to_string(node.iter) target = source.node_to_string(node.target) for sub in ast.walk(node): has_violation = (isinstance(sub, ast.Subscript) and not self._is_assigned_target(sub) and slices.is_same_slice(iterable, target, sub)) if has_violation: self.add_violation(ImplicitItemsIteratorViolation(node)) break
def get_all_exception_names(node: ast.Try) -> List[str]: """Returns a list of all exceptions names in ``ast.Try``.""" exceptions: List[str] = [] for exc_handler in node.handlers: # There might be complex things hidden inside an exception type, # so we want to get the string representation of it: if isinstance(exc_handler.type, ast.Name): exceptions.append(source.node_to_string(exc_handler.type)) elif isinstance(exc_handler.type, ast.Tuple): exceptions.extend([ source.node_to_string(node) for node in exc_handler.type.elts ]) return exceptions
def _check_implicit_get(self, node: ast.If) -> None: if not isinstance(node.test, ast.Compare): return if not isinstance(node.test.ops[0], ast.In): return checked_key = source.node_to_string(node.test.left) checked_collection = source.node_to_string(node.test.comparators[0]) for sub in ast.walk(node): if not isinstance(sub, ast.Subscript): continue if slices.is_same_slice(checked_collection, checked_key, sub): self.add_violation(refactoring.ImplicitDictGetViolation(sub))
def is_property_setter(node: ast.AST, _=None): """Check that function decorated with `property.setter`.""" if isinstance(node, FunctionNodes): for decorator in node.decorator_list: if node_to_string(decorator) in _property_exceptions: return True return False
def _check_set_elements( self, node: Union[ast.Set, ast.Dict], keys_or_elts: _HashItems, ) -> None: elements: List[str] = [] element_values = [] for set_item in keys_or_elts: if set_item is None: continue # happens for `{**a}` real_item = operators.unwrap_unary_node(set_item) if isinstance(real_item, self._elements_in_sets): # Similar look: node_repr = source.node_to_string(set_item) elements.append(node_repr.strip().strip('(').strip(')')) real_item = operators.unwrap_starred_node(real_item) # Non-constant nodes raise ValueError, # unhashables raise TypeError: with suppress(ValueError, TypeError): # Similar value: element_values.append( safe_eval.literal_eval_with_names(real_item, ) if isinstance( real_item, self._elements_to_eval, ) else set_item, ) self._report_set_elements(node, elements, element_values)
def is_function_overload(node: ast.AST) -> bool: """Check that function decorated with `typing.overload`.""" if isinstance(node, FunctionNodes): for decorator in node.decorator_list: if node_to_string(decorator) in _overload_exceptions: return True return False
def is_property_setter(node: ast.AST) -> bool: """Check that function decorated with ``@property.setter``.""" if isinstance(node, FunctionNodes): for decorator in node.decorator_list: if node_to_string(decorator) in _PROPERTY_EXCEPTIONS: return True return False
def _check_duplicate_exceptions(self, node: ast.Try) -> None: exceptions: List[str] = [] for exc_handler in node.handlers: # There might be complex things hidden inside an exception type, # so we want to get the string representation of it: if isinstance(exc_handler.type, ast.Name): exceptions.append(source.node_to_string(exc_handler.type)) elif isinstance(exc_handler.type, ast.Tuple): exceptions.extend([ source.node_to_string(node) for node in exc_handler.type.elts ]) for exc_name, count in Counter(exceptions).items(): if count > 1: self.add_violation( DuplicateExceptionViolation(node, text=exc_name), )
def _add_expression(self, node: ast.AST) -> None: if any(ignore(node) for ignore in self._ignore_predicates): return source_code = source.node_to_string(node) self._module_expressions[source_code].append(node) maybe_function = walk.get_closest_parent(node, FunctionNodes) if maybe_function is not None: self._function_expressions[maybe_function][source_code].append( node, )
def _is_call_ignored(self, node: ast.Call) -> bool: call = source.node_to_string(node.func) func_called = functions.given_function_called( node, self._functions.keys(), ) return bool( func_called and len(node.args) == self._functions[func_called], ) or any( call.endswith(post) for post in self._postfixes if len(node.args) == self._postfixes[post])
def _check_len_call(self, node: ast.Subscript) -> None: node_slice = get_slice_expr(node) is_len_call = (isinstance(node_slice, ast.BinOp) and isinstance(node_slice.op, ast.Sub) and self._is_wrong_len( node_slice, source.node_to_string(node.value), )) if is_len_call: self.add_violation( refactoring.ImplicitNegativeIndexViolation(node), )
def _check_implicit_in(self, node: ast.BoolOp) -> None: variables: List[Set[str]] = [] for cmp in node.values: if not isinstance(cmp, ast.Compare) or len(cmp.ops) != 1: return if not isinstance(cmp.ops[0], self._allowed[node.op.__class__]): return variables.append({source.node_to_string(cmp.left)}) for duplicate in _get_duplicate_names(variables): self.add_violation( ImplicitInConditionViolation(node, text=duplicate), )
def _duplicated_isinstance_call(node: ast.BoolOp) -> List[str]: counter: DefaultDict[str, int] = defaultdict(int) for call in node.values: if not isinstance(call, ast.Call) or len(call.args) != 2: continue if not given_function_called(call, {'isinstance'}): continue isinstance_object = source.node_to_string(call.args[0]) counter[isinstance_object] += 1 return [node_name for node_name, count in counter.items() if count > 1]
def _get_all_names( self, node: ast.BoolOp, ) -> List[str]: # We need to make sure that we do not visit # one chained `BoolOp` elements twice: self._same_nodes.append(node) names = [] for operand in node.values: if isinstance(operand, ast.BoolOp): names.extend(self._get_all_names(operand)) else: names.append(source.node_to_string(operand)) return names
def _find_lower_upper_bounds( self, comparison_node: ast.Compare, ) -> None: left_operand = comparison_node.left comparators = zip(comparison_node.ops, comparison_node.comparators) for operator, right_operand in comparators: for operand in (left_operand, right_operand): self._mutate( comparison_node, operator, source.node_to_string(operand), operand is left_operand, ) left_operand = right_operand
def _is_simplifiable_assign( self, node_body: List[ast.stmt], ) -> Optional[str]: wrong_length = len(node_body) != 1 if wrong_length or not isinstance(node_body[0], AssignNodes): return None if not isinstance(node_body[0].value, ast.NameConstant): return None if node_body[0].value.value is None: return None targets = get_assign_targets(node_body[0]) if len(targets) != 1: return None return source.node_to_string(targets[0])
def given_function_called(node: Call, to_check: Container[str]) -> str: """ Returns function name if it is called and contained in the container. >>> import ast >>> module = ast.parse('print(123, 456)') >>> given_function_called(module.body[0].value, ['print']) 'print' >>> given_function_called(module.body[0].value, ['adjust']) '' """ function_name = source.node_to_string(node.func) if function_name in to_check: return function_name return ''
def given_function_called( node: Call, to_check: Container[str], *, split_modules: bool = False, ) -> str: """ Returns function name if it is called and contained in the container. If `split_modules`, takes the modules or objects into account. Otherwise, it only cares about the function's name. """ function_name = source.node_to_string(node.func) if split_modules: function_name = function_name.split('.')[-1] if function_name in to_check: return function_name return ''
def _check_implicit_in(self, node: ast.BoolOp) -> None: allowed_ops: Dict[Type[ast.boolop], Type[ast.cmpop]] = { ast.And: ast.NotEq, ast.Or: ast.Eq, } variables: List[Set[str]] = [] for compare in node.values: if not isinstance(compare, ast.Compare) or len(compare.ops) != 1: return if not isinstance(compare.ops[0], allowed_ops[node.op.__class__]): return variables.append({source.node_to_string(compare.left)}) for duplicate in _get_duplicate_names(variables): self.add_violation( ImplicitInConditionViolation(node, text=duplicate), )
def _add_expression(self, node: ast.AST) -> None: ignore_predicates = [ self._is_decorator, self._is_self_method, self._is_annotation, # 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. _is_class_context, ] if any(ignore(node) for ignore in ignore_predicates): return source_code = source.node_to_string(node) self._module_expressions[source_code].append(node) maybe_function = walk.get_closest_parent(node, FunctionNodes) if maybe_function is not None: self._function_expressions[maybe_function][source_code].append( node, )
def _slot_item_name(self, node: ast.AST) -> Optional[str]: if isinstance(node, ast.Str): return node.s if isinstance(node, ast.Starred): return source.node_to_string(node) return None
def _get_annotation(self, node: ast.AST) -> str: """Smartly turns annotation node to string.""" full_annotation = source.node_to_string(node) for prefix in self._possible_prefixes: full_annotation = full_annotation.replace(prefix, '') return full_annotation
def _is_wrong_len(self, node: ast.BinOp, element: str) -> bool: return (isinstance(node.left, ast.Call) and bool(functions.given_function_called(node.left, {'len'})) and source.node_to_string(node.left.args[0]) == element)