Esempio n. 1
0
 def traverse(self) -> None:
     if not self.skip_file():
         try:
             if C.PY38_PLUS:
                 tree = ast.parse(self.source, type_comments=True)
             else:
                 tree = ast.parse(self.source)
         except SyntaxError as e:
             print(
                 color.paint(str(e), color.RED) + " at " +
                 color.paint(self.path.as_posix(), color.GREEN))
             return None
         else:
             relate(tree)
             name_scanner = _NameScanner()
             name_scanner.traverse(tree)
             self.names.extend(name_scanner.names)
             import_scanner = _ImportScanner(
                 source=self.source,
                 names=self.names,
                 include_star_import=self.include_star_import,
             )
             import_scanner.traverse(tree)
             self.imports.extend(import_scanner.imports)
             self.import_names.extend([imp.name for imp in self.imports])
Esempio n. 2
0
    def traverse(self) -> None:
        if self.skip_file():
            return None

        try:
            if C.PY38_PLUS:
                tree = ast.parse(self.source, type_comments=True)
            else:
                tree = ast.parse(self.source)
        except SyntaxError as e:
            print(
                color.paint(str(e), color.RED) + " at " +
                color.paint(self.path.as_posix(), color.GREEN))
            return None
        """
        Set parent
        """
        relate(tree)

        Scope.add_global_scope(tree)
        """
        Name analyzer
        """
        _NameAnalyzer().visit(tree)
        """
        Receive items on the __all__ list
        """
        importable_visitor = _ImportableAnalyzer()
        importable_visitor.visit(tree)
        for node in importable_visitor.importable_nodes:
            if isinstance(node, ast.Constant):
                Name.register(
                    lineno=node.lineno,
                    name=str(node.value),
                    node=node,
                    is_all=True,
                )
            elif isinstance(node, ast.Str):
                Name.register(lineno=node.lineno,
                              name=node.s,
                              node=node,
                              is_all=True)
        importable_visitor.clear()
        """
        Import analyzer
        """
        _ImportAnalyzer(
            source=self.source,
            include_star_import=self.include_star_import,
        ).traverse(tree)

        Scope.remove_current_scope()
Esempio n. 3
0
def show(unused_import: List[C.ImportT], py_path: Path) -> None:
    for imp in unused_import:
        if isinstance(imp, ImportFrom) and imp.star and imp.suggestions:
            context = (
                color.paint(f"from {imp.name} import *", color.RED) + " -> " +
                color.paint(
                    f"from {imp.name} import {', '.join(imp.suggestions)}",
                    color.GREEN,
                ))
        else:
            context = color.paint(imp.name, color.YELLOW)
        print(context + " at " + color.paint(py_path.as_posix(), color.GREEN) +
              ":" + color.paint(str(imp.lineno), color.GREEN))
Esempio n. 4
0
 def test_red_paint_on_win(self):
     action_content = paint(self.content, RED)
     if TERMINAL_SUPPORT_COLOR:
         expected_content = RED + self.content + RESET
     else:
         expected_content = self.content
     self.assertEqual(expected_content, action_content)
Esempio n. 5
0
 def traverse(
     self,
     source: Union[str, bytes],
     mode: str = "exec",
     parent: Optional[ast.AST] = None,
 ) -> None:
     try:
         if PY38_PLUS:
             tree = ast.parse(source, mode=mode, type_comments=True)
         else:
             tree = ast.parse(source, mode=mode)
     except SyntaxError as err:
         if self.show_error:
             print(color.paint(str(err), color.RED))  # pragma: no cover
         raise err
     relate(tree, parent=parent)
     self.visit(tree)
     """
     Receive items on the __all__ list
     """
     importable_visitor = ImportableVisitor()
     importable_visitor.traverse(self.source)
     for node in importable_visitor.importable_nodes:
         if isinstance(node, ast.Constant):
             self.names.append(
                 Name(lineno=node.lineno, name=str(node.value)))
         elif isinstance(node, ast.Str):
             self.names.append(Name(lineno=node.lineno, name=node.s))
