예제 #1
0
    def leave_Import(
            self, original_node: cst.Import, updated_node: cst.Import
    ) -> Union[cst.Import, cst.RemovalSentinel]:

        name_idxs_to_remove = []

        for n, alias in enumerate(original_node.names):

            if alias.asname:
                # If an `import ...` has an alias, then it simply needs to be
                # replaced, because that alias will necessarily serve as a type
                # of indirect import.

                indirect_ref = cst.helpers.parse_template_expression(
                    self.pkg_fullname + ".{name}", name=alias.asname.name)

                direct_ref = alias.name
                indirect_ref_name = cst.helpers.get_full_name_for_node(
                    indirect_ref)

                self.rewrites[indirect_ref_name] = direct_ref

                name_idxs_to_remove.append(n)
            else:
                module_fullname = cst.helpers.get_full_name_for_node(
                    alias.name)
                module_package = self.pkg_info.fullnames_to_packages[
                    module_fullname]

                if module_package == self.pkg_fullname:
                    pass
                elif module_package >= self.pkg_fullname:
                    # The imported object is in a sub-package of this package
                    # We could remove it, but that would require new `import`
                    # statements in the modules that use this direct reference.

                    # TODO: This seems like a good optional functionality to
                    # offer.
                    pass
                else:
                    # This import is for an object above this package level,
                    # but it's not an aliased import, so it can't be an
                    # indirect reference, but it could definitely be
                    # introducing some unwanted (sub-)package dependencies.
                    pass

        if name_idxs_to_remove:
            new_names = tuple(name for n, name in enumerate(updated_node.names)
                              if n not in name_idxs_to_remove)

            if not new_names:
                return cst.RemoveFromParent()

            updated_node = updated_node.with_changes(names=new_names)

        return updated_node
예제 #2
0
    def leave_Import(
        self, original_node: cst.Import, updated_node: cst.Import
    ) -> Union[cst.Import, 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

        names_to_keep = []
        for import_alias in original_node.names:
            if import_alias.evaluated_name not in self.unused_module_imports:
                # This is a keeper since we aren't removing it
                names_to_keep.append(import_alias)
                continue

            if (
                import_alias.evaluated_alias
                != self.unused_module_imports[import_alias.evaluated_name]
            ):
                # This is a keeper since the alias does not match
                # what we are looking for.
                names_to_keep.append(import_alias)
                continue

            # Now that we know we want to remove this module, 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 == original_node.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] != original_node.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)
예제 #3
0
    def leave_Import(self, original_node: cst.Import,
                     updated_node: cst.Import) -> cst.Import:

        code = self.get_metadata(cst.metadata.PositionProvider, original_node)
        new_import_alias = []
        line_no = code.start.line
        for import_alias in updated_node.names:
            if self.is_reimport(line_no, 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
예제 #4
0
    def leave_Import(
            self, original_node: cst.Import, updated_node: cst.Import
    ) -> Union[cst.Import, cst.RemovalSentinel]:
        names_to_keep = []
        for import_alias in original_node.names:
            if import_alias.evaluated_name not in self.unused_module_imports:
                # This is a keeper since we aren't removing it
                names_to_keep.append(import_alias)
                continue

            if (import_alias.evaluated_alias !=
                    self.unused_module_imports[import_alias.evaluated_name]):
                # This is a keeper since the alias does not match
                # what we are looking for.
                names_to_keep.append(import_alias)
                continue

            # Now that we know we want to remove this module, figure out if
            # there are any live references to it.
            if import_alias not in self._unused_imports:
                names_to_keep.append(import_alias)
                continue

        # no changes
        if names_to_keep == original_node.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] != original_node.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)
예제 #5
0
    def leave_Import(self, original_node: cst.Import,
                     updated_node: cst.Import) -> cst.Import:
        new_names = []
        for import_alias in updated_node.names:
            import_alias_name = import_alias.name
            import_alias_full_name = get_full_name_for_node(import_alias_name)
            if import_alias_full_name is None:
                raise Exception(
                    "Could not parse full name for ImportAlias.name node.")

            if isinstance(import_alias_name,
                          cst.Name) and self.old_name.startswith(
                              import_alias_full_name + "."):
                # Might, be in use elsewhere in the code, so schedule a potential removal, and add another alias.
                new_names.append(import_alias)
                self.scheduled_removals.add(original_node)
                new_names.append(
                    cst.ImportAlias(name=cst.Name(
                        value=self.gen_replacement_module(
                            import_alias_full_name))))
                self.bypass_import = True
            elif isinstance(import_alias_name,
                            cst.Attribute) and self.old_name.startswith(
                                import_alias_full_name + "."):
                # Same idea as above.
                new_names.append(import_alias)
                self.scheduled_removals.add(original_node)
                new_name_node: Union[
                    cst.Attribute, cst.Name] = self.gen_name_or_attr_node(
                        self.gen_replacement_module(import_alias_full_name))
                new_names.append(cst.ImportAlias(name=new_name_node))
                self.bypass_import = True
            else:
                new_names.append(import_alias)

        return updated_node.with_changes(names=new_names)