def __extract_names_multi_assign(self, elements): # Add self vars. in tuple assignments, e.g. self.x, self.y = 1, 2 # Adds variables in tuple(s) in multiple assignments, e.g. a, (b, c) = 1, (2, 3) names: List[cst.Name] = [] i = 0 while i < len(elements): if match.matches( elements[i], match.Element(value=match.Name(value=match.DoNotCare()))): names.append(elements[i].value) elif match.matches( elements[i], match.Element(value=match.Attribute(attr=match.Name( value=match.DoNotCare())))): names.append(elements[i].value) elif match.matches( elements[i], match.Element(value=match.Tuple( elements=match.DoNotCare()))): elements.extend( match.findall( elements[i].value, match.Element(value=match.OneOf( match.Attribute(attr=match.Name( value=match.DoNotCare())), match.Name(value=match.DoNotCare()))))) i += 1 return names
class HttpRequestXReadLinesTransformer(BaseDjCodemodTransformer): """Replace `HttpRequest.xreadlines()` by iterating over the request.""" deprecated_in = DJANGO_2_0 removed_in = DJANGO_3_0 # This should be conservative and only apply changes to: # - variables called `request`/`req` # - `request`/`req` attributes (e.g `self.request`/`view.req`...) matcher = m.Call( func=m.Attribute( value=m.OneOf( m.Name(value="request"), m.Name(value="req"), m.Attribute(attr=m.Name(value="request")), m.Attribute(attr=m.Name(value="req")), ), attr=m.Name(value="xreadlines"), ) ) def leave_Call(self, original_node: Call, updated_node: Call) -> BaseExpression: if not isinstance(updated_node.func, Attribute): # A bit redundant with matcher below, # but this is to make type checker happy return super().leave_Call(original_node, updated_node) if m.matches(updated_node, self.matcher): return updated_node.func.value return super().leave_Call(original_node, updated_node)
def _test_import_from(self, node: ImportFrom) -> bool: return m.matches( node, m.ImportFrom(module=m.Attribute( value=m.Attribute(value=m.Name("django"), attr=m.Name(value="utils")), attr=m.Name("translation"), ), ), )
def test_extract_predicates(self) -> None: expression = cst.parse_expression("a + b[c], d(e, f * g)") nodes = m.extract( expression, m.Tuple(elements=[ m.Element( m.BinaryOperation( left=m.SaveMatchedNode(m.Name(), "left"))), m.Element( m.Call(func=m.SaveMatchedNode(m.Name(), "func") | m.SaveMatchedNode(m.Attribute(), "attr"))), ]), ) extracted_node_left = cst.ensure_type( cst.ensure_type(expression, cst.Tuple).elements[0].value, cst.BinaryOperation, ).left extracted_node_func = cst.ensure_type( cst.ensure_type(expression, cst.Tuple).elements[1].value, cst.Call).func self.assertEqual(nodes, { "left": extracted_node_left, "func": extracted_node_func }) expression = cst.parse_expression("a + b[c], d.z(e, f * g)") nodes = m.extract( expression, m.Tuple(elements=[ m.Element( m.BinaryOperation( left=m.SaveMatchedNode(m.Name(), "left"))), m.Element( m.Call(func=m.SaveMatchedNode(m.Name(), "func") | m.SaveMatchedNode(m.Attribute(), "attr"))), ]), ) extracted_node_left = cst.ensure_type( cst.ensure_type(expression, cst.Tuple).elements[0].value, cst.BinaryOperation, ).left extracted_node_attr = cst.ensure_type( cst.ensure_type(expression, cst.Tuple).elements[1].value, cst.Call).func self.assertEqual(nodes, { "left": extracted_node_left, "attr": extracted_node_attr })
def obf_function_name(self, func: cst.Call, updated_node): func_name = func.func # Обфускация имени функции if m.matches( func_name, m.Attribute()) and self.change_methods and self.can_rename( func_name.attr.value, 'cf'): func_name = cst.ensure_type(func_name, cst.Attribute) func_name = func_name.with_changes( attr=self.get_new_cst_name(func_name.attr)) updated_node = updated_node.with_changes(func=func_name) elif m.matches(func_name, m.Name()) and ( self.change_functions and self.can_rename(func_name.value, 'f') or self.change_classes and self.can_rename(func_name.value, 'c')): func_name = cst.ensure_type(func_name, cst.Name) func_name = self.get_new_cst_name(func_name.value) updated_node = updated_node.with_changes(func=func_name) else: pass return updated_node
def leave_AnnAssign( self, original_node: cst.AnnAssign, updated_node: cst.AnnAssign ) -> Union[cst.BaseSmallStatement, cst.RemovalSentinel]: # It handles a special case where a type-annotated variable has not initialized, e.g. foo: str # This case will be converted to foo = ... so that nodes traversal won't encounter exceptions later on if match.matches( original_node, match.AnnAssign( target=match.Name(value=match.DoNotCare()), annotation=match.Annotation(annotation=match.DoNotCare()), value=None)): updated_node = cst.Assign( targets=[cst.AssignTarget(target=original_node.target)], value=cst.Ellipsis()) # Handles type-annotated class attributes that has not been initialized, e.g. self.foo: str elif match.matches( original_node, match.AnnAssign( target=match.Attribute(value=match.DoNotCare()), annotation=match.Annotation(annotation=match.DoNotCare()), value=None)): updated_node = cst.Assign( targets=[cst.AssignTarget(target=original_node.target)], value=cst.Ellipsis()) else: updated_node = cst.Assign( targets=[cst.AssignTarget(target=original_node.target)], value=original_node.value) return updated_node
def _get_async_expr_replacement( self, node: cst.CSTNode) -> Optional[cst.CSTNode]: if m.matches(node, m.Call()): node = cast(cst.Call, node) return self._get_async_call_replacement(node) elif m.matches(node, m.Attribute()): node = cast(cst.Attribute, node) return self._get_async_attr_replacement(node) elif m.matches(node, m.UnaryOperation(operator=m.Not())): node = cast(cst.UnaryOperation, node) replacement_expression = self._get_async_expr_replacement( node.expression) if replacement_expression is not None: return node.with_changes(expression=replacement_expression) elif m.matches(node, m.BooleanOperation()): node = cast(cst.BooleanOperation, node) maybe_left = self._get_async_expr_replacement(node.left) maybe_right = self._get_async_expr_replacement(node.right) if maybe_left is not None or maybe_right is not None: left_replacement = maybe_left if maybe_left is not None else node.left right_replacement = (maybe_right if maybe_right is not None else node.right) return node.with_changes(left=left_replacement, right=right_replacement) return None
def visit_Call(self, node: cst.Call) -> None: if m.matches( node, m.Call(func=m.Attribute(value=m.SimpleString(), attr=m.Name(value="format"))), ): self.report(node)
def _func_name(self, func): if m.matches(func, m.Name()): return func.value elif m.matches(func, m.Attribute()): return func.attr.value else: return 'func'
class ConstantsRenameCommand(VisitorBasedCodemodCommand): DESCRIPTION: str = "Rename constants" matchers_map = { matchers.Attribute(value=matchers.Name(value="wx"), attr=matchers.Name(value=name)): renamed for name, renamed in [ ("WXK_PRIOR", "WXK_PAGEUP"), ("WXK_NEXT", "WXK_PAGEDOWN"), ("WXK_NUMPAD_PRIOR", "WXK_NUMPAD_PAGEUP"), ("WXK_NUMPAD_NEXT", "WXK_NUMPAD_PAGEDOWN"), ("OPEN", "FD_OPEN"), ("FILE_MUST_EXIST", "FD_FILE_MUST_EXIST"), ("TE_LINEWRAP", "TE_BESTWRAP"), ] } def leave_Attribute(self, original_node: cst.Attribute, updated_node: cst.Attribute) -> cst.Attribute: for matcher, renamed in self.matchers_map.items(): if matchers.matches(updated_node, matcher): return updated_node.with_changes(attr=cst.Name(value=renamed)) return updated_node
def visit_Assign(self, node: Assign) -> Optional[bool]: """Record variable name the `Library()` call is assigned to.""" if self.library_call_matcher and m.matches( node, m.Assign(value=self.library_call_matcher), ): # Visiting a `register = template.Library()` statement # Get all names on the left side of the assignment target_names = ( assign_target.target.value for assign_target in node.targets ) # Build the decorator matchers to look out for target_matchers = ( m.Decorator( decorator=m.Attribute( value=m.Name(name), attr=m.Name("assignment_tag"), ) ) for name in target_names ) # The final matcher should match if any of the decorator matchers matches self.context.scratch[self.ctx_key_decorator_matcher] = m.OneOf( *target_matchers ) return super().visit_Assign(node)
def process_variable(self, node: Union[cst.BaseExpression, cst.BaseAssignTargetExpression]): if m.matches(node, m.Name()): node = cst.ensure_type(node, cst.Name) if self.class_stack and not self.function_stack: self.class_stack[-1].variables.append(node.value) else: self.info.variables.append(node.value) elif m.matches(node, m.Attribute()): node = cst.ensure_type(node, cst.Attribute) splitted_attributes = split_attribute(node) if splitted_attributes[ 0] == 'self' and self.class_stack and self.function_stack and len( splitted_attributes) > 1: self.class_stack[-1].variables.append(splitted_attributes[1]) else: self.info.variables.append(splitted_attributes[0]) elif m.matches(node, m.Tuple()): node = cst.ensure_type(node, cst.Tuple) for el in node.elements: self.process_variable(el.value) else: pass
def new_obf_function_name(self, func: cst.Call): func_name = func.func # Обфускация имени функции if m.matches(func_name, m.Attribute()): func_name = cst.ensure_type(func_name, cst.Attribute) # Переименовывание имени if self.change_variables: func_name = func_name.with_changes( value=self.obf_universal(func_name.value, 'v')) # Переименовывание метода if self.change_methods: func_name = func_name.with_changes( attr=self.obf_universal(func_name.attr, 'cf')) elif m.matches(func_name, m.Name()): func_name = cst.ensure_type(func_name, cst.Name) if (self.change_functions or self.change_classes) and self.can_rename( func_name.value, 'c', 'f'): func_name = self.get_new_cst_name(func_name.value) else: pass func = func.with_changes(func=func_name) return func
def handle_any_string( self, node: Union[cst.SimpleString, cst.ConcatenatedString]) -> None: value = node.evaluated_value if value is None: return mod = cst.parse_module(value) extracted_nodes = m.extractall( mod, m.Name( value=m.SaveMatchedNode(m.DoNotCare(), "name"), metadata=m.MatchMetadataIfTrue( cst.metadata.ParentNodeProvider, lambda parent: not isinstance(parent, cst.Attribute), ), ) | m.SaveMatchedNode(m.Attribute(), "attribute"), metadata_resolver=MetadataWrapper(mod, unsafe_skip_copy=True), ) names = { cast(str, values["name"]) for values in extracted_nodes if "name" in values } | { name for values in extracted_nodes if "attribute" in values for name, _ in cst.metadata.scope_provider._gen_dotted_names( cast(cst.Attribute, values["attribute"])) } self.names.update(names)
def obf_function_args(self, func: cst.Call): new_args = [] func_root = func.func func_name = '' if m.matches(func_root, m.Name()): func_name = cst.ensure_type(func_root, cst.Name).value elif m.matches(func_root, m.Attribute()): func_name = split_attribute( cst.ensure_type(func_root, cst.Attribute))[-1] if self.change_arguments or self.change_method_arguments: for arg in func.args: # Значения аргументов arg = arg.with_changes(value=self.obf_universal(arg.value)) # Имена аргументов if arg.keyword is not None and self.can_rename_func_param( arg.keyword.value, func_name): new_keyword = self.get_new_cst_name( arg.keyword) if arg.keyword is not None else None arg = arg.with_changes(keyword=new_keyword) new_args.append(arg) func = func.with_changes(args=new_args) return func
def __extract_variable_name_type(self, node: cst.AnnAssign): """ Extracts a variable's identifier name and its type annotation """ return match.extract( node, match.AnnAssign( # Annotated Assignment target=match.OneOf( match.Name( # Variable name of assignment (only one) value=match.SaveMatchedNode( # Save result match.MatchRegex( r'(.)+'), # Match any string literal "name")), # This extracts variables inside __init__ which typically starts with self (e.g. self.x:int=2) match.Attribute( value=match.Name(value=match.SaveMatchedNode( match.MatchRegex(r'(.)+'), "obj_name" # Object name )), attr=match.Name( match.SaveMatchedNode(match.MatchRegex(r'(.)+'), "name")), )), annotation=match.SaveMatchedNode( # Save result match.DoNotCare(), # Match any string literal "type")))
def leave_ClassDef(self, original_node: cst.ClassDef, updated_node: cst.ClassDef): self.class_stack.pop() if not self.change_classes: return updated_node class_name = updated_node.name.value new_bases = [] if self.can_rename(class_name, 'c'): updated_node = self.renamed(updated_node) for base in updated_node.bases: full_name = base.value if m.matches(full_name, m.Name()): full_name = cst.ensure_type(full_name, cst.Name) if self.can_rename(full_name.value, 'c'): base = base.with_changes( value=self.get_new_cst_name(full_name.value)) elif m.matches(full_name, m.Attribute()): # TODO поддержка импортов pass else: pass new_bases.append(base) updated_node = updated_node.with_changes(bases=new_bases) return updated_node
def module_matcher(import_parts: Sequence) -> Union[m.BaseMatcherNode, m.DoNotCare]: """Build matcher for a module given sequence of import parts.""" # If only one element, it is just a Name if len(import_parts) == 1: return m.Name(import_parts[0]) *values, attr = import_parts value = module_matcher(values) return m.Attribute(value=value, attr=m.Name(attr))
def visit_Attribute(self, node: cst.Attribute) -> None: # The attribute node can come through other context as well but we only care # about the ones coming from assignments. if self._assigntarget_counter > 0: # We only care about assignment attribute to *self*. if m.matches(node, m.Attribute(value=m.Name(value="self"))): self._validate_nodename(node, node.attr.value, NamingConvention.SNAKE_CASE)
def _get_async_call_replacement(self, node: cst.Call) -> Optional[cst.CSTNode]: func = node.func if m.matches(func, m.Attribute()): func = cast(cst.Attribute, func) attr_func_replacement = self._get_async_attr_replacement(func) if attr_func_replacement is not None: return node.with_changes(func=attr_func_replacement) return self._get_awaitable_replacement(node)
def module_matcher(import_parts): *values, attr = import_parts if len(values) > 1: value = module_matcher(values) elif len(values) == 1: value = m.Name(values[0]) else: value = None return m.Attribute(value=value, attr=m.Name(attr))
def _make_name_or_attribute( parts: List[str]) -> Union[m.Name, m.Attribute]: if not parts: raise RuntimeError("Expected a non empty list of strings") # just a name, e.g. `coroutine` if len(parts) == 1: return m.Name(parts[0]) # a name and attribute, e.g. `gen.coroutine` if len(parts) == 2: return m.Attribute(value=m.Name(parts[0]), attr=m.Name(parts[1])) # a complex attribute, e.g. `tornado.gen.coroutine`, we want to make # the attribute with value `tornado.gen` and attr `coroutine` value = _make_name_or_attribute(parts[:-1]) attr = _make_name_or_attribute(parts[-1:]) return m.Attribute(value=value, attr=attr)
def leave_ImportFrom( self, original_node: ImportFrom, updated_node: ImportFrom ) -> Union[BaseSmallStatement, RemovalSentinel]: base_cls_matcher = [] if m.matches( updated_node, m.ImportFrom(module=module_matcher(["django", "contrib", "admin"])), ): for imported_name in updated_node.names: if m.matches( imported_name, m.ImportAlias(name=m.Name("TabularInline")) ): base_cls_matcher.append(m.Arg(m.Name("TabularInline"))) if m.matches( imported_name, m.ImportAlias(name=m.Name("StackedInline")) ): base_cls_matcher.append(m.Arg(m.Name("StackedInline"))) if m.matches( updated_node, m.ImportFrom(module=module_matcher(["django", "contrib"])), ): for imported_name in updated_node.names: if m.matches(imported_name, m.ImportAlias(name=m.Name("admin"))): base_cls_matcher.extend( [ m.Arg( m.Attribute( value=m.Name("admin"), attr=m.Name("TabularInline") ) ), m.Arg( m.Attribute( value=m.Name("admin"), attr=m.Name("StackedInline") ) ), ] ) # Save valid matchers in the context if base_cls_matcher: self.context.scratch[self.ctx_key_base_cls_matcher] = m.OneOf( *base_cls_matcher ) return super().leave_ImportFrom(original_node, updated_node)
class MakeModalCommand(VisitorBasedCodemodCommand): DESCRIPTION: str = "Replace built-in method MAkeModal with helper" method_matcher = matchers.FunctionDef( name=matchers.Name(value="MakeModal"), params=matchers.Parameters(params=[ matchers.Param(name=matchers.Name(value="self")), matchers.ZeroOrMore() ]), ) call_matcher = matchers.Call( func=matchers.Attribute(value=matchers.Name(value="self"), attr=matchers.Name(value="MakeModal"))) method_cst = cst.parse_statement( textwrap.dedent(""" def MakeModal(self, modal=True): if modal and not hasattr(self, '_disabler'): self._disabler = wx.WindowDisabler(self) if not modal and hasattr(self, '_disabler'): del self._disabler """)) def __init__(self, context: CodemodContext): super().__init__(context) self.stack: List[cst.ClassDef] = [] def visit_ClassDef(self, node: cst.ClassDef) -> None: self.stack.append(node) def leave_ClassDef(self, original_node: cst.ClassDef, updated_node: cst.ClassDef) -> cst.ClassDef: return self.stack.pop() def leave_Call(self, original_node: cst.Call, updated_node: cst.Call) -> cst.Call: if matchers.matches(updated_node, self.call_matcher): # Search for MakeModal() method current_class = self.stack[-1] has_make_modal_method = False for method in current_class.body.body: if matchers.matches(method, self.method_matcher): has_make_modal_method = True # If not, add it to the current class if not has_make_modal_method: current_class = current_class.with_changes( body=current_class.body.with_changes( body=[*current_class.body.body, self.method_cst])) self.stack[-1] = current_class return updated_node
def _build_arg_class_matcher(self) -> Union[m.Attribute, m.Name]: matcher = m.Name(value=self.current_classes[0]) # For nested classes, we need to match attributes, so we can target # `super(Foo.InnerFoo, self)` for example. if len(self.current_classes) > 1: for class_name in self.current_classes[1:]: matcher = m.Attribute(value=matcher, attr=m.Name(value=class_name)) return matcher
class InlineHasAddPermissionsTransformer(ContextAwareTransformer): """Add the ``obj`` argument to ``InlineModelAdmin.has_add_permission()``.""" context_key = "InlineHasAddPermissionsTransformer" base_cls_matcher = m.OneOf( m.Arg(m.Attribute(value=m.Name("admin"), attr=m.Name("TabularInline"))), m.Arg(m.Name("TabularInline")), m.Arg(m.Attribute(value=m.Name("admin"), attr=m.Name("StackedInline"))), m.Arg(m.Name("StackedInline")), ) def visit_ClassDef_bases(self, node: ClassDef) -> None: for base_cls in node.bases: if m.matches(base_cls, self.base_cls_matcher): self.context.scratch[self.context_key] = True super().visit_ClassDef_bases(node) def leave_ClassDef( self, original_node: ClassDef, updated_node: ClassDef) -> Union[BaseStatement, RemovalSentinel]: self.context.scratch.pop(self.context_key, None) return super().leave_ClassDef(original_node, updated_node) @property def _is_context_right(self): return self.context.scratch.get(self.context_key, False) def leave_FunctionDef( self, original_node: FunctionDef, updated_node: FunctionDef ) -> Union[BaseStatement, RemovalSentinel]: if (m.matches(updated_node, m.FunctionDef(name=m.Name("has_add_permission"))) and self._is_context_right): if len(updated_node.params.params) == 2: old_params = updated_node.params updated_params = old_params.with_changes(params=( *old_params.params, Param(name=Name("obj"), default=Name("None")), )) return updated_node.with_changes(params=updated_params) return super().leave_FunctionDef(original_node, updated_node)
def leave_Call(self, original_node, updated_node) -> cst.BaseExpression: if m.matches(updated_node.func, m.Attribute(value=m.Call(m.Name('super')))): return updated_node \ .with_deep_changes( updated_node.func, value=cst.Name(self.cls.__name__)) \ .with_changes( args=[cst.Arg(cst.Name('self'))] + list(updated_node.args)) return updated_node
def leave_FunctionDef(self, original_node, updated_node) -> cst.BaseStatement: fdef = updated_node ftool_pattern = m.Call( func=m.Attribute(value=m.Name("functools"), attr=m.Name("wraps"))) if len(fdef.decorators) == 1: dec = fdef.decorators[0].decorator if m.matches(dec, ftool_pattern): return fdef.with_changes(decorators=[]) return fdef
def visit_Call(self, node: cst.Call) -> None: if m.matches( node, m.Call(func=m.Attribute(value=m.Name("self"), attr=m.Name("assertEquals"))), ): new_call = node.with_deep_changes( old_node=cst.ensure_type(node.func, cst.Attribute).attr, value="assertEqual", ) self.report(node, replacement=new_call)
class MenuAppendCommand(VisitorBasedCodemodCommand): DESCRIPTION: str = "Migrate to wx.MenuAppend() method and update keywords" args_map = {"help": "helpString", "text": "item"} args_matchers_map = { matchers.Arg(keyword=matchers.Name(value=value)): renamed for value, renamed in args_map.items() } call_matcher = matchers.Call( func=matchers.Attribute(attr=matchers.Name(value="Append")), args=matchers.MatchIfTrue(lambda args: bool( set(arg.keyword.value for arg in args if arg and arg.keyword). intersection(MenuAppendCommand.args_map.keys()))), ) deprecated_call_matcher = matchers.Call( func=matchers.Attribute(attr=matchers.Name(value="AppendItem")), args=[matchers.DoNotCare()], ) def leave_Call(self, original_node: cst.Call, updated_node: cst.Call) -> cst.Call: # Migrate form deprecated method AppendItem() if matchers.matches(updated_node, self.deprecated_call_matcher): updated_node = updated_node.with_changes( func=updated_node.func.with_changes(attr=cst.Name( value="Append"))) # Update keywords if matchers.matches(updated_node, self.call_matcher): updated_node_args = list(updated_node.args) for arg_matcher, renamed in self.args_matchers_map.items(): for i, node_arg in enumerate(updated_node.args): if matchers.matches(node_arg, arg_matcher): updated_node_args[i] = node_arg.with_changes( keyword=cst.Name(value=renamed)) updated_node = updated_node.with_changes( args=updated_node_args) return updated_node