Esempio n. 6
0
def check(
    path: Path,
    unused_imports: List[Union[Import, ImportFrom]],
    use_color_setting: bool,
) -> None:
    for imp in unused_imports:
        if isinstance(imp, ImportFrom) and imp.star and imp.suggestions:
            context = (
                paint(f"from {imp.name} import *", RED, use_color_setting) +
                " -> " + paint(
                    f"from {imp.name} import {', '.join(imp.suggestions)}",
                    GREEN,
                    use_color_setting,
                ))
        else:
            context = paint(imp.name, YELLOW, use_color_setting)
        print(context + " at " +
              paint(path.as_posix(), GREEN, use_color_setting) + ":" +
              paint(str(imp.lineno), GREEN, use_color_setting))
Esempio n. 7
0
 def read(self, path: Path) -> Tuple[str, str]:
     try:
         with tokenize.open(path) as stream:
             source = stream.read()
             encoding = stream.encoding
     except (OSError, SyntaxError) as err:
         if self.show_error:
             print(color.paint(str(err), color.RED))  # pragma: no cover
         return "", "utf-8"
     return source, encoding
Esempio n. 8
0
 def test_red_paint(self):
     action_content = paint(self.content, RED)
     expected_content = RED + self.content + RESET
     self.assertEqual(expected_content, action_content)
Esempio n. 9
0
def main(argv: Optional[Sequence[str]] = None) -> int:
    parser = argparse.ArgumentParser(
        prog="unimport",
        description=C.DESCRIPTION,
        epilog="Get rid of all unused imports 🥳",
    )
    exclusive_group = parser.add_mutually_exclusive_group(required=False)
    parser.add_argument(
        "sources",
        default=[Path(".")],
        nargs="*",
        help="files and folders to find the unused imports.",
        action="store",
        type=Path,
    )
    parser.add_argument(
        "-c",
        "--config",
        default=".",
        help="read configuration from PATH.",
        metavar="PATH",
        action="store",
        type=Path,
    )
    parser.add_argument(
        "--include",
        help="file include pattern.",
        metavar="include",
        action="store",
        default=[],
        type=lambda value: [value],
    )
    parser.add_argument(
        "--exclude",
        help="file exclude pattern.",
        metavar="exclude",
        action="store",
        default=[],
        type=lambda value: [value],
    )
    parser.add_argument(
        "--gitignore",
        action="store_true",
        help="exclude .gitignore patterns. if present.",
    )
    parser.add_argument(
        "--include-star-import",
        action="store_true",
        help="Include star imports during scanning and refactor.",
    )
    parser.add_argument(
        "--show-error",
        action="store_true",
        help="Show or don't show errors captured during static analysis.",
    )
    parser.add_argument(
        "-d",
        "--diff",
        action="store_true",
        help="Prints a diff of all the changes unimport would make to a file.",
    )
    exclusive_group.add_argument(
        "-r",
        "--remove",
        action="store_true",
        help="remove unused imports automatically.",
    )
    exclusive_group.add_argument(
        "-p",
        "--permission",
        action="store_true",
        help="Refactor permission after see diff.",
    )
    parser.add_argument(
        "--requirements",
        action="store_true",
        help=
        "Include requirements.txt file, You can use it with all other arguments",
    )
    parser.add_argument(
        "--check",
        action="store_true",
        help="Prints which file the unused imports are in.",
    )
    parser.add_argument(
        "-v",
        "--version",
        action="version",
        version=f"Unimport {C.VERSION}",
        help="Prints version of unimport",
    )
    argv = argv if argv is not None else sys.argv[1:]
    args = parser.parse_args(argv)
    session = Session(
        config_file=args.config,
        include_star_import=args.include_star_import,
        show_error=args.show_error,
    )
    args.remove = args.remove or session.config.remove  # type: ignore
    args.diff = any(
        (args.diff, args.permission, session.config.diff))  # type: ignore
    args.check = args.check or not any((args.diff, args.remove))
    args.requirements = args.requirements or session.config.requirements  # type: ignore
    args.gitignore = args.gitignore or session.config.gitignore  # type: ignore
    args.sources.extend(session.config.sources)  # type: ignore
    args.include.extend(session.config.include)  # type: ignore
    args.exclude.extend(session.config.exclude)  # type: ignore
    if args.gitignore:
        args.exclude.extend(get_exclude_list_from_gitignore())
    include = re.compile("|".join(args.include)).pattern
    exclude = re.compile("|".join(args.exclude)).pattern
    unused_modules = set()
    packages: Set[str] = set()
    for source_path in args.sources:
        for py_path in session.list_paths(source_path, include, exclude):
            session.scanner.scan(source=session.read(py_path)[0])
            unused_imports = session.scanner.unused_imports
            unused_modules.update({imp.name for imp in unused_imports})
            packages.update(
                get_used_packages(session.scanner.imports,
                                  session.scanner.unused_imports))
            if args.check:
                show(unused_imports, py_path)
            session.scanner.clear()
            if args.diff:
                exists_diff = print_if_exists(session.diff_file(py_path))
            if args.permission and exists_diff:
                action = input(
                    f"Apply suggested changes to '{color.paint(str(py_path), color.YELLOW)}' [Y/n/q] ? >"
                ).lower()
                if action == "q":
                    return 1
                elif actiontobool(action):
                    args.remove = True
            if args.remove and session.refactor_file(py_path, apply=True)[1]:
                print(
                    f"Refactoring '{color.paint(str(py_path), color.GREEN)}'")
    if not unused_modules and args.check:
        print(
            color.paint(
                "✨ Congratulations there is no unused import in your project. ✨",
                color.GREEN,
            ))
    if args.requirements and packages:
        for requirements in Path(".").glob("requirements*.txt"):
            splitlines_requirements = requirements.read_text().splitlines()
            result = splitlines_requirements.copy()
            for index, requirement in enumerate(splitlines_requirements):
                module_name = package_name_from_metadata(
                    requirement.split("==")[0])
                if module_name is None:
                    if args.show_error:
                        print(
                            color.paint(requirement + " not found", color.RED))
                    continue
                if module_name not in packages:
                    result.remove(requirement)
                    if args.check:
                        print(
                            f"{color.paint(requirement, color.CYAN)} at "
                            f"{color.paint(requirements.as_posix(), color.CYAN)}:{color.paint(str(index + 1), color.CYAN)}"
                        )
            if args.diff:
                exists_diff = print_if_exists(
                    tuple(
                        difflib.unified_diff(
                            splitlines_requirements,
                            result,
                            fromfile=requirements.as_posix(),
                        )))
            if args.permission and exists_diff:
                action = input(
                    f"Apply suggested changes to '{color.paint(requirements.as_posix(), color.CYAN)}' [Y/n/q] ? >"
                ).lower()
                if action == "q":
                    return 1
                if actiontobool(action):
                    args.remove = True
            if args.remove:
                requirements.write_text("".join(result))
                print(
                    f"Refactoring '{color.paint(requirements.as_posix(), color.CYAN)}'"
                )
    if unused_modules:
        return 1
    else:
        return 0
