def leave_ImportFrom(self, original_node: cst.ImportFrom, updated_node: cst.ImportFrom) -> cst.ImportFrom: module = updated_node.module if module is None: return updated_node imported_module_name = get_full_name_for_node(module) names = original_node.names if imported_module_name is None or not isinstance(names, Sequence): return updated_node else: new_names = [] for import_alias in names: alias_name = get_full_name_for_node(import_alias.name) if alias_name is not None: qual_name = f"{imported_module_name}.{alias_name}" if self.old_name == qual_name: replacement_module = self.gen_replacement_module( imported_module_name) replacement_obj = self.gen_replacement(alias_name) if not replacement_obj: # The user has requested an `import` statement rather than an `from ... import`. # This will be taken care of in `leave_Module`, in the meantime, schedule for potential removal. new_names.append(import_alias) self.scheduled_removals.add(original_node) continue new_import_alias_name: Union[ cst.Attribute, cst.Name] = self.gen_name_or_attr_node( replacement_obj) # Rename on the spot only if this is the only imported name under the module. if len(names) == 1: self.bypass_import = True return updated_node.with_changes( module=cst.parse_expression( replacement_module), names=(cst.ImportAlias( name=new_import_alias_name), ), ) # Or if the module name is to stay the same. elif replacement_module == imported_module_name: self.bypass_import = True new_names.append( cst.ImportAlias(name=new_import_alias_name)) else: if self.old_name.startswith(qual_name + "."): # This import might be in use elsewhere in the code, so schedule a potential removal. self.scheduled_removals.add(original_node) new_names.append(import_alias) return updated_node.with_changes(names=new_names) return updated_node
def leave_ImportFrom(self, original_node: libcst.ImportFrom, updated_node: libcst.ImportFrom) -> libcst.ImportFrom: if isinstance(updated_node.names, libcst.ImportStar): # There's nothing to do here! return updated_node # Get the module we're importing as a string, see if we have work to do. module = get_absolute_module_for_import(self.context.full_module_name, updated_node) if (module is None or module not in self.module_mapping and module not in self.alias_mapping): return updated_node # We have work to do, mark that we won't modify this again. imports_to_add = self.module_mapping.get(module, []) if module in self.module_mapping: del self.module_mapping[module] aliases_to_add = self.alias_mapping.get(module, []) if module in self.alias_mapping: del self.alias_mapping[module] # Now, do the actual update. return updated_node.with_changes(names=[ *(libcst.ImportAlias(name=libcst.Name(imp)) for imp in sorted(imports_to_add)), *(libcst.ImportAlias( name=libcst.Name(imp), asname=libcst.AsName(name=libcst.Name(alias)), ) for (imp, alias) in sorted(aliases_to_add)), *updated_node.names, ])
def leave_ImportFrom( self, original_node: cst.ImportFrom, updated_node: cst.ImportFrom ) -> Union[cst.RemovalSentinel, cst.ImportFrom]: if isinstance(updated_node.names, cst.ImportStar): def get_star_imp() -> Optional[ImportFrom]: if isinstance(updated_node.module, cst.Attribute): import_name = self.get_import_name_from_attr( attr_node=updated_node.module) else: import_name = updated_node.module.value location = self.get_location(original_node) for imp in self.unused_imports: if (isinstance(imp, ImportFrom) and imp.name == import_name and imp.lineno == location.start.line): return imp else: return None imp = get_star_imp() if imp: return self.leave_StarImport(updated_node, imp) else: return original_node rpar = self.get_rpar(updated_node.rpar, self.get_location(original_node)) return self.leave_import_alike(original_node, updated_node.with_changes(rpar=rpar))
def leave_ImportFrom( self, original_node: cst.ImportFrom, updated_node: cst.ImportFrom ) -> Union[cst.ImportFrom, cst.RemovalSentinel]: # Grab the scope for this import. If we don't have scope, we can't determine # whether this import is unused so it is unsafe to remove. scope = self.get_metadata(ScopeProvider, original_node, None) if scope is None: return updated_node # Make sure we have anything to do with this node. names = original_node.names if isinstance(names, cst.ImportStar): # This is a star import, so we won't remove it. return updated_node # Make sure we actually know the absolute module. module_name = get_absolute_module_for_import( self.context.full_module_name, updated_node ) if module_name is None or module_name not in self.unused_obj_imports: # This node isn't on our list of todos, so let's bail. return updated_node objects_to_remove = self.unused_obj_imports[module_name] names_to_keep = [] for import_alias in names: # Figure out if it is in our list of things to kill for name, alias in objects_to_remove: if ( name == import_alias.evaluated_name and alias == import_alias.evaluated_alias ): break else: # This is a keeper, we don't have it on our list. names_to_keep.append(import_alias) continue # Now that we know we want to remove this object, figure out if # there are any live references to it. if self._is_in_use(scope, import_alias): names_to_keep.append(import_alias) continue # no changes if names_to_keep == names: return updated_node # Now, either remove this statement or remove the imports we are # deleting from this statement. if len(names_to_keep) == 0: return cst.RemoveFromParent() if names_to_keep[-1] != names[-1]: # Remove trailing comma in order to not mess up import statements. names_to_keep = [ *names_to_keep[:-1], names_to_keep[-1].with_changes(comma=cst.MaybeSentinel.DEFAULT), ] return updated_node.with_changes(names=names_to_keep)
def leave_StarImport( updated_node: cst.ImportFrom, imp: ImportFrom, ) -> Union[cst.ImportFrom, cst.RemovalSentinel]: if imp.suggestions: names_to_suggestions = [ cst.ImportAlias(cst.Name(module)) for module in imp.suggestions ] return updated_node.with_changes(names=names_to_suggestions) else: return cst.RemoveFromParent()
def leave_ImportFrom(self, original_node: cst.ImportFrom, updated_node: cst.ImportFrom) -> cst.ImportFrom: self.import_statements.append(original_node) # pyre-fixme[6]: Expected `Union[Attribute, Name]` for 1st param but got # `Optional[Union[Attribute, Name]]`. key = _get_attribute_as_string(original_node.module) if (original_node.module is not None # pyre-fixme[16]: `Optional` has no attribute `value`. and original_node.module.value in self.imports): names = list(updated_node.names) + self.imports[key].names updated_node = updated_node.with_changes(names=tuple(names)) del self.imports[key] return updated_node
def leave_StarImport(original_node: cst.ImportFrom, updated_node: cst.ImportFrom, **kwargs) -> Union[cst.ImportFrom, RemovalSentinel]: imp = kwargs["imp"] if imp["modules"]: modules = ",".join(imp["modules"]) names_to_suggestion = [] for module in modules.split(","): names_to_suggestion.append(cst.ImportAlias(cst.Name(module))) return updated_node.with_changes(names=names_to_suggestion) else: if imp["module"]: return cst.RemoveFromParent() return original_node
def _check_import_from_parent( self, original_node: ImportFrom, updated_node: ImportFrom ) -> Optional[Union[BaseSmallStatement, RemovalSentinel]]: """ Check for when the parent module of thing to replace is imported. When `parent.module.the_thing` is transformed, detect such import: from parent import module """ # First, exit early if 'import *' is used if isinstance(updated_node.names, ImportStar): return None # Check whether parent module is imported if not import_from_matches(updated_node, self.old_parent_module_parts): return None # Match, update the node an return it new_import_aliases = [] for import_alias in updated_node.names: if import_alias.evaluated_name == self.old_parent_name: self.save_import_scope(original_node) module_name_str = (import_alias.evaluated_alias or import_alias.evaluated_name) self.context.scratch[self.ctx_key_name_matcher] = m.Attribute( value=m.Name(module_name_str), attr=m.Name(self.old_name), ) self.context.scratch[self.ctx_key_new_func] = Attribute( attr=Name(self.new_name), value=Name(import_alias.evaluated_alias or self.new_parent_name), ) if self.old_parent_module_parts != self.new_parent_module_parts: # import statement needs updating AddImportsVisitor.add_needed_import( context=self.context, module=".".join(self.new_parent_module_parts), obj=self.new_parent_name, asname=import_alias.evaluated_alias, ) continue new_import_aliases.append(import_alias) if not new_import_aliases: # Nothing left in the import statement: remove it return RemoveFromParent() # Some imports are left, update the statement new_import_aliases = clean_new_import_aliases(new_import_aliases) return updated_node.with_changes(names=new_import_aliases)
def leave_ImportFrom( self, original_node: ImportFrom, updated_node: ImportFrom ) -> Union[BaseSmallStatement, RemovalSentinel]: """Update import statements for matching old module name.""" if not import_from_matches(updated_node, self.old_module_parts) or isinstance( updated_node.names, ImportStar): return updated_node # This is a match new_names = list(self.gen_new_imported_names(updated_node.names)) self.save_import_scope(original_node) if not new_names: # Nothing left in the import statement: remove it return RemoveFromParent() # Some imports are left, update the statement cleaned_names = self.tidy_new_imported_names(new_names) return updated_node.with_changes(names=cleaned_names)
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 leave_ImportFrom(self, original_node: cst.ImportFrom, updated_node: cst.ImportFrom): new_names = [] if m.matches(updated_node.names, m.ImportStar()): new_names = updated_node.names else: for el in updated_node.names: el_name = el.name.value if self.can_rename( el_name) and self.name_generator.in_dictionary( el_name): el = el.with_changes(name=self.get_new_cst_name(el_name)) new_names.append(el) updated_node = updated_node.with_changes(names=new_names) return updated_node
def leave_ImportFrom( self, original_node: cst.ImportFrom, updated_node: cst.ImportFrom ) -> cst.ImportFrom: self.import_statements.append(original_node) # pyre-fixme[6]: Expected `Union[Attribute, Name]` for 1st param but got # `Optional[Union[Attribute, Name]]`. key = _get_attribute_as_string(original_node.module) import_names = updated_node.names module = original_node.module if ( module is not None and module.value in self.imports and not isinstance(import_names, cst.ImportStar) ): names_as_string = [_get_name_as_string(name.name) for name in import_names] updated_names = self.imports[key].names.union(set(names_as_string)) names = [cst.ImportAlias(cst.Name(name)) for name in sorted(updated_names)] updated_node = updated_node.with_changes(names=tuple(names)) del self.imports[key] return updated_node
def leave_ImportFrom(self, original_node: cst.ImportFrom, updated_node: cst.ImportFrom) -> cst.ImportFrom: code = self.get_metadata(cst.metadata.PositionProvider, original_node) line_no = code.start.line new_import_alias = [] if isinstance(updated_node.names, cst.ImportStar): # we do not handle ImportStar return updated_node for import_alias in updated_node.names: if self.is_reimport_from(line_no, updated_node.module, import_alias): continue new_import_alias.append(import_alias) if new_import_alias: new_import_alias[-1] = new_import_alias[-1].with_changes( comma=cst.MaybeSentinel.DEFAULT) return updated_node.with_changes(names=new_import_alias) if len(new_import_alias) == 0: return cst.RemoveFromParent() return updated_node
def leave_ImportFrom( self, original_node: cst.ImportFrom, updated_node: cst.ImportFrom ) -> Union[cst.ImportFrom, cst.RemovalSentinel]: names = original_node.names if isinstance(names, cst.ImportStar): # This is a star import, so we won't remove it. return updated_node # Make sure we actually know the absolute module. module_name = get_absolute_module_for_import( self.context.full_module_name, updated_node) if module_name is None or module_name not in self.unused_obj_imports: # This node isn't on our list of todos, so let's bail. return updated_node updates = self._process_importfrom_aliases(updated_node, names, module_name) names_to_keep = updates["names"] # no changes if names_to_keep == names: return updated_node # Now, either remove this statement or remove the imports we are # deleting from this statement. if len(names_to_keep) == 0: return cst.RemoveFromParent() if names_to_keep[-1] != names[-1]: # Remove trailing comma in order to not mess up import statements. names_to_keep = [ *names_to_keep[:-1], names_to_keep[-1].with_changes( comma=cst.MaybeSentinel.DEFAULT), ] updates["names"] = names_to_keep return updated_node.with_changes(**updates)
def leave_ImportFrom(self, original_node: libcst.ImportFrom, updated_node: libcst.ImportFrom) -> libcst.ImportFrom: if len(updated_node.relative) > 0 or updated_node.module is None: # Don't support relative-only imports at the moment. return updated_node if updated_node.names == "*": # There's nothing to do here! return updated_node # Get the module we're importing as a string, see if we have work to do module = self._get_string_name(updated_node.module) if module not in self.module_mapping and module not in self.alias_mapping: return updated_node # We have work to do, mark that we won't modify this again. imports_to_add = self.module_mapping.get(module, []) if module in self.module_mapping: del self.module_mapping[module] aliases_to_add = self.alias_mapping.get(module, []) if module in self.alias_mapping: del self.alias_mapping[module] # Now, do the actual update. return updated_node.with_changes(names=( *[ libcst.ImportAlias(name=libcst.Name(imp)) for imp in imports_to_add ], *[ libcst.ImportAlias( name=libcst.Name(imp), asname=libcst.AsName(name=libcst.Name(alias)), ) for (imp, alias) in aliases_to_add ], *updated_node.names, ))