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)
def leave_ImportFrom( self, original_node: ImportFrom, updated_node: ImportFrom ) -> Union[BaseSmallStatement, RemovalSentinel]: if isinstance(updated_node.names, ImportStar): return super().leave_ImportFrom(original_node, updated_node) if m.matches( updated_node, m.ImportFrom(module=module_matcher(["django", "db"])), ): for imported_name in updated_node.names: if m.matches(imported_name, m.ImportAlias(name=m.Name("models"))): self.add_decorator_matcher( m.Decorator( decorator=m.Attribute( value=m.Name("models"), attr=m.Name("permalink") ) ) ) if m.matches( updated_node, m.ImportFrom(module=module_matcher(["django", "db", "models"])), ): updated_names = [] for imported_name in updated_node.names: if m.matches(imported_name, m.ImportAlias(name=m.Name("permalink"))): decorator_name_str = ( imported_name.evaluated_alias or imported_name.evaluated_name ) self.add_decorator_matcher( m.Decorator(decorator=m.Name(decorator_name_str)) ) else: updated_names.append(imported_name) if not updated_names: return RemoveFromParent() # sort imports new_names = sorted(updated_names, key=lambda n: n.evaluated_name) # remove any trailing commas last_name = new_names[-1] if last_name.comma != MaybeSentinel.DEFAULT: new_names[-1] = last_name.with_changes(comma=MaybeSentinel.DEFAULT) return updated_node.with_changes(names=new_names) return super().leave_ImportFrom(original_node, updated_node)
def visit_ImportFrom(self, node: cst.ImportFrom) -> None: if m.matches( node, m.ImportFrom( module=m.Name("__future__"), names=[ m.ZeroOrMore(), m.ImportAlias(name=m.Name("annotations")), m.ZeroOrMore(), ], ), ): self.has_future_annotations_import = True
def _get_imported_name(node: ImportFrom, import_path: str) -> Optional[str]: """Resolve the imported name if present.""" if isinstance(node.names, ImportStar): return None *modules, name = import_path.split(".") if not import_from_matches(node, modules): return None for import_alias in node.names: if m.matches(import_alias, m.ImportAlias(name=m.Name(name))): # We're visiting the import statement we're looking for # Get the actual name it's imported as (in case of import alias) imported_name_str = ( import_alias.evaluated_alias or import_alias.evaluated_name ) return imported_name_str return None
def _check_libary_imported(self, node: ImportFrom) -> bool: """Record matcher if django.template.Library is imported.""" if import_from_matches(node, ["django", "template"]): for import_alias in node.names: if m.matches(import_alias, m.ImportAlias(name=m.Name("Library"))): # We're visiting the `from django.template import Library` statement # Get the actual name it's imported as (in case of import alias) imported_name = ( import_alias.asname and import_alias.asname.name or import_alias.name ) # Build the `Call` matcher to look out for, eg `Library()` self.context.scratch[self.ctx_key_library_call_matcher] = m.Call( func=m.Name(imported_name.value) ) return True return False
def visit_ImportAlias(self, node: cst.ImportAlias): """ Extracts imports. Even if an Import has no alias, the Import node will still have an ImportAlias with it's real name. """ # Extract information from the Import Alias node. # Both the (real) import name and the alias are extracted. # Result is returned as a dictionary with a name:node KV pair, # and an alias:name KV pair if an alias is present. import_info = match.extract( node, match.ImportAlias( asname=match.AsName( # Attempt to match alias name=match.Name( # Check for alias name value=match.SaveMatchedNode( # Save the alias name match.MatchRegex( r'(.)+'), # Match any string literal "alias"))) | ~match.AsName(), # If there is no AsName, we should still match. We negate AsName to and OR to form a tautology. name=match.SaveMatchedNode( # Match & save name of import match.DoNotCare(), "name"))) # Add import if a name could be extracted. if "name" in import_info: # Append import name to imports list. # We convert the ImportAlias node to the code representation for simplified conversion # of multi-level imports (e.g. import.x.y.z) # TODO: This might be un-needed after implementing import type extraction change. # TODO: So far, no differentiation between import and from imports. import_name = self.__convert_node_to_code(import_info["name"]) import_name = self.__clean_string_whitespace(import_name) self.imports.append(import_name) if "alias" in import_info: import_name = self.__clean_string_whitespace(import_info["alias"]) self.imports.append(import_name) # No need to traverse children, as we already performed the matching. return False
class Modernizer(m.MatcherDecoratableTransformer): METADATA_DEPENDENCIES = (PositionProvider,) # FIXME use a stack of e.g. SimpleStatementLine then proper visit_Import/ImportFrom to store the ssl node def __init__( self, path: Path, verbose: bool = False, ignored: Optional[List[str]] = None ): super().__init__() self.path = path self.verbose = verbose self.ignored = set(ignored or []) self.errors = False self.stack: List[Tuple[str, ...]] = [] self.annotations: Dict[ Tuple[str, ...], Comment # key: tuple of canonical variable name ] = {} self.python_future_updated_node: Optional[SimpleStatementLine] = None self.python_future_imports: Dict[str, str] = {} self.python_future_new_imports: Set[str] = set() self.builtins_imports: Dict[str, str] = {} self.builtins_new_imports: Set[str] = set() self.builtins_updated_node: Optional[SimpleStatementLine] = None self.future_utils_imports: Dict[str, str] = {} self.future_utils_new_imports: Set[str] = set() self.future_utils_updated_node: Optional[SimpleStatementLine] = None # self.last_import_node: Optional[CSTNode] = None self.last_import_node_stmt: Optional[CSTNode] = None # @m.call_if_inside(m.ImportFrom(module=m.Name("__future__"))) # @m.visit(m.ImportAlias() | m.ImportStar()) # def import_python_future_check(self, node: Union[ImportAlias, ImportStar]) -> None: # self.add_import(self.python_future_imports, node) # @m.leave(m.ImportFrom(module=m.Name("__future__"))) # def import_python_future_modify( # self, original_node: ImportFrom, updated_node: ImportFrom # ) -> Union[BaseSmallStatement, RemovalSentinel]: # return updated_node @m.call_if_inside(m.ImportFrom(module=m.Name("builtins"))) @m.visit(m.ImportAlias() | m.ImportStar()) def import_builtins_check(self, node: Union[ImportAlias, ImportStar]) -> None: self.add_import(self.builtins_imports, node) # @m.leave(m.ImportFrom(module=m.Name("builtins"))) # def builtins_modify( # self, original_node: ImportFrom, updated_node: ImportFrom # ) -> Union[BaseSmallStatement, RemovalSentinel]: # return updated_node @m.call_if_inside( m.ImportFrom(module=m.Attribute(value=m.Name("future"), attr=m.Name("utils"))) ) @m.visit(m.ImportAlias() | m.ImportStar()) def import_future_utils_check(self, node: Union[ImportAlias, ImportStar]) -> None: self.add_import(self.future_utils_imports, node) # @m.leave( # m.ImportFrom(module=m.Attribute(value=m.Name("future"), attr=m.Name("utils"))) # ) # def future_utils_modify( # self, original_node: ImportFrom, updated_node: ImportFrom # ) -> Union[BaseSmallStatement, RemovalSentinel]: # return updated_node @staticmethod def add_import( imports: Dict[str, str], node: Union[ImportAlias, ImportStar] ) -> None: if isinstance(node, ImportAlias): imports[node.name.value] = ( node.asname.name.value if node.asname else node.name.value ) else: imports["*"] = "*" # @m.call_if_not_inside(m.BaseCompoundStatement()) # def visit_Import(self, node: Import) -> Optional[bool]: # self.last_import_node = node # return None # @m.call_if_not_inside(m.BaseCompoundStatement()) # def visit_ImportFrom(self, node: ImportFrom) -> Optional[bool]: # self.last_import_node = node # return None @m.call_if_not_inside(m.ClassDef() | m.FunctionDef() | m.If()) def visit_SimpleStatementLine(self, node: SimpleStatementLine) -> Optional[bool]: for n in node.body: if m.matches(n, m.Import() | m.ImportFrom()): self.last_import_node_stmt = node return None @m.call_if_not_inside(m.ClassDef() | m.FunctionDef() | m.If()) def leave_SimpleStatementLine( self, original_node: SimpleStatementLine, updated_node: SimpleStatementLine ) -> Union[BaseStatement, RemovalSentinel]: for n in updated_node.body: if m.matches(n, m.ImportFrom(module=m.Name("__future__"))): self.python_future_updated_node = updated_node elif m.matches(n, m.ImportFrom(module=m.Name("builtins"))): self.builtins_updated_node = updated_node elif m.matches( n, m.ImportFrom( module=m.Attribute(value=m.Name("future"), attr=m.Name("utils")) ), ): self.future_utils_updated_node = updated_node return updated_node # @m.visit( # m.AllOf( # m.SimpleStatementLine(), # m.MatchIfTrue( # lambda node: any(m.matches(c, m.Assign()) for c in node.children) # ), # m.MatchIfTrue( # lambda node: "# type:" in node.trailing_whitespace.comment.value # ), # ) # ) # def visit_assign(self, node: SimpleStatementSuite) -> None: # return None def visit_Param(self, node: Param) -> Optional[bool]: class Visitor(m.MatcherDecoratableVisitor): def __init__(self): super().__init__() self.ptype: Optional[str] = None def visit_TrailingWhitespace_comment( self, node: "TrailingWhitespace" ) -> None: if node.comment and "type:" in node.comment.value: mo = re.match(r"#\s*type:\s*(\S*)", node.comment.value) self.ptype = mo.group(1) if mo else None return None v = Visitor() node.visit(v) if self.verbose: pos = self.get_metadata(PositionProvider, node).start print( f"{self.path}:{pos.line}:{pos.column}: parameter {node.name.value}: {v.ptype or 'unknown type'}" ) return None @m.visit(m.SimpleStatementLine()) def visit_simple_stmt(self, node: SimpleStatementLine) -> None: assign = None for c in node.children: if m.matches(c, m.Assign()): assign = ensure_type(c, Assign) if assign: if m.MatchIfTrue( lambda n: n.trailing_whitespace.comment and "type:" in n.trailing_whitespace.comment.value ): class TypingVisitor(m.MatcherDecoratableVisitor): def __init__(self): super().__init__() self.vtype = None def visit_TrailingWhitespace_comment( self, node: "TrailingWhitespace" ) -> None: if node.comment: mo = re.match(r"#\s*type:\s*(\S*)", node.comment.value) if mo: vtype = mo.group(1) return None tv = TypingVisitor() node.visit(tv) vtype = tv.vtype else: vtype = None class NameVisitor(m.MatcherDecoratableVisitor): def __init__(self): super().__init__() self.names: List[str] = [] def visit_Name(self, node: Name) -> Optional[bool]: self.names.append(node.value) return None if self.verbose: pos = self.get_metadata(PositionProvider, node).start for target in assign.targets: v = NameVisitor() target.visit(v) for name in v.names: print( f"{self.path}:{pos.line}:{pos.column}: variable {name}: {vtype or 'unknown type'}" ) def visit_FunctionDef_body(self, node: FunctionDef) -> None: class Visitor(m.MatcherDecoratableVisitor): def __init__(self): super().__init__() def visit_EmptyLine_comment(self, node: "EmptyLine") -> None: # FIXME too many matches on test_param_02 if not node.comment: return # TODO: use comment.value return None v = Visitor() node.visit(v) return None map_matcher = m.Call( func=m.Name("filter") | m.Name("map") | m.Name("zip") | m.Name("range") ) @m.visit(map_matcher) def visit_map(self, node: Call) -> None: func_name = ensure_type(node.func, Name).value if func_name not in self.builtins_imports: self.builtins_new_imports.add(func_name) @m.call_if_not_inside( m.Call( func=m.Name("list") | m.Name("set") | m.Name("tuple") | m.Attribute(attr=m.Name("join")) ) | m.CompFor() | m.For() ) @m.leave(map_matcher) def fix_map(self, original_node: Call, updated_node: Call) -> BaseExpression: # TODO test with CompFor etc. # TODO improve join test func_name = ensure_type(updated_node.func, Name).value if func_name not in self.builtins_imports: updated_node = Call(func=Name("list"), args=[Arg(updated_node)]) return updated_node @m.visit(m.Call(func=m.Name("xrange") | m.Name("raw_input"))) def visit_xrange(self, node: Call) -> None: orig_func_name = ensure_type(node.func, Name).value func_name = "range" if orig_func_name == "xrange" else "input" if func_name not in self.builtins_imports: self.builtins_new_imports.add(func_name) @m.leave(m.Call(func=m.Name("xrange") | m.Name("raw_input"))) def fix_xrange(self, original_node: Call, updated_node: Call) -> BaseExpression: orig_func_name = ensure_type(updated_node.func, Name).value func_name = "range" if orig_func_name == "xrange" else "input" return updated_node.with_changes(func=Name(func_name)) iter_matcher = m.Call( func=m.Attribute( attr=m.Name("iterkeys") | m.Name("itervalues") | m.Name("iteritems") ) ) @m.visit(iter_matcher) def visit_iter(self, node: Call) -> None: func_name = ensure_type(node.func, Attribute).attr.value if func_name not in self.future_utils_imports: self.future_utils_new_imports.add(func_name) @m.leave(iter_matcher) def fix_iter(self, original_node: Call, updated_node: Call) -> BaseExpression: attribute = ensure_type(updated_node.func, Attribute) func_name = attribute.attr dict_name = attribute.value return updated_node.with_changes(func=func_name, args=[Arg(dict_name)]) not_iter_matcher = m.Call( func=m.Attribute(attr=m.Name("keys") | m.Name("values") | m.Name("items")) ) @m.call_if_not_inside( m.Call( func=m.Name("list") | m.Name("set") | m.Name("tuple") | m.Attribute(attr=m.Name("join")) ) | m.CompFor() | m.For() ) @m.leave(not_iter_matcher) def fix_not_iter(self, original_node: Call, updated_node: Call) -> BaseExpression: updated_node = Call(func=Name("list"), args=[Arg(updated_node)]) return updated_node @m.call_if_not_inside(m.Import() | m.ImportFrom()) @m.leave(m.Name(value="unicode")) def fix_unicode(self, original_node: Name, updated_node: Name) -> BaseExpression: value = "text_type" if value not in self.future_utils_imports: self.future_utils_new_imports.add(value) return updated_node.with_changes(value=value) def leave_Module(self, original_node: Module, updated_node: Module) -> Module: updated_node = self.update_imports( original_node, updated_node, "builtins", self.builtins_updated_node, self.builtins_imports, self.builtins_new_imports, True, ) updated_node = self.update_imports( original_node, updated_node, "future.utils", self.future_utils_updated_node, self.future_utils_imports, self.future_utils_new_imports, False, ) return updated_node def update_imports( self, original_module: Module, updated_module: Module, import_name: str, updated_import_node: SimpleStatementLine, current_imports: Dict[str, str], new_imports: Set[str], noqa: bool, ) -> Module: if not new_imports: return updated_module noqa_comment = " # noqa" if noqa else "" if not updated_import_node: i = -1 blank_lines = "\n\n" if self.last_import_node_stmt: blank_lines = "" for i, (original, updated) in enumerate( zip(original_module.body, updated_module.body) ): if original is self.last_import_node_stmt: break stmt = parse_module( f"from {import_name} import {', '.join(sorted(new_imports))}{noqa_comment}\n{blank_lines}", config=updated_module.config_for_parsing, ) body = list(updated_module.body) self.last_import_node_stmt = stmt return updated_module.with_changes( body=body[: i + 1] + stmt.children + body[i + 1 :] ) else: if "*" not in current_imports: current_imports_set = { f"{k}" if k == v else f"{k} as {v}" for k, v in current_imports.items() } stmt = parse_statement( f"from {import_name} import {', '.join(sorted(new_imports | current_imports_set))}{noqa_comment}" ) return updated_module.deep_replace(updated_import_node, stmt) # for i, (original, updated) in enumerate( # zip(original_module.body, updated_module.body) # ): # if original is original_import_node: # body = list(updated_module.body) # return updated_module.with_changes( # body=body[:i] + [stmt] + body[i + 1 :] # ) return updated_module
class Checker(m.MatcherDecoratableVisitor): METADATA_DEPENDENCIES = (PositionProvider,) def __init__( self, path: Path, verbose: bool = False, ignored: Optional[List[str]] = None ): super().__init__() self.path = path self.verbose = verbose self.ignored = set(ignored or []) self.future_division = False self.errors = False self.stack: List[str] = [] @m.call_if_inside(m.ImportFrom(module=m.Name("__future__"))) @m.visit(m.ImportAlias(name=m.Name("division"))) def import_div(self, node: ImportAlias) -> None: self.future_division = True @m.visit(m.BinaryOperation(operator=m.Divide())) def check_div(self, node: BinaryOperation) -> None: if "division" in self.ignored: return if not self.future_division: pos = self.get_metadata(PositionProvider, node).start print( f"{self.path}:{pos.line}:{pos.column}: division without `from __future__ import division`" ) self.errors = True @m.visit(m.Attribute(attr=m.Name("maxint"), value=m.Name("sys"))) def check_maxint(self, node: Attribute) -> None: if "sys.maxint" in self.ignored: return pos = self.get_metadata(PositionProvider, node).start print(f"{self.path}:{pos.line}:{pos.column}: use of sys.maxint") self.errors = True def visit_ClassDef(self, node: ClassDef) -> None: self.stack.append(node.name.value) def leave_ClassDef(self, node: ClassDef) -> None: self.stack.pop() def visit_FunctionDef(self, node: FunctionDef) -> None: self.stack.append(node.name.value) def leave_FunctionDef(self, node: FunctionDef) -> None: self.stack.pop() def visit_ClassDef_bases(self, node: "ClassDef") -> None: return @m.visit( m.Call( func=m.Attribute(attr=m.Name("assertEquals") | m.Name("assertItemsEqual")) ) ) def visit_old_assert(self, node: Call) -> None: name = ensure_type(node.func, Attribute).attr.value if name in self.ignored: return pos = self.get_metadata(PositionProvider, node).start print(f"{self.path}:{pos.line}:{pos.column}: use of {name}") self.errors = True
class SignalDisconnectWeakTransformer(BaseDjCodemodTransformer): """Remove the `weak` argument to `Signal.disconnect()`.""" deprecated_in = DJANGO_1_9 removed_in = DJANGO_2_0 ctx_key_prefix = "SignalDisconnectWeakTransformer" ctx_key_call_matchers = f"{ctx_key_prefix}-call_matchers" builtin_signals = [ "pre_init", "post_init", "pre_save", "post_save", "pre_delete", "post_delete", "m2m_changed", "pre_migrate", "post_migrate", ] import_alias_matcher = m.OneOf(*(m.ImportAlias(name=m.Name(signal_name)) for signal_name in builtin_signals)) @property def disconnect_call_matchers(self) -> List[m.Call]: return self.context.scratch.get(self.ctx_key_call_matchers, []) def add_disconnect_call_matcher(self, call_matcher: m.Call) -> None: self.context.scratch[ self.ctx_key_call_matchers] = self.disconnect_call_matchers + [ call_matcher ] def leave_Module(self, original_node: Module, updated_node: Module) -> Module: """Clear context when leaving module.""" self.context.scratch.pop(self.ctx_key_call_matchers, None) return super().leave_Module(original_node, updated_node) def visit_ImportFrom(self, node: ImportFrom) -> Optional[bool]: """Set the `Call` matcher depending on which signals are imported..""" if import_from_matches(node, ["django", "db", "models", "signals"]): for import_alias in node.names: if m.matches(import_alias, self.import_alias_matcher): # We're visiting an import statement for a built-in signal # Get the actual name it's imported as (in case of import alias) imported_name = (import_alias.asname and import_alias.asname.name or import_alias.name) # Add the call matcher for the current signal to the list self.add_disconnect_call_matcher( m.Call(func=m.Attribute( value=m.Name(imported_name.value), attr=m.Name("disconnect"), ), )) return super().visit_ImportFrom(node) def leave_Call(self, original_node: Call, updated_node: Call) -> BaseExpression: """ Remove the `weak` argument if present in the call. This is only changing calls with keyword arguments. """ if self.disconnect_call_matchers and m.matches( updated_node, m.OneOf(*self.disconnect_call_matchers)): updated_args = [] should_change = False last_comma = MaybeSentinel.DEFAULT # Keep all arguments except the one with the keyword `weak` (if present) for index, arg in enumerate(updated_node.args): if m.matches(arg, m.Arg(keyword=m.Name("weak"))): # An argument with the keyword `weak` was found # -> we need to rewrite the statement should_change = True else: updated_args.append(arg) last_comma = arg.comma if should_change: # Make sure the end of line is formatted as initially updated_args[-1] = updated_args[-1].with_changes( comma=last_comma) return updated_node.with_changes(args=updated_args) return super().leave_Call(original_node, updated_node)