Esempio n. 10
0
def main(argv: Optional[Sequence[str]] = None) -> int:
    parser = argparse.ArgumentParser(
        prog="unimport",
        description=C.DESCRIPTION,
        epilog="Get rid of all unused imports 🥳",
    )
    exclusive_group = parser.add_mutually_exclusive_group(required=False)
    parser.add_argument(
        "sources",
        default=default_config.sources,
        nargs="*",
        help="Files and folders to find the unused imports.",
        action="store",
        type=Path,
    )
    parser.add_argument(
        "--check",
        action="store_true",
        help="Prints which file the unused imports are in.",
        default=default_config.check,
    )
    parser.add_argument(
        "-c",
        "--config",
        default=".",
        help="Read configuration from PATH.",
        metavar="PATH",
        action="store",
        type=Path,
    )
    parser.add_argument(
        "--include",
        help="File include pattern.",
        metavar="include",
        action="store",
        default=default_config.include,
        type=str,
    )
    parser.add_argument(
        "--exclude",
        help="File exclude pattern.",
        metavar="exclude",
        action="store",
        default=default_config.exclude,
        type=str,
    )
    parser.add_argument(
        "--gitignore",
        action="store_true",
        help="Exclude .gitignore patterns. if present.",
        default=default_config.gitignore,
    )
    parser.add_argument(
        "--ignore-init",
        action="store_true",
        help="Ignore the __init__.py file.",
        default=default_config.ignore_init,
    )
    parser.add_argument(
        "--include-star-import",
        action="store_true",
        help="Include star imports during scanning and refactor.",
        default=default_config.include_star_import,
    )
    parser.add_argument(
        "-d",
        "--diff",
        action="store_true",
        help="Prints a diff of all the changes unimport would make to a file.",
        default=default_config.diff,
    )
    exclusive_group.add_argument(
        "-r",
        "--remove",
        action="store_true",
        help="Remove unused imports automatically.",
        default=default_config.remove,
    )
    exclusive_group.add_argument(
        "-p",
        "--permission",
        action="store_true",
        help="Refactor permission after see diff.",
        default=default_config.permission,
    )
    parser.add_argument(
        "--requirements",
        action="store_true",
        help=
        "Include requirements.txt file, You can use it with all other arguments",
        default=default_config.requirements,
    )
    parser.add_argument(
        "-v",
        "--version",
        action="version",
        version=f"Unimport {C.VERSION}",
        help="Prints version of unimport",
    )
    argv = argv if argv is not None else sys.argv[1:]
    args = parser.parse_args(argv)
    config = (Config(args.config).parse() if args.config
              and args.config.name in CONFIG_FILES else default_config)
    config = config.merge(**vars(args))
    unused_modules = set()
    used_packages: Set[str] = set()
    for source_path in config.sources:
        for py_path in utils.list_paths(source_path, config.include,
                                        config.exclude):
            source, encoding = utils.read(py_path)
            analyzer = Analyzer(
                source=source,
                path=py_path,
                include_star_import=config.include_star_import,
            )
            analyzer.traverse()
            unused_imports = list(analyzer.get_unused_imports())
            unused_modules.update({imp.name for imp in unused_imports})
            used_packages.update(
                utils.get_used_packages(analyzer.imports, unused_imports))
            if config.check:
                for imp in unused_imports:
                    if (isinstance(imp, ImportFrom) and imp.star
                            and imp.suggestions):
                        context = (color.paint(
                            f"from {imp.name} import *", color.RED
                        ) + " -> " + color.paint(
                            f"from {imp.name} import {', '.join(imp.suggestions)}",
                            color.GREEN,
                        ))
                    else:
                        context = color.paint(imp.name, color.YELLOW)
                    print(context + " at " +
                          color.paint(py_path.as_posix(), color.GREEN) + ":" +
                          color.paint(str(imp.lineno), color.GREEN))
            if any((config.diff, config.remove)):
                refactor_result = refactor_string(
                    source=source,
                    unused_imports=unused_imports,
                )
            if config.diff:
                diff = utils.diff(
                    source=source,
                    refactor_result=refactor_result,
                    fromfile=py_path,
                )
                exists_diff = bool(diff)
                if exists_diff:
                    print(color.difference(diff))
            if config.permission and exists_diff:
                action = input(
                    f"Apply suggested changes to '{color.paint(str(py_path), color.YELLOW)}' [Y/n/q] ? >"
                ).lower()
                if action == "q":
                    return 1
                elif utils.actiontobool(action):
                    config = config._replace(remove=True)
            if config.remove and source != refactor_result:
                py_path.write_text(refactor_result, encoding=encoding)
                print(
                    f"Refactoring '{color.paint(str(py_path), color.GREEN)}'")
            analyzer.clear()
    if not unused_modules and config.check:
        print(
            color.paint(
                "✨ Congratulations there is no unused import in your project. ✨",
                color.GREEN,
            ))
    if config.requirements:
        for requirements in Path(".").glob("requirements*.txt"):
            source = requirements.read_text()
            copy_source = source.splitlines().copy()
            for index, requirement in enumerate(source.splitlines()):
                module_name = utils.package_name_from_metadata(
                    requirement.split("==")[0])
                if module_name is None:
                    print(color.paint(requirement + " not found", color.RED))
                    continue
                if module_name not in used_packages:
                    copy_source.remove(requirement)
                    if config.check:
                        print(
                            f"{color.paint(requirement, color.CYAN)} at "
                            f"{color.paint(requirements.as_posix(), color.CYAN)}:{color.paint(str(index + 1), color.CYAN)}"
                        )
            refactor_result = "\n".join(copy_source)
            if config.diff:
                diff = utils.diff(
                    source=source,
                    refactor_result=refactor_result,
                    fromfile=requirements,
                )
                exists_diff = bool(diff)
                if exists_diff:
                    print(color.difference(diff))
            if config.permission and exists_diff:
                action = input(
                    f"Apply suggested changes to '{color.paint(requirements.as_posix(), color.CYAN)}' [Y/n/q] ? >"
                ).lower()
                if action == "q":
                    return 1
                if utils.actiontobool(action):
                    config = config._replace(remove=True)
            if config.remove:
                requirements.write_text(refactor_result)
                print(
                    f"Refactoring '{color.paint(requirements.as_posix(), color.CYAN)}'"
                )
    if unused_modules:
        return 1
    else:
        return 0
Esempio n. 11
0
def test_use_color_setting_true():
    text = "test text"

    action_text = paint(text, RED, True)
    assert RED + text + RESET == action_text
Esempio n. 12
0
def test_use_color_setting_false():
    text = "test text"

    action_text = paint(text, RED, False)
    assert text == action_text
Esempio n. 13
0
def test_red_paint():
    text = "test text"

    action_text = paint(text, RED)
    assert RED + text + RESET == action_text
Esempio n. 14
0
def main(argv: Optional[Sequence[str]] = None) -> int:
    parser = argparse.ArgumentParser(
        prog="unimport",
        description=C.DESCRIPTION,
        epilog=f"Get rid of all unused imports {emoji.PARTYING_FACE}",
    )
    exclusive_group = parser.add_mutually_exclusive_group(required=False)

    options.add_sources_option(parser)
    options.add_check_option(parser)
    options.add_config_option(parser)
    color.add_color_option(parser)
    options.add_include_option(parser)
    options.add_exclude_option(parser)
    options.add_gitignore_option(parser)
    options.add_ignore_init_option(parser)
    options.add_include_star_import_option(parser)
    options.add_diff_option(parser)
    options.add_remove_option(exclusive_group)
    options.add_permission_option(exclusive_group)
    options.add_requirements_option(parser)
    options.add_version_option(parser)

    argv = argv if argv is not None else sys.argv[1:]
    args = parser.parse_args(argv)
    config = Config.get_config(args)

    unused_modules = set()
    used_packages: Set[str] = set()

    for path in config.get_paths():
        source, encoding, newline = utils.read(path)

        with Analyzer(
            source=source,
            path=path,
            include_star_import=config.include_star_import,
        ):
            unused_imports = list(
                Import.get_unused_imports(config.include_star_import)
            )
            unused_modules.update({imp.name for imp in unused_imports})
            used_packages.update(
                utils.get_used_packages(Import.imports, unused_imports)
            )
            if config.check:
                commands.check(path, unused_imports, args.color)
            if any((config.diff, config.remove)):
                refactor_result = refactor_string(
                    source=source,
                    unused_imports=unused_imports,
                )
                if config.diff:
                    exists_diff = commands.diff(path, source, refactor_result)
                if config.permission and exists_diff:
                    commands.permission(
                        path, encoding, newline, refactor_result, args.color
                    )
                if config.remove and source != refactor_result:
                    commands.remove(
                        path, encoding, newline, refactor_result, args.color
                    )

    if not unused_modules and config.check:
        print(
            color.paint(
                f"{emoji.STAR} Congratulations there is no unused import in your project. {emoji.STAR}",
                color.GREEN,
                args.color,
            )
        )
    if config.requirements:
        for path in config.get_requirements():
            source = path.read_text()
            copy_source = source.splitlines().copy()

            for index, requirement in enumerate(source.splitlines()):
                module_name = utils.package_name_from_metadata(
                    requirement.split("==")[0]
                )
                if module_name is None:
                    print(
                        color.paint(
                            requirement + " not found", color.RED, args.color
                        )
                    )
                    continue

                if module_name not in used_packages:
                    copy_source.remove(requirement)

                    if config.check:
                        commands.requirements_check(
                            path, index, requirement, args.color
                        )

            refactor_result = "\n".join(copy_source)
            if config.diff:
                exists_diff = commands.diff(path, source, refactor_result)
                if config.permission and exists_diff:
                    commands.requirements_permission(
                        path, refactor_result, args.color
                    )
                if config.remove and source != refactor_result:
                    commands.requirements_remove(
                        path, refactor_result, args.color
                    )

    if unused_modules:
        return 1
    else:
        return 0