├── .dockerignore ├── .github ├── FUNDING.yml ├── PULL_REQUEST_TEMPLATE.md ├── codecov.yml └── workflows │ ├── docker.yml │ ├── docs.yml │ ├── pre-commit.yml │ ├── pypi.yml │ ├── test.yml │ └── unimport.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .pre-commit-hooks.yaml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── action.yml ├── docs ├── .nojekyll ├── AUTHORS.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── assets │ └── logo │ │ ├── unimport.ai │ │ └── unimport.png ├── index.md ├── installation.md ├── overrides │ └── main.html └── tutorial │ ├── command-line-options.md │ ├── configurations.md │ ├── other-useful-features.md │ ├── supported-behaviors.md │ ├── use-with-docker.md │ ├── use-with-github-action.md │ └── use-with-pre-commit.md ├── mkdocs.yml ├── pyproject.toml ├── setup.cfg ├── src └── unimport │ ├── __init__.py │ ├── __main__.py │ ├── analyzers │ ├── __init__.py │ ├── decarators.py │ ├── import_statement.py │ ├── importable.py │ ├── main.py │ ├── name.py │ └── utils.py │ ├── color.py │ ├── commands │ ├── __init__.py │ ├── check.py │ ├── diff.py │ ├── options.py │ ├── parser.py │ ├── permission.py │ └── remove.py │ ├── config.py │ ├── constants.py │ ├── enums.py │ ├── exceptions.py │ ├── main.py │ ├── meta.py │ ├── py.typed │ ├── refactor.py │ ├── statement.py │ ├── typing.py │ └── utils.py ├── tests ├── __init__.py ├── cases │ ├── __init__.py │ ├── analyzer │ │ ├── all │ │ │ └── module_from_all_set_the_name.py │ │ ├── as_import │ │ │ ├── all_unused_all_cases.py │ │ │ ├── dotted_module_name_simple.py │ │ │ ├── multiple_from_as_import.py │ │ │ ├── multiple_import_name_as_import.py │ │ │ ├── multiple_import_name_as_import_duplicate.py │ │ │ └── used_all_cases.py │ │ ├── dotted │ │ │ └── dotted_module_name_simple.py │ │ ├── duplicate_import │ │ │ ├── different_duplicate_unused.py │ │ │ ├── different_duplicate_used.py │ │ │ ├── full_unused.py │ │ │ ├── import_in_function.py │ │ │ ├── import_in_function_used_two_different.py │ │ │ ├── multi_duplicate.py │ │ │ ├── multi_duplicate_one_used.py │ │ │ ├── one_used.py │ │ │ ├── one_used_bottom_multi_duplicate.py │ │ │ ├── same_line.py │ │ │ ├── three_used.py │ │ │ ├── two_multi_duplicate_one_used.py │ │ │ └── two_used.py │ │ ├── error │ │ │ ├── as_import.py │ │ │ ├── bad_syntax.py │ │ │ ├── import_else_another.py │ │ │ ├── syntax_error.py │ │ │ ├── try_except.py │ │ │ └── tuple.py │ │ ├── if_dispatch │ │ │ ├── case_1.py │ │ │ ├── case_2.py │ │ │ ├── case_3.py │ │ │ ├── case_4.py │ │ │ ├── case_5.py │ │ │ └── case_6.py │ │ ├── scope │ │ │ ├── global_import.py │ │ │ ├── nonlocal_import.py │ │ │ ├── nonlocal_import_2.py │ │ │ ├── nonlocal_import_3.py │ │ │ └── nonlocal_import_5.py │ │ ├── star_import │ │ │ ├── 2.py │ │ │ ├── get_source_from_importable_names.py │ │ │ ├── star_imports.py │ │ │ └── two_suggestions.py │ │ ├── statement │ │ │ └── match_statement.py │ │ ├── style │ │ │ ├── 1_vertical.py │ │ │ ├── 2_1_vertical.py │ │ │ ├── 3_1_vertical.py │ │ │ ├── 4_1_paren_horizontal.py │ │ │ └── vertical_dont_add_extra_line.py │ │ ├── type_variable │ │ │ ├── type_assing_cast.py │ │ │ ├── type_assing_list.py │ │ │ └── type_assing_union.py │ │ ├── typing │ │ │ ├── function_arg.py │ │ │ ├── function_return.py │ │ │ ├── function_str_arg.py │ │ │ ├── type_comment_funcdef.py │ │ │ ├── type_comment_params.py │ │ │ ├── type_comments.py │ │ │ ├── type_comments_with_variable.py │ │ │ ├── type_parameter_syntax.py │ │ │ └── variable.py │ │ └── unused │ │ │ ├── comment.py │ │ │ ├── do_not_remove_augmented_imports.py │ │ │ ├── future_from_import.py │ │ │ ├── future_import.py │ │ │ ├── get_star_imp_none.py │ │ │ ├── inside_function_unused.py │ │ │ ├── local_import.py │ │ │ ├── multiple_imports.py │ │ │ ├── remove_unused_from_imports.py │ │ │ ├── star.py │ │ │ └── startswith_name.py │ ├── refactor │ │ ├── all │ │ │ └── module_from_all_set_the_name.py │ │ ├── as_import │ │ │ ├── all_unused_all_cases.py │ │ │ ├── dotted_module_name_simple.py │ │ │ ├── multiple_from_as_import.py │ │ │ ├── multiple_import_name_as_import.py │ │ │ ├── multiple_import_name_as_import_duplicate.py │ │ │ └── used_all_cases.py │ │ ├── dotted │ │ │ └── dotted_module_name_simple.py │ │ ├── duplicate_import │ │ │ ├── different_duplicate_unused.py │ │ │ ├── different_duplicate_used.py │ │ │ ├── full_unused.py │ │ │ ├── import_in_function.py │ │ │ ├── import_in_function_used_two_different.py │ │ │ ├── multi_duplicate.py │ │ │ ├── multi_duplicate_one_used.py │ │ │ ├── one_used.py │ │ │ ├── one_used_bottom_multi_duplicate.py │ │ │ ├── same_line.py │ │ │ ├── three_used.py │ │ │ ├── two_multi_duplicate_one_used.py │ │ │ └── two_used.py │ │ ├── error │ │ │ ├── as_import.py │ │ │ ├── bad_syntax.py │ │ │ ├── import_else_another.py │ │ │ ├── syntax_error.py │ │ │ ├── try_except.py │ │ │ └── tuple.py │ │ ├── if_dispatch │ │ │ ├── case_1.py │ │ │ ├── case_2.py │ │ │ ├── case_3.py │ │ │ ├── case_4.py │ │ │ ├── case_5.py │ │ │ └── case_6.py │ │ ├── scope │ │ │ ├── global_import.py │ │ │ ├── nonlocal_import.py │ │ │ ├── nonlocal_import_2.py │ │ │ ├── nonlocal_import_3.py │ │ │ └── nonlocal_import_5.py │ │ ├── star_import │ │ │ ├── 2.py │ │ │ ├── get_source_from_importable_names.py │ │ │ ├── star_imports.py │ │ │ └── two_suggestions.py │ │ ├── statement │ │ │ └── match_statement.py │ │ ├── style │ │ │ ├── 1_vertical.py │ │ │ ├── 2_1_vertical.py │ │ │ ├── 3_1_vertical.py │ │ │ ├── 4_1_paren_horizontal.py │ │ │ └── vertical_dont_add_extra_line.py │ │ ├── type_variable │ │ │ ├── type_assing_cast.py │ │ │ ├── type_assing_list.py │ │ │ └── type_assing_union.py │ │ ├── typing │ │ │ ├── function_arg.py │ │ │ ├── function_return.py │ │ │ ├── function_str_arg.py │ │ │ ├── type_comment_funcdef.py │ │ │ ├── type_comment_params.py │ │ │ ├── type_comments.py │ │ │ ├── type_comments_with_variable.py │ │ │ ├── type_parameter_syntax.py │ │ │ └── variable.py │ │ └── unused │ │ │ ├── comment.py │ │ │ ├── do_not_remove_augmented_imports.py │ │ │ ├── future_from_import.py │ │ │ ├── future_import.py │ │ │ ├── get_star_imp_none.py │ │ │ ├── inside_function_unused.py │ │ │ ├── local_import.py │ │ │ ├── multiple_imports.py │ │ │ ├── remove_unused_from_imports.py │ │ │ ├── star.py │ │ │ └── startswith_name.py │ ├── source │ │ ├── all │ │ │ └── module_from_all_set_the_name.py │ │ ├── as_import │ │ │ ├── all_unused_all_cases.py │ │ │ ├── dotted_module_name_simple.py │ │ │ ├── multiple_from_as_import.py │ │ │ ├── multiple_import_name_as_import.py │ │ │ ├── multiple_import_name_as_import_duplicate.py │ │ │ └── used_all_cases.py │ │ ├── dotted │ │ │ └── dotted_module_name_simple.py │ │ ├── duplicate_import │ │ │ ├── different_duplicate_unused.py │ │ │ ├── different_duplicate_used.py │ │ │ ├── full_unused.py │ │ │ ├── import_in_function.py │ │ │ ├── import_in_function_used_two_different.py │ │ │ ├── multi_duplicate.py │ │ │ ├── multi_duplicate_one_used.py │ │ │ ├── one_used.py │ │ │ ├── one_used_bottom_multi_duplicate.py │ │ │ ├── same_line.py │ │ │ ├── three_used.py │ │ │ ├── two_multi_duplicate_one_used.py │ │ │ └── two_used.py │ │ ├── error │ │ │ ├── as_import.py │ │ │ ├── bad_syntax.py │ │ │ ├── import_else_another.py │ │ │ ├── syntax_error.py │ │ │ ├── try_except.py │ │ │ └── tuple.py │ │ ├── if_dispatch │ │ │ ├── case_1.py │ │ │ ├── case_2.py │ │ │ ├── case_3.py │ │ │ ├── case_4.py │ │ │ ├── case_5.py │ │ │ └── case_6.py │ │ ├── scope │ │ │ ├── global_import.py │ │ │ ├── nonlocal_import.py │ │ │ ├── nonlocal_import_2.py │ │ │ ├── nonlocal_import_3.py │ │ │ └── nonlocal_import_5.py │ │ ├── star_import │ │ │ ├── 2.py │ │ │ ├── get_source_from_importable_names.py │ │ │ ├── star_imports.py │ │ │ └── two_suggestions.py │ │ ├── statement │ │ │ └── match_statement.py │ │ ├── style │ │ │ ├── 1_vertical.py │ │ │ ├── 2_1_vertical.py │ │ │ ├── 3_1_vertical.py │ │ │ ├── 4_1_paren_horizontal.py │ │ │ └── vertical_dont_add_extra_line.py │ │ ├── type_variable │ │ │ ├── type_assing_cast.py │ │ │ ├── type_assing_list.py │ │ │ └── type_assing_union.py │ │ ├── typing │ │ │ ├── function_arg.py │ │ │ ├── function_return.py │ │ │ ├── function_str_arg.py │ │ │ ├── type_comment_funcdef.py │ │ │ ├── type_comment_params.py │ │ │ ├── type_comments.py │ │ │ ├── type_comments_with_variable.py │ │ │ ├── type_parameter_syntax.py │ │ │ └── variable.py │ │ └── unused │ │ │ ├── comment.py │ │ │ ├── do_not_remove_augmented_imports.py │ │ │ ├── future_from_import.py │ │ │ ├── future_import.py │ │ │ ├── get_star_imp_none.py │ │ │ ├── inside_function_unused.py │ │ │ ├── local_import.py │ │ │ ├── remove_unused_from_imports.py │ │ │ ├── star.py │ │ │ └── startswith_name.py │ └── test_cases.py ├── commands │ ├── __init__.py │ ├── test_check.py │ ├── test_diff.py │ ├── test_options.py │ ├── test_parser.py │ ├── test_permission.py │ └── test_remove.py ├── config │ ├── configs │ │ ├── like-commands │ │ │ ├── pyproject.toml │ │ │ └── setup.cfg │ │ ├── mistyped │ │ │ ├── pyproject.toml │ │ │ └── setup.cfg │ │ ├── no_unimport │ │ │ ├── pyproject.toml │ │ │ └── setup.cfg │ │ ├── pyproject.toml │ │ └── setup.cfg │ └── test_config.py ├── conftest.py ├── test_color.py ├── test_enum.py ├── test_linesep.py ├── test_main.py ├── test_utils.py └── utils.py └── tox.ini /.dockerignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.pyc 3 | .tox 4 | .coverage 5 | .coverage.* 6 | coverage.xml 7 | .git 8 | .mypy_cache 9 | .pytest_cache 10 | venv 11 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [hakancelikdev] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: yes 4 | 5 | comment: false 6 | 7 | ignore: 8 | - "setup.py" 9 | - "tests" 10 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: build-and-push-image 2 | 3 | on: 4 | push: 5 | branches: 6 | - stable 7 | 8 | # Publish `1.2.3` tags as releases. 9 | tags: 10 | - "**" 11 | 12 | env: 13 | IMAGE_NAME: unimport 14 | 15 | jobs: 16 | build-and-push: 17 | runs-on: ubuntu-latest 18 | permissions: 19 | packages: write 20 | contents: read 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | 25 | - name: Build image 26 | run: 27 | docker build . --file Dockerfile --tag $IMAGE_NAME --label 28 | "runnumber=${GITHUB_RUN_ID}" 29 | 30 | - name: Log in to registry 31 | # This is where you will update the PAT to GH_TOKEN 32 | run: 33 | echo "${{ secrets.GH_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} 34 | --password-stdin 35 | 36 | - name: Push image 37 | run: | 38 | IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME 39 | 40 | # Strip git ref prefix from version 41 | VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') 42 | 43 | # [ "$VERSION" == "main" ] && VERSION=latest 44 | # [ "$VERSION" == "stable" ] && VERSION=stable 45 | 46 | docker tag $IMAGE_NAME $IMAGE_ID:$VERSION 47 | docker push $IMAGE_ID:$VERSION 48 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: build-docs 2 | on: 3 | push: 4 | tags: 5 | - "**" 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3.5.3 11 | - uses: actions/setup-python@v4.6.1 12 | with: 13 | python-version: "3.12" 14 | architecture: "x64" 15 | 16 | - name: Install Dependencies 17 | run: | 18 | python -m pip install --upgrade pip 19 | python -m pip install .[docs] 20 | 21 | - name: Build Docs 22 | run: | 23 | git config --local user.email "hakancelikdev@gmail.com" 24 | git config --local user.name "Hakan Celik" 25 | git fetch --all 26 | mike deploy ${{github.ref_name}} latest --update-aliases --push 27 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: pre-commit 2 | on: [push, pull_request] 3 | jobs: 4 | run: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v3.5.3 8 | - uses: actions/setup-python@v4.6.1 9 | - name: Install dependencies for pre-commit 10 | run: | 11 | python -m pip install --upgrade pip 12 | python -m pip install tox 13 | - name: pre-commit 14 | run: | 15 | tox -e pre-commit 16 | -------------------------------------------------------------------------------- /.github/workflows/pypi.yml: -------------------------------------------------------------------------------- 1 | name: publish-package-on-pypi 2 | on: 3 | push: 4 | tags: 5 | - "**" 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3.5.3 11 | - uses: actions/setup-python@v4.6.1 12 | with: 13 | python-version: "3.12" 14 | architecture: "x64" 15 | 16 | - name: Install Dependencies 17 | run: | 18 | python -m pip install --upgrade pip 19 | python -m pip install --upgrade build 20 | 21 | - run: python -m build 22 | - name: Publish package to TestPyPI 23 | uses: pypa/gh-action-pypi-publish@release/v1 24 | with: 25 | password: ${{ secrets.TEST_PYPI_API_TOKEN }} 26 | repository_url: https://test.pypi.org/legacy/ 27 | verbose: true 28 | 29 | publish: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v3.5.3 33 | - uses: actions/setup-python@v4.6.1 34 | with: 35 | python-version: "3.12" 36 | architecture: "x64" 37 | 38 | - name: Install Dependencies 39 | run: | 40 | python -m pip install --upgrade pip 41 | python -m pip install --upgrade build 42 | 43 | - run: python -m build 44 | - name: Publish a Python distribution to PyPI 45 | uses: pypa/gh-action-pypi-publish@release/v1 46 | with: 47 | password: ${{ secrets.PYPI_API_TOKEN }} 48 | verbose: true 49 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | runs-on: ${{ matrix.os }} 6 | strategy: 7 | matrix: 8 | os: [ubuntu-latest, macos-latest, windows-latest] 9 | python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] 10 | steps: 11 | - uses: actions/checkout@v3.5.3 12 | 13 | - name: Set up Python${{ matrix.python-version }} 14 | uses: actions/setup-python@v4.6.1 15 | with: 16 | python-version: ${{ matrix.python-version }} 17 | architecture: x64 18 | 19 | - name: Install Dependencies for Python${{ matrix.python-version }} 20 | run: | 21 | python -m pip install --upgrade pip 22 | python -m pip install tox 23 | 24 | - name: Test with pytest for Python${{ matrix.python-version }} 25 | run: | 26 | tox -e ${{ matrix.python-version }} 27 | 28 | coverage: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v3.5.3 32 | 33 | - name: Set up Python${{ matrix.python-version }} 34 | uses: actions/setup-python@v4.6.1 35 | with: 36 | python-version: ${{ matrix.python-version }} 37 | architecture: x64 38 | 39 | - name: Install Dependencies 40 | run: | 41 | python -m pip install --upgrade pip 42 | python -m pip install .[test] 43 | 44 | - name: Generate coverage report 45 | run: | 46 | python -m pytest --cov=./ --cov-report=xml 47 | 48 | - name: Upload coverage to Codecov 49 | uses: codecov/codecov-action@v1.0.14 50 | with: 51 | token: ${{secrets.CODECOV_TOKEN }} 52 | -------------------------------------------------------------------------------- /.github/workflows/unimport.yml: -------------------------------------------------------------------------------- 1 | name: unimport 2 | on: [push, pull_request] 3 | jobs: 4 | run: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v3.5.3 8 | - uses: actions/setup-python@v4.6.1 9 | - name: unimport 10 | uses: ./ 11 | with: 12 | extra_args: --include src/ 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env*/ 86 | .venv 87 | env*/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | # typing 107 | monkeytype.sqlite3 108 | 109 | # custom 110 | .idea/ 111 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | exclude: tests/cases/(refactor|source).* 2 | 3 | repos: 4 | - repo: https://github.com/psf/black 5 | rev: 23.12.1 6 | hooks: 7 | - id: black 8 | 9 | - repo: https://github.com/PyCQA/isort 10 | rev: 5.13.2 11 | hooks: 12 | - id: isort 13 | 14 | - repo: https://github.com/hakancelikdev/unimport 15 | rev: 1.2.1 16 | hooks: 17 | - id: unimport 18 | 19 | - repo: https://github.com/pre-commit/mirrors-mypy 20 | rev: v1.8.0 21 | hooks: 22 | - id: mypy 23 | additional_dependencies: [types-toml==0.1.3] 24 | 25 | - repo: https://github.com/pre-commit/mirrors-prettier 26 | rev: v4.0.0-alpha.8 27 | hooks: 28 | - id: prettier 29 | args: [--prose-wrap=always, --print-width=88] 30 | 31 | - repo: https://github.com/pre-commit/pre-commit-hooks 32 | rev: v4.5.0 33 | hooks: 34 | - id: end-of-file-fixer 35 | files: "\\.(py|.txt|.yaml|.json|.in|.md|.toml|.cfg|.html|.yml)$" 36 | 37 | - repo: https://github.com/asottile/pyupgrade 38 | rev: v3.15.0 39 | hooks: 40 | - id: pyupgrade 41 | args: [--py38-plus] 42 | 43 | - repo: https://github.com/hakancelikdev/unexport 44 | rev: 0.4.0 45 | hooks: 46 | - id: unexport 47 | args: 48 | - --refactor 49 | - --exclude=tests|setup.py|__init__.py| 50 | -------------------------------------------------------------------------------- /.pre-commit-hooks.yaml: -------------------------------------------------------------------------------- 1 | - id: unimport 2 | name: unimport 3 | description: 4 | "🚀 The ultimate linter and formatter for removing unused import statements in your 5 | code." 6 | entry: unimport 7 | language: python 8 | language_version: python3 9 | types: [python] 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-alpine 2 | 3 | COPY . /app 4 | WORKDIR /app 5 | 6 | RUN apk add cargo \ 7 | && pip install --no-cache-dir --upgrade pip \ 8 | && pip install --no-cache-dir . 9 | 10 | ENTRYPOINT ["python", "-m", "unimport"] 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Hakan Çelik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: lint test clean 2 | 3 | .PHONY: dev 4 | dev: 5 | python3.10 -m venv venv 6 | source venv/bin/activate; pip install pip -U; pip install -e .[test] 7 | source venv/bin/activate; pip install pre-commit 8 | 9 | .PHONY: lint 10 | lint: 11 | git add . 12 | source venv/bin/activate; pre-commit run 13 | 14 | .PHONY: test 15 | test: 16 | source venv/bin/activate; pytest tests -x -v --disable-warnings 17 | 18 | .PHONY: tox 19 | tox: 20 | tox 21 | 22 | .PHONY: clean 23 | clean: 24 | rm -rf `find . -name __pycache__` 25 | rm -f `find . -type f -name '*.py[co]' ` 26 | rm -f `find . -type f -name '*~' ` 27 | rm -f `find . -type f -name '.*~' ` 28 | rm -rf .cache 29 | rm -rf .pytest_cache 30 | rm -rf .mypy_cache 31 | rm -rf htmlcov 32 | rm -rf *.egg-info 33 | rm -f .coverage 34 | rm -f .coverage.* 35 | rm -rf .tox 36 | rm -rf build 37 | 38 | .PHONY: push 39 | push: 40 | git push origin head 41 | 42 | .PHONY: amend 43 | amend: 44 | git add . 45 | git commit --amend --no-edit 46 | git push origin head -f 47 | 48 | .PHONY: stable 49 | stable: 50 | git checkout main 51 | git branch -D stable 52 | git checkout -b stable 53 | git push origin head -f 54 | git checkout main 55 | 56 | .PHONY: git 57 | git: 58 | git config --local user.email "hakancelikdev@gmail.com" 59 | git config --local user.name "Hakan Celik" 60 | 61 | .PHONY: publish 62 | publish: 63 | python -m pip install --upgrade pip 64 | python -m pip install --upgrade build 65 | python -m pip install --upgrade twine 66 | python -m build 67 | python -m twine upload dist/* 68 | 69 | .PHONY: docs 70 | docs: 71 | source venv/bin/activate; pip install -e .[docs] 72 | source venv/bin/activate; mkdocs serve 73 | 74 | .PHONY: sync-main 75 | sync-main: 76 | git fetch origin main 77 | git rebase origin/main 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![unimport](https://raw.githubusercontent.com/hakancelikdev/unimport/main/docs/assets/logo/unimport.png) 2 | 3 | **🚀 The ultimate linter and formatter for removing unused import statements in your 4 | code.** 5 | 6 | Looking for a way to eliminate those pesky unused import statements in your code? Look 7 | no further than Unimport! This powerful tool serves as both a linter and formatter, 8 | making it easy to detect and remove any imports that are no longer needed. Say goodbye 9 | to cluttered, inefficient code and hello to a cleaner, more streamlined development 10 | process with Unimport. 11 | 12 | [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/hakancelikdev/unimport/main.svg)](https://results.pre-commit.ci/latest/github/hakancelikdev/unimport/main) 13 | ![test](https://github.com/hakancelikdev/unimport/workflows/test/badge.svg) 14 | 15 | [![Pypi](https://img.shields.io/pypi/v/unimport)](https://pypi.org/project/unimport/) 16 | ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/unimport) 17 | [![Downloads](https://static.pepy.tech/personalized-badge/unimport?period=total&units=international_system&left_color=grey&right_color=red&left_text=downloads)](https://pepy.tech/project/unimport) 18 | [![License](https://img.shields.io/github/license/hakancelikdev/unimport.svg)](https://github.com/hakancelikdev/unimport/blob/main/LICENSE) 19 | 20 | [![Forks](https://img.shields.io/github/forks/hakancelikdev/unimport)](https://github.com/hakancelikdev/unimport/fork) 21 | [![Issues](https://img.shields.io/github/issues/hakancelikdev/unimport)](https://github.com/hakancelikdev/unimport/issues) 22 | [![Stars](https://img.shields.io/github/stars/hakancelikdev/unimport)](https://github.com/hakancelikdev/unimport/stargazers) 23 | 24 | [![Codecov](https://codecov.io/gh/hakancelikdev/unimport/branch/main/graph/badge.svg)](https://codecov.io/gh/hakancelikdev/unimport) 25 | [![Contributors](https://img.shields.io/github/contributors/hakancelikdev/unimport)](https://github.com/hakancelikdev/unimport/graphs/contributors) 26 | [![Last Commit](https://img.shields.io/github/last-commit/hakancelikdev/unimport.svg)](https://github.com/hakancelikdev/unimport/commits/main) 27 | 28 | For more information see: https://unimport.hakancelik.dev/ 29 | 30 | Try it out now using the Unimport Playground, 31 | https://playground-unimport.hakancelik.dev/ 32 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | # action.yml 2 | name: "unimport" 3 | description: "Get rid of all unused imports 🥳" 4 | 5 | inputs: 6 | extra_args: 7 | description: "options to pass to unimport run" 8 | required: false 9 | default: "--check" 10 | runs: 11 | using: "composite" 12 | steps: 13 | - run: pip install --upgrade pip && python -m pip install unimport==1.2.1 14 | shell: bash 15 | - run: unimport --color auto --gitignore --ignore-init ${{ inputs.extra_args }} 16 | shell: bash 17 | branding: 18 | icon: "check" 19 | color: "red" 20 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakancelikdev/unimport/563fb2610888cb903ad4614eaee2e8fe0152535f/docs/.nojekyll -------------------------------------------------------------------------------- /docs/AUTHORS.md: -------------------------------------------------------------------------------- 1 | ## Authors 2 | 3 | - Hakan Çelik (@hakancelikdev) 4 | - Batuhan Taşkaya (@isidentical) 5 | - Gökmen Görgen (@gkmngrgn) 6 | 7 | ## Contributors 8 | 9 | 10 | 11 | - Benjamin Schubert (@BenjaminSchubert) 12 | - C.A.M. Gerlach (@CAM-Gerlach) 13 | - Drew Winstel (@drewbrew) 14 | - Furkan Önder (@furkanonder) 15 | - Hadi Alqattan (@hadialqattan) 16 | - Işık Kaplan (@isik-kaplan) 17 | - Sema Karataş (@semakaratas) 18 | - Sümeyye Boynukara (@sumeyyeboynukara) 19 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Development and Contributing 2 | 3 | ## Issue 4 | 5 | To make an improvement, add a new feature or anything else, please open a issue first. 6 | 7 | **Good first issues are the issues that you can quickly solve, we recommend you take a 8 | look.** 9 | [Good first issue](https://github.com/hakancelikdev/unimport/labels/good%20first%20issue) 10 | 11 | ## Fork Repository 12 | 13 | [fork the unimport.](https://github.com/hakancelikdev/unimport/fork) 14 | 15 | ## Clone Repository 16 | 17 | ```shell 18 | $ git clone git@github.com:/unimport.git 19 | $ cd unimport 20 | ``` 21 | 22 | ## Setup Branch 23 | 24 | ```shell 25 | git checkout -b i{your issue number} 26 | ``` 27 | 28 | ## How to Update My Local Repository 29 | 30 | ```shell 31 | $ git remote add upstream git@github.com:hakancelikdev/unimport.git 32 | $ git fetch upstream # or git fetch --all 33 | $ git rebase upstream/main 34 | ``` 35 | 36 | ## Testing 37 | 38 | First, make sure you have at least one of the python versions py3.8, py3.9, py3.10, 39 | py3.11 and py3.12 If not all versions are available, after opening PR, github action 40 | will run the tests for each version, so you can be sure that you wrote the correct code. 41 | You can skip the tox step below. 42 | 43 | After typing your codes, you should run the tests by typing the following command. 44 | 45 | ```shell 46 | $ python3.12 -m pip install tox 47 | $ tox 48 | ``` 49 | 50 | If all tests pass. 51 | 52 | ## The final step 53 | 54 | After adding a new feature or fixing a bug please report your change to 55 | [CHANGELOG.md](CHANGELOG.md) and write your name, GitHub address, and email in the 56 | [AUTHORS.md](AUTHORS.md) file in alphabetical order. 57 | 58 | ## License 59 | 60 | Unimport is MIT licensed, as found in the LICENSE file. 61 | -------------------------------------------------------------------------------- /docs/assets/logo/unimport.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakancelikdev/unimport/563fb2610888cb903ad4614eaee2e8fe0152535f/docs/assets/logo/unimport.ai -------------------------------------------------------------------------------- /docs/assets/logo/unimport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakancelikdev/unimport/563fb2610888cb903ad4614eaee2e8fe0152535f/docs/assets/logo/unimport.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ![unimport](assets/logo/unimport.png ":size=60%") 2 | 3 | **🚀 The ultimate linter and formatter for removing unused import statements in your 4 | code.** 5 | 6 | Looking for a way to eliminate those pesky unused import statements in your code? Look 7 | no further than Unimport! This powerful tool serves as both a linter and formatter, 8 | making it easy to detect and remove any imports that are no longer needed. Say goodbye 9 | to cluttered, inefficient code and hello to a cleaner, more streamlined development 10 | process with Unimport. 11 | 12 | [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/hakancelikdev/unimport/main.svg)](https://results.pre-commit.ci/latest/github/hakancelikdev/unimport/main) 13 | ![test](https://github.com/hakancelikdev/unimport/workflows/test/badge.svg) 14 | 15 | [![Pypi](https://img.shields.io/pypi/v/unimport)](https://pypi.org/project/unimport/) 16 | ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/unimport) 17 | [![Downloads](https://static.pepy.tech/personalized-badge/unimport?period=total&units=international_system&left_color=grey&right_color=red&left_text=downloads)](https://pepy.tech/project/unimport) 18 | [![License](https://img.shields.io/github/license/hakancelikdev/unimport.svg)](https://github.com/hakancelikdev/unimport/blob/main/LICENSE) 19 | 20 | [![Forks](https://img.shields.io/github/forks/hakancelikdev/unimport)](https://github.com/hakancelikdev/unimport/fork) 21 | [![Issues](https://img.shields.io/github/issues/hakancelikdev/unimport)](https://github.com/hakancelikdev/unimport/issues) 22 | [![Stars](https://img.shields.io/github/stars/hakancelikdev/unimport)](https://github.com/hakancelikdev/unimport/stargazers) 23 | 24 | [![Codecov](https://codecov.io/gh/hakancelikdev/unimport/branch/main/graph/badge.svg)](https://codecov.io/gh/hakancelikdev/unimport) 25 | [![Contributors](https://img.shields.io/github/contributors/hakancelikdev/unimport)](https://github.com/hakancelikdev/unimport/graphs/contributors) 26 | [![Last Commit](https://img.shields.io/github/last-commit/hakancelikdev/unimport.svg)](https://github.com/hakancelikdev/unimport/commits/main) 27 | 28 | --- 29 | 30 | - **Documentation** https://unimport.hakancelik.dev/ 31 | - **Issues** https://github.com/hakancelikdev/unimport/issues/ 32 | - **Changelog** https://unimport.hakancelik.dev/1.2.1/CHANGELOG/ 33 | - **Playground** https://playground-unimport.hakancelik.dev/ 34 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | Unimport requires Python 3.8+ and can be easily installed using most common Python 2 | packaging tools. We recommend installing the latest stable release from PyPI with pip: 3 | 4 | ```shell 5 | $ pip install unimport 6 | ``` 7 | -------------------------------------------------------------------------------- /docs/overrides/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} {% block announce %} 2 |
3 |
You can now sponsor Unimport 🍰
4 | 11 |
12 | {% endblock %} {% block outdated %} You're not viewing the latest version. 13 | 14 | Click here to go to latest. 15 | 16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /docs/tutorial/configurations.md: -------------------------------------------------------------------------------- 1 | It's possible to configure **unimport** from `pyproject.toml` or `setup.cfg` files if 2 | you have. 3 | 4 | Automatically pick up config options from setup.cfg if it is present in the project root 5 | else check and if it exists use pyproject.toml. 6 | 7 | If you want you can disable this feature by passing `--disable-auto-discovery-config` or 8 | you can pass the path to the configuration file by passing 9 | `--config path/to/pyproject.toml`. 10 | 11 | For example: 12 | 13 | **pyproject.toml** 14 | 15 | ```ini 16 | [tool.unimport] 17 | sources = ["path1", "path2"] 18 | exclude = '__init__.py|tests/' 19 | include = 'test|test2|tests.py' 20 | gitignore = true 21 | remove = false 22 | check = true 23 | diff = true 24 | include_star_import = true 25 | ignore_init = true 26 | ``` 27 | 28 | **setup.cfg** 29 | 30 | ```ini 31 | [unimport] 32 | sources = ["path1", "path2"] 33 | exclude = __init__.py|tests/ 34 | include = test|test2|tests.py 35 | gitignore = true 36 | remove = false 37 | check = true 38 | diff = true 39 | include_star_import = true 40 | ignore_init = true 41 | ``` 42 | 43 | ## Manage like CLI in configuration 44 | 45 | ```ini 46 | [tool.unimport] 47 | include-star-import = true 48 | ignore-init = true 49 | ``` 50 | 51 | **setup.cfg** 52 | 53 | ```ini 54 | [unimport] 55 | include-star-import = true 56 | ignore-init = true 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/tutorial/other-useful-features.md: -------------------------------------------------------------------------------- 1 | ## Skip Import 2 | 3 | Leave '# unimport: skip' or '# noqa' at the end of the line to skip imports **for 4 | example:** 5 | 6 | ```python 7 | import x # unimport:skip 8 | ``` 9 | 10 | ```python 11 | from x import ( # noqa 12 | t, y, 13 | f, r 14 | ) 15 | ``` 16 | 17 | Unimport support multiple skip like below. _It doesn't matter which line you put the 18 | comment on._ 19 | 20 | ```python 21 | from package import ( 22 | module, 23 | module1, 24 | ) # unimport:skip 25 | ``` 26 | 27 | or 28 | 29 | ```python 30 | from package import ( 31 | module, # unimport:skip 32 | module1, 33 | ) 34 | ``` 35 | 36 | --- 37 | 38 | ## File Wide Skips 39 | 40 | To skip a file by typing `# unimport: skip_file` anywhere in that file **for example:** 41 | 42 | ```python 43 | # unimport: skip_file 44 | 45 | import x 46 | 47 | ``` 48 | 49 | or 50 | 51 | ```python 52 | import x 53 | 54 | # unimport: skip_file 55 | 56 | ``` 57 | 58 | --- 59 | 60 | ## Exit code behavior 61 | 62 | Exit code 1 if there is a syntax error Exit code 0 if unused import versa and auto 63 | removed for all other cases exit code 1 Exit code 0 if there is no unused import. 64 | -------------------------------------------------------------------------------- /docs/tutorial/supported-behaviors.md: -------------------------------------------------------------------------------- 1 | ## Typing 2 | 3 | Unimport can understand that imports are used these cases. 4 | 5 | ```python 6 | from typing import List, Dict 7 | 8 | 9 | def test(arg: List[Dict]) -> None: 10 | pass 11 | ``` 12 | 13 | --- 14 | 15 | #### String 16 | 17 | Unimport supports the following cases 18 | 19 | ```python 20 | from typing import List, Dict 21 | 22 | 23 | def test(arg: 'List[Dict]') -> None: 24 | pass 25 | ``` 26 | 27 | ```python 28 | from typing import List, Dict 29 | 30 | 31 | def test(arg: "List['Dict']") -> None: 32 | pass 33 | ``` 34 | 35 | --- 36 | 37 | #### Comments 38 | 39 | Imports in the example below aren't flag as unused by unimport. 40 | 41 | ```python 42 | from typing import Any 43 | from typing import Tuple 44 | from typing import Union 45 | 46 | 47 | def function(a, b): 48 | # type: (Any, str) -> Union[Tuple[None, None], Tuple[str, str]] 49 | pass 50 | ``` 51 | 52 | For more information 53 | 54 | [PEP 526 - Syntax for Variable Annotations](https://www.python.org/dev/peps/pep-0526/) 55 | 56 | --- 57 | 58 | ## All 59 | 60 | Unimport looks at the items in the `__all__` list, if it matches the imports, marks it 61 | as being used. 62 | 63 | ```python 64 | import os 65 | 66 | __all__ = ["os"] # this import is used and umimport can understand 67 | ``` 68 | 69 | Other supported operations, **append** and **extend** 70 | 71 | ```python 72 | from os import * 73 | 74 | 75 | __all__ = [] 76 | __all__.append("removedirs") 77 | __all__.extend(["walk"]) 78 | ``` 79 | 80 | after refactoring 81 | 82 | ```python 83 | from os import removedirs, walk 84 | 85 | 86 | __all__ = [] 87 | __all__.append("removedirs") 88 | __all__.extend(["walk"]) 89 | ``` 90 | 91 | --- 92 | 93 | ## Scope 94 | 95 | Unimport tries to better understand whether the import is unused by performing scope 96 | analysis. 97 | 98 | Let me give a few examples. 99 | 100 | **input** 101 | 102 | ```python 103 | import x 104 | 105 | 106 | def func(): 107 | import x 108 | 109 | def inner(): 110 | import x 111 | x 112 | 113 | ``` 114 | 115 | **output** 116 | 117 | ```python 118 | def func(): 119 | 120 | def inner(): 121 | import x 122 | x 123 | ``` 124 | 125 | **input** 126 | 127 | ```python 128 | import x 129 | 130 | 131 | class Klass: 132 | 133 | def f(self): 134 | import x 135 | 136 | def ff(): 137 | import x 138 | 139 | x 140 | ``` 141 | 142 | **output** 143 | 144 | ```python 145 | 146 | class Klass: 147 | 148 | def f(self): 149 | 150 | def ff(): 151 | import x 152 | 153 | x 154 | ``` 155 | -------------------------------------------------------------------------------- /docs/tutorial/use-with-docker.md: -------------------------------------------------------------------------------- 1 | Install from the command line: 2 | 3 | To use the stable 4 | 5 | ``` 6 | $ docker pull ghcr.io/hakancelikdev/unimport:stable 7 | ``` 8 | 9 | To use the other versions 10 | 11 | ``` 12 | $ docker pull ghcr.io/hakancelikdev/unimport:{version_number} 13 | ``` 14 | 15 | Usage: 16 | 17 | ```shell 18 | $ docker run -v {your_project_path}:/opt/workspace -it ghcr.io/hakancelikdev/unimport:latest /opt/workspace/ {other_arguments} 19 | ``` 20 | 21 | For more information see: 22 | https://github.com/hakancelikdev/unimport/pkgs/container/unimport 23 | -------------------------------------------------------------------------------- /docs/tutorial/use-with-github-action.md: -------------------------------------------------------------------------------- 1 | You can use stable version 2 | 3 | ```yaml 4 | name: Unimport 5 | on: [push, pull_request] 6 | jobs: 7 | lint: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3.5.3 11 | - uses: actions/setup-python@v4.6.1 12 | - name: Check unused imports 13 | uses: hakancelikdev/unimport@stable 14 | with: 15 | extra_args: --include src/ 16 | ``` 17 | 18 | or you can use a specific version if you want. 19 | 20 | ```yaml 21 | name: Unimport 22 | on: [push, pull_request] 23 | jobs: 24 | lint: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v3.5.3 28 | - uses: actions/setup-python@v4.6.1 29 | - name: Check unused imports 30 | uses: hakancelikdev/unimport@1.2.1 31 | with: 32 | extra_args: --include src/ 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/tutorial/use-with-pre-commit.md: -------------------------------------------------------------------------------- 1 | Once you have [pre-commit](https://pre-commit.com/) 2 | [installed](https://pre-commit.com/#install), adding pre-commit plugins to your project 3 | is done with the .pre-commit-config.yaml configuration file. 4 | 5 | Add a file called .pre-commit-config.yaml to the root of your project. The pre-commit 6 | config file describes what repositories and hooks are installed. 7 | 8 | ```yaml 9 | repos: 10 | - repo: https://github.com/hakancelikdev/unimport 11 | rev: stable 12 | hooks: 13 | - id: unimport 14 | args: 15 | - --include-star-import 16 | - --ignore-init 17 | - --gitignore 18 | ``` 19 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Unimport 2 | site_description: 3 | 🚀 The ultimate linter and formatter for removing unused import statements in your 4 | code. 5 | site_url: https://unimport.hakancelik.dev 6 | repo_url: https://github.com/hakancelikdev/unimport 7 | repo_name: hakancelikdev/unimport 8 | edit_uri: https://github.com/hakancelikdev/unimport/tree/main/docs 9 | copyright: Copyright © 2019 - 2024 Hakan Çelik 10 | 11 | markdown_extensions: 12 | - fenced_code 13 | - footnotes 14 | - tables 15 | - codehilite 16 | - legacy_em 17 | - meta 18 | - sane_lists 19 | - smarty 20 | - toc: 21 | permalink: true 22 | baselevel: 3 23 | - pymdownx.arithmatex: 24 | generic: true 25 | - pymdownx.betterem: 26 | smart_enable: all 27 | - pymdownx.caret 28 | - pymdownx.critic 29 | - pymdownx.details 30 | - pymdownx.emoji: 31 | emoji_index: !!python/name:materialx.emoji.twemoji 32 | emoji_generator: !!python/name:materialx.emoji.to_svg 33 | - pymdownx.highlight 34 | - pymdownx.inlinehilite 35 | - pymdownx.keys 36 | - pymdownx.magiclink: 37 | repo_url_shorthand: true 38 | user: hakancelikdev 39 | repo: unimport 40 | - pymdownx.mark 41 | - pymdownx.smartsymbols 42 | - pymdownx.superfences: 43 | custom_fences: 44 | - name: mermaid 45 | class: mermaid-experimental 46 | format: !!python/name:pymdownx.superfences.fence_code_format 47 | - pymdownx.tabbed 48 | - pymdownx.tasklist: 49 | custom_checkbox: true 50 | - pymdownx.tilde 51 | 52 | plugins: 53 | - search: 54 | separator: '[\s\-\.]+' 55 | - git-revision-date-localized: 56 | type: date 57 | enable_creation_date: true 58 | - minify: 59 | minify_html: true 60 | - mike: 61 | canonical_version: latest 62 | 63 | extra: 64 | version: 65 | provider: mike 66 | default: latest 67 | analytics: 68 | provider: google 69 | property: G-2EGM5WWEED 70 | consent: 71 | title: Cookie consent 72 | description: >- 73 | We use cookies to recognize your repeated visits and preferences, as well as to 74 | measure the effectiveness of our documentation and whether users find what they're 75 | searching for. With your consent, you're helping us to make our documentation 76 | better. 77 | social: 78 | - icon: fontawesome/brands/twitter 79 | link: https://twitter.com/hakancelikdev 80 | - icon: fontawesome/brands/linkedin 81 | link: https://www.linkedin.com/in/hakancelikdev 82 | - icon: fontawesome/solid/globe 83 | link: https://hakancelik.dev 84 | homepage: https://unimport.hakancelik.dev 85 | 86 | theme: 87 | name: material 88 | custom_dir: docs/overrides 89 | language: en 90 | features: 91 | - navigation.instant 92 | - navigation.tracking 93 | - navigation.indexes 94 | - search.suggest 95 | - search.highlight 96 | - search.share 97 | - header.autohide 98 | - navigation.top 99 | palette: 100 | - media: "(prefers-color-scheme: light)" 101 | scheme: default 102 | primary: white 103 | accent: amber 104 | toggle: 105 | icon: material/weather-sunny 106 | name: Switch to dark mode 107 | - media: "(prefers-color-scheme: dark)" 108 | scheme: slate 109 | primary: black 110 | accent: amber 111 | toggle: 112 | icon: material/weather-night 113 | name: Switch to light mode 114 | font: 115 | text: Noto Sans 116 | code: Roboto Mono 117 | icon: 118 | repo: fontawesome/brands/github-alt 119 | edit: material/file-document-edit-outline 120 | 121 | nav: 122 | - Overview: index.md 123 | - Installation: installation.md 124 | - User Guide: 125 | - tutorial/command-line-options.md 126 | - tutorial/supported-behaviors.md 127 | - tutorial/other-useful-features.md 128 | - tutorial/configurations.md 129 | - tutorial/use-with-pre-commit.md 130 | - tutorial/use-with-docker.md 131 | - tutorial/use-with-github-action.md 132 | - Contributing: CONTRIBUTING.md 133 | - Changelog: CHANGELOG.md 134 | - Authors: AUTHORS.md 135 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # Build system 2 | 3 | [build-system] 4 | requires = [ 5 | "setuptools>=45", 6 | "wheel" 7 | ] 8 | build-backend = "setuptools.build_meta" 9 | 10 | # Tools 11 | 12 | [tool.pytest.ini_options] 13 | addopts = "--strict-markers" 14 | xfail_strict = true 15 | testpaths = ["tests"] 16 | 17 | [tool.isort] 18 | profile = "black" 19 | line_length = 120 20 | skip_gitignore = true 21 | 22 | [tool.black] 23 | line-length = 120 24 | target-version = ['py38', 'py39', 'py310', 'py311', 'py312'] 25 | 26 | [tool.unimport] 27 | include_star_import = true 28 | ignore_init = true 29 | gitignore = true 30 | 31 | [tool.docformatter] 32 | recursive = true 33 | wrap-summaries = 79 34 | wrap-descriptions = 79 35 | blank = true 36 | 37 | [tool.mypy] 38 | warn_unused_configs = true 39 | no_strict_optional = true 40 | ignore_missing_imports = true 41 | show_error_codes = true 42 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = unimport 3 | version = attr: unimport.__version__ 4 | description = The ultimate linter and formatter for removing unused import statements in your code. 5 | long_description = file: README.md 6 | url = https://unimport.hakancelik.dev/ 7 | author = Hakan Çelik 8 | author_email = hakancelikdev@gmail.com 9 | keywords = 10 | unused 11 | import 12 | long_description_content_type = text/markdown 13 | license = MIT 14 | classifiers = 15 | Development Status :: 5 - Production/Stable 16 | License :: OSI Approved :: MIT License 17 | Operating System :: OS Independent 18 | Programming Language :: Python 19 | Programming Language :: Python :: 3 20 | Programming Language :: Python :: 3 :: Only 21 | Programming Language :: Python :: 3.8 22 | Programming Language :: Python :: 3.9 23 | Programming Language :: Python :: 3.10 24 | Programming Language :: Python :: 3.11 25 | Programming Language :: Python :: 3.12 26 | Programming Language :: Python :: Implementation :: CPython 27 | Environment :: Console 28 | Topic :: Software Development :: Libraries :: Python Modules 29 | project_urls = 30 | Documentation = https://unimport.hakancelik.dev/ 31 | Issues = https://github.com/hakancelikdev/unimport/issues/ 32 | Changelog = https://unimport.hakancelik.dev/1.2.1/CHANGELOG/ 33 | 34 | [options] 35 | python_requires = >=3.8, <3.13 36 | include_package_data = true 37 | zip_safe = true 38 | packages = 39 | unimport 40 | unimport.commands 41 | unimport.analyzers 42 | package_dir = 43 | =src 44 | install_requires = 45 | libcst>=0.4.10, <=1.1.0; python_version >= '3.11' 46 | libcst>=0.3.7, <=1.1.0; python_version == '3.10' 47 | libcst>=0.3.7, <=1.1.0; python_version == '3.9' 48 | libcst>=0.3.0, <=1.1.0; python_version == '3.8' 49 | pathspec>=0.10.1, <1 50 | toml>=0.9.0, <1 51 | 52 | [options.entry_points] 53 | console_scripts = 54 | unimport = unimport.__main__:main 55 | 56 | [options.extras_require] 57 | docs = 58 | mkdocs==1.5.3 59 | mkdocs-material==9.4.9 60 | mkdocs-markdownextradata-plugin==0.2.5 61 | mkdocs-minify-plugin==0.7.1 62 | mkdocs-git-revision-date-localized-plugin==1.2.1 63 | mike==2.0.0 64 | test = 65 | pytest==7.4.3 66 | pytest-cov==4.1.0 67 | semantic-version==2.10.0 68 | 69 | [options.package_data] 70 | * = 71 | py.typed 72 | -------------------------------------------------------------------------------- /src/unimport/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.2.1" 2 | __description__ = "A linter, formatter for finding and removing unused import statements." 3 | -------------------------------------------------------------------------------- /src/unimport/__main__.py: -------------------------------------------------------------------------------- 1 | def main(): 2 | from unimport.color import paint 3 | from unimport.enums import Color, Emoji 4 | from unimport.main import Main 5 | 6 | main = Main.run() 7 | if not main.is_unused_imports and main.config.check: 8 | print( 9 | paint( 10 | f"{Emoji.STAR} Congratulations there is no unused import in your project. {Emoji.STAR}", 11 | Color.GREEN, 12 | main.config.use_color, 13 | ) 14 | ) 15 | 16 | raise SystemExit(main.exit_code()) 17 | 18 | 19 | if __name__ == "__main__": 20 | main() 21 | -------------------------------------------------------------------------------- /src/unimport/analyzers/__init__.py: -------------------------------------------------------------------------------- 1 | from unimport.analyzers.main import MainAnalyzer 2 | 3 | __all__ = ("MainAnalyzer",) 4 | -------------------------------------------------------------------------------- /src/unimport/analyzers/decarators.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import re 3 | from typing import cast 4 | 5 | from unimport import typing as T 6 | 7 | __all__ = ("generic_visit", "skip_import") 8 | 9 | 10 | def generic_visit(func: T.FunctionT) -> T.FunctionT: 11 | @functools.wraps(func) 12 | def wrapper(self, node, *args, **kwargs): 13 | func(self, node, *args, **kwargs) 14 | self.generic_visit(node) 15 | 16 | return cast(T.FunctionT, wrapper) 17 | 18 | 19 | def skip_import(func: T.FunctionT) -> T.FunctionT: 20 | SKIP_IMPORT_COMMENTS_REGEX = "#.*(unimport: {0,1}skip|noqa)" 21 | 22 | @functools.wraps(func) 23 | def wrapper(self, node, *args, **kwargs): 24 | source_segment = "\n".join(self.source.splitlines()[node.lineno - 1 : node.end_lineno]) 25 | skip_comment = bool(re.search(SKIP_IMPORT_COMMENTS_REGEX, source_segment, re.IGNORECASE)) 26 | if not any((skip_comment, self.any_import_error)): 27 | func(self, node, *args, **kwargs) 28 | 29 | return cast(T.FunctionT, wrapper) 30 | -------------------------------------------------------------------------------- /src/unimport/analyzers/importable.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import ast 4 | 5 | import unimport.constants as C 6 | import unimport.typing as T 7 | from unimport.analyzers.decarators import generic_visit 8 | from unimport.analyzers.utils import first_parent_match 9 | 10 | __all__ = ( 11 | "ImportableNameAnalyzer", 12 | "SuggestionNameAnalyzer", 13 | ) 14 | 15 | from unimport.statement import Name, Scope 16 | 17 | 18 | class ImportableNameAnalyzer(ast.NodeVisitor): 19 | __slots__ = ("importable_nodes",) 20 | 21 | def __init__(self) -> None: 22 | self.importable_nodes: list[ast.Constant] = [] # nodes on the __all__ list 23 | 24 | def traverse(self, tree): 25 | self.visit(tree) 26 | 27 | @generic_visit 28 | def visit_Assign(self, node: ast.Assign) -> None: 29 | if getattr(node.targets[0], "id", None) == "__all__" and isinstance(node.value, (ast.List, ast.Tuple, ast.Set)): 30 | for item in node.value.elts: 31 | if isinstance(item, ast.Constant) and isinstance(item.value, str): 32 | self.importable_nodes.append(item) 33 | 34 | @generic_visit 35 | def visit_Expr(self, node: ast.Expr) -> None: 36 | if ( 37 | isinstance(node.value, ast.Call) 38 | and isinstance(node.value.func, ast.Attribute) 39 | and isinstance(node.value.func.value, ast.Name) 40 | and node.value.func.value.id == "__all__" 41 | ): 42 | if node.value.func.attr == "append": 43 | for arg in node.value.args: 44 | if isinstance(arg, ast.Constant) and isinstance(arg.value, str): 45 | self.importable_nodes.append(arg) 46 | 47 | elif node.value.func.attr == "extend": 48 | for arg in node.value.args: 49 | if isinstance(arg, ast.List): 50 | for item in arg.elts: 51 | if isinstance(item, ast.Constant) and isinstance(item.value, str): 52 | self.importable_nodes.append(item) 53 | 54 | 55 | class SuggestionNameAnalyzer(ast.NodeVisitor): 56 | __slots__ = ("suggestions_nodes",) 57 | 58 | def __init__(self) -> None: 59 | self.suggestions_nodes: list[T.ASTImportableT] = [] # nodes on the CFN 60 | 61 | def traverse(self, tree): 62 | self.visit(tree) 63 | 64 | @generic_visit 65 | def visit_def(self, node: T.CFNT) -> None: 66 | if not first_parent_match(node, C.DEF_TUPLE): 67 | self.suggestions_nodes.append(node) 68 | 69 | visit_ClassDef = visit_FunctionDef = visit_AsyncFunctionDef = visit_def 70 | 71 | @generic_visit 72 | def visit_Import(self, node: ast.Import) -> None: 73 | for alias in node.names: 74 | self.suggestions_nodes.append(alias) 75 | 76 | @generic_visit 77 | def visit_ImportFrom(self, node: ast.ImportFrom) -> None: 78 | if not node.names[0].name == "*": 79 | for alias in node.names: 80 | self.suggestions_nodes.append(alias) 81 | 82 | @generic_visit 83 | def visit_Assign(self, node: ast.Assign) -> None: 84 | for target in node.targets: # we only get assigned names 85 | if isinstance(target, (ast.Name, ast.Attribute)): 86 | self.suggestions_nodes.append(target) 87 | 88 | 89 | class ImportableNameWithScopeAnalyzer(ImportableNameAnalyzer): 90 | def traverse(self, tree): 91 | super().traverse(tree) 92 | 93 | for node in self.importable_nodes: 94 | Name.register(lineno=node.lineno, name=node.value, node=node, is_all=True) 95 | 96 | def visit_def(self, node: T.CFNT) -> None: 97 | Scope.add_current_scope(node) 98 | 99 | self.generic_visit(node) 100 | 101 | Scope.remove_current_scope() 102 | 103 | visit_ClassDef = visit_FunctionDef = visit_AsyncFunctionDef = visit_def 104 | -------------------------------------------------------------------------------- /src/unimport/analyzers/main.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import re 3 | from pathlib import Path 4 | 5 | from unimport.analyzers.import_statement import ImportAnalyzer 6 | from unimport.analyzers.importable import ImportableNameWithScopeAnalyzer 7 | from unimport.analyzers.name import NameAnalyzer 8 | from unimport.analyzers.utils import get_defined_names, set_tree_parents 9 | from unimport.statement import Import, ImportFrom, Name, Scope 10 | 11 | __all__ = ("MainAnalyzer",) 12 | 13 | 14 | class MainAnalyzer(ast.NodeVisitor): 15 | __slots__ = ("source", "path", "include_star_import") 16 | 17 | def __init__(self, *, source: str, path: Path = Path(""), include_star_import: bool = False): 18 | self.source = source 19 | self.path = path 20 | self.include_star_import = include_star_import 21 | 22 | def __enter__(self): 23 | self.traverse() 24 | return self 25 | 26 | def __exit__(self, exc_type, exc_value, exc_tb): 27 | self.clear() 28 | 29 | def traverse(self) -> None: 30 | if self.skip_file(): 31 | return None 32 | 33 | tree = ast.parse(self.source, type_comments=True) 34 | 35 | set_tree_parents(tree) # set parents to tree 36 | 37 | Scope.add_global_scope(tree) # add global scope of the top tree 38 | 39 | NameAnalyzer().traverse(tree) # name analyzers 40 | 41 | ImportableNameWithScopeAnalyzer().traverse(tree) # importable analyzers for collect in __all__ 42 | 43 | ImportAnalyzer( # import analyzers 44 | source=self.source, include_star_import=self.include_star_import, defined_names=get_defined_names(tree) 45 | ).traverse(tree) 46 | 47 | Scope.remove_current_scope() # remove global scope 48 | 49 | def skip_file(self) -> bool: 50 | SKIP_FILE_REGEX = "#.*(unimport: {0,1}skip_file)" 51 | 52 | return bool(re.search(SKIP_FILE_REGEX, self.source, re.IGNORECASE)) 53 | 54 | @staticmethod 55 | def clear(): 56 | Name.clear() 57 | Import.clear() 58 | ImportFrom.clear() 59 | Scope.clear() 60 | -------------------------------------------------------------------------------- /src/unimport/analyzers/name.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import contextlib 3 | 4 | from unimport import constants as C 5 | from unimport import typing as T 6 | from unimport.analyzers.decarators import generic_visit 7 | from unimport.analyzers.utils import first_parent_match, set_tree_parents 8 | from unimport.statement import Name, Scope 9 | 10 | __all__ = ("NameAnalyzer",) 11 | 12 | 13 | class NameAnalyzer(ast.NodeVisitor): 14 | def visit_ClassDef(self, node) -> None: 15 | Scope.add_current_scope(node) 16 | 17 | self.generic_visit(node) 18 | 19 | Scope.remove_current_scope() 20 | 21 | def visit_FunctionDef(self, node: T.ASTFunctionT) -> None: 22 | Scope.add_current_scope(node) 23 | 24 | if node.type_comment is not None: 25 | self.join_visit(node.type_comment, node, mode="func_type") 26 | 27 | self.generic_visit(node) 28 | 29 | Scope.remove_current_scope() 30 | 31 | visit_AsyncFunctionDef = visit_FunctionDef 32 | 33 | @generic_visit 34 | def visit_Constant(self, node: ast.Constant) -> None: 35 | if isinstance(node.value, str): 36 | if (first_annassign_or_arg := first_parent_match(node, (ast.AnnAssign, ast.arg))) and isinstance( 37 | first_annassign_or_arg.annotation, ast.Constant 38 | ): 39 | self.join_visit(node.value, node) 40 | elif ( 41 | first_func_parent := first_parent_match(node, *C.AST_FUNCTION_TUPLE) 42 | ) and first_func_parent.returns is node: 43 | self.join_visit(node.value, node) 44 | 45 | @generic_visit 46 | def visit_Name(self, node: ast.Name) -> None: 47 | if not isinstance(node.parent, ast.Attribute): # type: ignore 48 | Name.register(lineno=node.lineno, name=node.id, node=node) 49 | 50 | @generic_visit 51 | def visit_Attribute(self, node: ast.Attribute) -> None: 52 | if not isinstance(node.value, ast.Call): 53 | names = [] 54 | for sub_node in ast.walk(node): 55 | if isinstance(sub_node, ast.Attribute): 56 | names.append(sub_node.attr) 57 | elif isinstance(sub_node, ast.Name): 58 | names.append(sub_node.id) 59 | names.reverse() 60 | Name.register(lineno=node.lineno, name=".".join(names), node=node) 61 | 62 | @generic_visit 63 | def visit_Assign(self, node: ast.Assign) -> None: 64 | if node.type_comment is not None: 65 | self.join_visit(node.type_comment, node) 66 | 67 | @generic_visit 68 | def visit_arg(self, node: ast.arg) -> None: 69 | if node.type_comment is not None: 70 | self.join_visit(node.type_comment, node) 71 | 72 | @generic_visit 73 | def visit_Subscript(self, node: ast.Subscript) -> None: 74 | if ( 75 | isinstance(node.value, ast.Attribute) 76 | and isinstance(node.value.value, ast.Name) 77 | and node.value.value.id == "typing" 78 | ) or (isinstance(node.value, ast.Name) and node.value.id in C.SUBSCRIPT_TYPE_VARIABLE): 79 | if C.PY39_PLUS: 80 | _slice = node.slice 81 | else: 82 | _slice = node.slice.value # type: ignore 83 | 84 | if isinstance(_slice, ast.Tuple): # type: ignore 85 | for elt in _slice.elts: # type: ignore 86 | if isinstance(elt, ast.Constant) and isinstance(elt.value, str): 87 | self.join_visit(elt.value, elt) 88 | else: 89 | if isinstance(_slice, ast.Constant) and isinstance(_slice.value, str): # type: ignore 90 | self.join_visit(_slice.value, _slice) 91 | 92 | @generic_visit 93 | def visit_Call(self, node: ast.Call) -> None: 94 | if ( 95 | ( 96 | isinstance(node.func, ast.Attribute) 97 | and isinstance(node.func.value, ast.Name) 98 | and node.func.value.id == "typing" 99 | and node.func.attr == "cast" 100 | ) 101 | or isinstance(node.func, ast.Name) 102 | and node.func.id == "cast" 103 | ): 104 | if isinstance(node.args[0], ast.Constant) and isinstance(node.args[0].value, str): 105 | self.join_visit(node.args[0].value, node.args[0]) 106 | 107 | def join_visit(self, value: str, node: ast.AST, *, mode: str = "eval") -> None: 108 | """A function that parses the value, copies locations from the node and 109 | includes them in self.visit.""" 110 | with contextlib.suppress(SyntaxError): 111 | tree = ast.parse(value, mode=mode, type_comments=True) 112 | set_tree_parents(tree, parent=node.parent) # type: ignore 113 | for new_node in ast.walk(tree): 114 | ast.copy_location(new_node, node) 115 | self.visit(tree) 116 | 117 | def traverse(self, tree) -> None: 118 | self.visit(tree) 119 | -------------------------------------------------------------------------------- /src/unimport/analyzers/utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import ast 4 | import typing 5 | 6 | __all__ = ("set_tree_parents", "get_parents", "first_parent_match", "get_defined_names") 7 | 8 | 9 | def set_tree_parents(tree: ast.AST, parent: ast.AST | None = None) -> None: 10 | tree.parent = parent # type: ignore 11 | for node in ast.walk(tree): 12 | for child in ast.iter_child_nodes(node): 13 | child.parent = node # type: ignore 14 | 15 | 16 | def get_parents(node: ast.AST) -> typing.Iterator[ast.AST]: 17 | parent = node 18 | while parent: 19 | parent = parent.parent # type: ignore 20 | if parent: 21 | yield parent 22 | 23 | 24 | def first_parent_match(node: ast.AST, *ancestors): 25 | return next(filter(lambda parent: isinstance(parent, ancestors), get_parents(node)), None) 26 | 27 | 28 | def get_defined_names(tree: ast.AST) -> set[str]: 29 | defined_names: set[str] = set() 30 | for node in ast.walk(tree): 31 | if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): 32 | defined_names.add(node.name) 33 | elif isinstance(node, ast.Name) and isinstance(node.ctx, ast.Store): 34 | defined_names.add(node.id) 35 | 36 | return defined_names 37 | -------------------------------------------------------------------------------- /src/unimport/color.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import Tuple 3 | 4 | from unimport.enums import Color 5 | 6 | __all__ = ( 7 | "TERMINAL_SUPPORT_COLOR", 8 | "difference", 9 | "paint", 10 | "Color", 11 | ) 12 | 13 | if sys.platform == "win32": # pragma: no cover (windows) 14 | 15 | def _enable() -> None: 16 | from ctypes import POINTER, WINFUNCTYPE, WinError, windll 17 | from ctypes.wintypes import BOOL, DWORD, HANDLE 18 | 19 | STD_ERROR_HANDLE = -12 20 | ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 21 | 22 | def bool_errcheck(result, func, args): 23 | if not result: 24 | raise WinError() 25 | return args 26 | 27 | GetStdHandle = WINFUNCTYPE(HANDLE, DWORD)( 28 | ("GetStdHandle", windll.kernel32), 29 | ((1, "nStdHandle"),), 30 | ) 31 | 32 | GetConsoleMode = WINFUNCTYPE(BOOL, HANDLE, POINTER(DWORD))( 33 | ("GetConsoleMode", windll.kernel32), 34 | ((1, "hConsoleHandle"), (2, "lpMode")), 35 | ) 36 | GetConsoleMode.errcheck = ( # type: ignore[assignment, misc] 37 | bool_errcheck # type: ignore[assignment] 38 | ) 39 | 40 | SetConsoleMode = WINFUNCTYPE(BOOL, HANDLE, DWORD)( 41 | ("SetConsoleMode", windll.kernel32), 42 | ((1, "hConsoleHandle"), (1, "dwMode")), 43 | ) 44 | SetConsoleMode.errcheck = ( # type: ignore[assignment, misc] 45 | bool_errcheck # type: ignore[assignment] 46 | ) 47 | 48 | # As of Windows 10, the Windows console supports (some) ANSI escape 49 | # sequences, but it needs to be enabled using `SetConsoleMode` first. 50 | # 51 | # More info on the escape sequences supported: 52 | # https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032(v=vs.85).aspx 53 | stderr = GetStdHandle(STD_ERROR_HANDLE) 54 | flags = GetConsoleMode(stderr) 55 | SetConsoleMode(stderr, flags | ENABLE_VIRTUAL_TERMINAL_PROCESSING) 56 | 57 | try: 58 | _enable() 59 | except OSError: 60 | TERMINAL_SUPPORT_COLOR = False 61 | else: 62 | TERMINAL_SUPPORT_COLOR = True 63 | else: # pragma: win32 no cover 64 | TERMINAL_SUPPORT_COLOR = True 65 | 66 | 67 | def paint(text: str, color: Color, use_color: bool = True) -> str: 68 | return color.value + text + Color.RESET.value if use_color else text 69 | 70 | 71 | def difference(text: Tuple[str, ...], use_color: bool = True) -> str: # pragma: no cover 72 | lines = list(text) 73 | for i, line in enumerate(lines): 74 | if line.startswith("+++") or line.startswith("---"): 75 | lines[i] = paint(line, Color.BOLD_WHITE, use_color) 76 | elif line.startswith("@@"): 77 | lines[i] = paint(line, Color.CYAN, use_color) 78 | elif line.startswith("+"): 79 | lines[i] = paint(line, Color.GREEN, use_color) 80 | elif line.startswith("-"): 81 | lines[i] = paint(line, Color.RED, use_color) 82 | return "\n".join(lines) 83 | -------------------------------------------------------------------------------- /src/unimport/commands/__init__.py: -------------------------------------------------------------------------------- 1 | from unimport.commands import options 2 | from unimport.commands.check import check 3 | from unimport.commands.diff import diff 4 | from unimport.commands.parser import generate_parser 5 | from unimport.commands.permission import permission 6 | from unimport.commands.remove import remove 7 | 8 | __all__ = ( 9 | "check", 10 | "diff", 11 | "permission", 12 | "remove", 13 | "options", 14 | "generate_parser", 15 | ) 16 | -------------------------------------------------------------------------------- /src/unimport/commands/check.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pathlib import Path 4 | 5 | from unimport.color import paint 6 | from unimport.enums import Color 7 | from unimport.statement import Import, ImportFrom 8 | 9 | __all__ = ("check",) 10 | 11 | 12 | def check(path: Path, unused_imports: list[Import | ImportFrom], use_color: bool) -> None: 13 | for imp in unused_imports: 14 | if isinstance(imp, ImportFrom) and imp.star and imp.suggestions: 15 | context = ( 16 | paint(f"from {imp.name} import *", Color.RED, use_color) 17 | + " -> " 18 | + paint( 19 | f"from {imp.name} import {', '.join(imp.suggestions)}", 20 | Color.GREEN, 21 | use_color, 22 | ) 23 | ) 24 | else: 25 | context = paint(imp.name, Color.YELLOW, use_color) 26 | print( 27 | context 28 | + " at " 29 | + paint(path.as_posix(), Color.GREEN, use_color) 30 | + ":" 31 | + paint(str(imp.lineno), Color.GREEN, use_color) 32 | ) 33 | -------------------------------------------------------------------------------- /src/unimport/commands/diff.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from unimport import utils 4 | from unimport.color import difference 5 | 6 | __all__ = ("diff",) 7 | 8 | 9 | def diff(path: Path, source: str, refactor_result: str, use_color: bool = True) -> bool: 10 | diff_ = utils.diff(source=source, refactor_result=refactor_result, fromfile=path) 11 | exists_diff = bool(diff_) 12 | if exists_diff: 13 | print(difference(diff_, use_color)) 14 | 15 | return exists_diff 16 | -------------------------------------------------------------------------------- /src/unimport/commands/parser.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | from unimport import __description__ 4 | from unimport.commands import options 5 | from unimport.enums import Emoji 6 | 7 | __all__ = ("generate_parser",) 8 | 9 | 10 | def generate_parser() -> argparse.ArgumentParser: 11 | parser = argparse.ArgumentParser( 12 | prog="unimport", 13 | description=__description__, 14 | epilog=f"Get rid of all unused imports {Emoji.PARTYING_FACE}", 15 | ) 16 | exclusive_group = parser.add_mutually_exclusive_group(required=False) 17 | 18 | options.add_color_option(parser) 19 | options.add_sources_option(parser) 20 | options.add_check_option(parser) 21 | options.add_config_option(parser) 22 | options.add_disable_auto_discovery_config_option(parser) 23 | options.add_include_option(parser) 24 | options.add_exclude_option(parser) 25 | options.add_gitignore_option(parser) 26 | options.add_ignore_init_option(parser) 27 | options.add_include_star_import_option(parser) 28 | options.add_diff_option(parser) 29 | options.add_remove_option(exclusive_group) 30 | options.add_permission_option(exclusive_group) 31 | options.add_version_option(parser) 32 | 33 | return parser 34 | -------------------------------------------------------------------------------- /src/unimport/commands/permission.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from unimport import utils 4 | from unimport.color import paint 5 | from unimport.enums import Color 6 | 7 | __all__ = ("permission",) 8 | 9 | 10 | def permission(path: Path, use_color: bool) -> bool: 11 | action = input(f"Apply suggested changes to '{paint(str(path), Color.YELLOW, use_color)}' [Y/n/q] ? >").lower() 12 | if action == "q": 13 | raise SystemExit(1) 14 | return utils.action_to_bool(action) 15 | -------------------------------------------------------------------------------- /src/unimport/commands/remove.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from unimport.color import paint 4 | from unimport.enums import Color 5 | 6 | __all__ = ("remove",) 7 | 8 | 9 | def remove(path: Path, encoding, newline, refactor_result: str, use_color) -> None: 10 | with open(path, mode="w", encoding=encoding, newline=newline) as py_file: 11 | py_file.write(refactor_result) 12 | 13 | print(f"Refactoring '{paint(str(path), Color.GREEN, use_color)}'") 14 | -------------------------------------------------------------------------------- /src/unimport/constants.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import sys 3 | import sysconfig 4 | 5 | __all__ = ( 6 | "BUILTIN_MODULE_NAMES", 7 | "EXCLUDE_REGEX_PATTERN", 8 | "GLOB_PATTERN", 9 | "INCLUDE_REGEX_PATTERN", 10 | "INIT_FILE_IGNORE_REGEX", 11 | "PY39_PLUS", 12 | "PY310_PLUS", 13 | "PY312_PLUS", 14 | "STDLIB_PATH", 15 | "SUBSCRIPT_TYPE_VARIABLE", 16 | ) 17 | 18 | # REGEX 19 | GLOB_PATTERN = r"**/*.py" 20 | INCLUDE_REGEX_PATTERN = r"\.(py)$" 21 | EXCLUDE_REGEX_PATTERN = r"^$" 22 | INIT_FILE_IGNORE_REGEX = r"__init__\.py" 23 | 24 | # TUPLE 25 | DEF_TUPLE = (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef) 26 | AST_FUNCTION_TUPLE = (ast.FunctionDef, ast.AsyncFunctionDef) 27 | 28 | # CONF 29 | PY39_PLUS = sys.version_info >= (3, 9) 30 | PY310_PLUS = sys.version_info >= (3, 10) 31 | PY312_PLUS = sys.version_info >= (3, 12) 32 | 33 | SUBSCRIPT_TYPE_VARIABLE = frozenset( 34 | { 35 | "AbstractSet", 36 | "AsyncContextManager", 37 | "AsyncGenerator", 38 | "AsyncIterable", 39 | "AsyncIterator", 40 | "Awaitable", 41 | "Callable", 42 | "ChainMap", 43 | "ClassVar", 44 | "Collection", 45 | "Container", 46 | "ContextManager", 47 | "Coroutine", 48 | "Counter", 49 | "DefaultDict", 50 | "Deque", 51 | "Dict", 52 | "FrozenSet", 53 | "Generator", 54 | "IO", 55 | "ItemsView", 56 | "Iterable", 57 | "Iterator", 58 | "KeysView", 59 | "List", 60 | "Mapping", 61 | "MappingView", 62 | "Match", 63 | "MutableMapping", 64 | "MutableSequence", 65 | "MutableSet", 66 | "Optional", 67 | "Pattern", 68 | "Reversible", 69 | "Sequence", 70 | "Set", 71 | "SupportsRound", 72 | "Tuple", 73 | "Type", 74 | "Union", 75 | "ValuesView", 76 | # Python >= 3.7. 77 | "Literal", 78 | # Python >= 3.8. 79 | "OrderedDict", 80 | } 81 | ) 82 | 83 | BUILTIN_MODULE_NAMES = sys.builtin_module_names 84 | STDLIB_PATH = sysconfig.get_paths()["stdlib"] 85 | -------------------------------------------------------------------------------- /src/unimport/enums.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | __all__ = ("Emoji", "Color", "ColorSelect") 4 | 5 | 6 | class Emoji(str, enum.Enum): 7 | STAR = "\U0001F929" 8 | PARTYING_FACE = "\U0001F973" 9 | 10 | 11 | class Color(str, enum.Enum): 12 | RESET = "\033[0m" 13 | BLACK = "\033[30m" 14 | RED = "\033[31m" 15 | GREEN = "\033[32m" 16 | YELLOW = "\033[33m" 17 | BLUE = "\033[34m" 18 | MAGENTA = "\033[35m" 19 | CYAN = "\033[36m" 20 | WHITE = "\033[97m" 21 | BOLD_WHITE = "\033[1;37m" 22 | 23 | 24 | class ColorSelect(str, enum.Enum): 25 | AUTO = "auto" 26 | ALWAYS = "always" 27 | NEVER = "never" 28 | -------------------------------------------------------------------------------- /src/unimport/exceptions.py: -------------------------------------------------------------------------------- 1 | from unimport.meta import MakeSingletonWithParams 2 | 3 | __all__ = ( 4 | "UnimportBaseException", 5 | "UnknownConfigKeyException", 6 | "ConfigFileNotFound", 7 | ) 8 | 9 | 10 | class UnimportBaseException(Exception, metaclass=MakeSingletonWithParams): 11 | pass 12 | 13 | 14 | class UnknownConfigKeyException(UnimportBaseException): 15 | def __init__(self, key: str) -> None: 16 | self.key = key 17 | 18 | def __str__(self): 19 | return f"Unknown config key '{self.key}', please read the documentation for more information." 20 | 21 | 22 | class ConfigFileNotFound(FileNotFoundError, UnimportBaseException): 23 | def __init__(self, config_file: str) -> None: 24 | self.config_file = config_file 25 | 26 | def __str__(self): 27 | return f"Config file not found '{self.config_file}'" 28 | 29 | 30 | class UnsupportedConfigFile(UnimportBaseException): 31 | def __init__(self, config_file: str) -> None: 32 | self.config_file = config_file 33 | 34 | def __str__(self): 35 | return f"Unsupported config file '{self.config_file}'" 36 | -------------------------------------------------------------------------------- /src/unimport/main.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import contextlib 4 | import dataclasses 5 | import typing 6 | from pathlib import Path 7 | 8 | from unimport import commands, utils 9 | from unimport.analyzers import MainAnalyzer 10 | from unimport.color import paint 11 | from unimport.config import Config 12 | from unimport.enums import Color 13 | from unimport.statement import Import 14 | 15 | if typing.TYPE_CHECKING: 16 | from unimport.statement import ImportFrom 17 | 18 | 19 | __all__ = ("Main",) 20 | 21 | 22 | @dataclasses.dataclass 23 | class _Result: 24 | unused_imports: list[Import | ImportFrom] = dataclasses.field(repr=False) 25 | path: Path 26 | source: str 27 | encoding: str 28 | newline: str | None = None 29 | 30 | 31 | @dataclasses.dataclass 32 | class Main: 33 | argv: typing.Sequence[str] | None = None 34 | 35 | config: Config = dataclasses.field(init=False) 36 | is_syntax_error: bool = dataclasses.field(init=False, default=False) 37 | is_unused_imports: bool = dataclasses.field(init=False, default=False) 38 | refactor_applied: bool = dataclasses.field(init=False, default=False) 39 | 40 | def __post_init__(self): 41 | self.config = self.argv_to_config() 42 | 43 | def argv_to_config(self) -> Config: 44 | import sys 45 | 46 | from unimport.config import ParseConfig 47 | 48 | return ParseConfig.parse_args( 49 | commands.generate_parser().parse_args(self.argv if self.argv is not None else sys.argv[1:]) 50 | ) 51 | 52 | @contextlib.contextmanager 53 | def analysis(self, source: str, path: Path) -> typing.Iterator: 54 | analyzer = MainAnalyzer( 55 | source=source, 56 | path=path, 57 | include_star_import=self.config.include_star_import, 58 | ) 59 | try: 60 | analyzer.traverse() 61 | except SyntaxError as exc: 62 | print( 63 | paint(str(exc), Color.RED, self.config.use_color) 64 | + " at " 65 | + paint(path.as_posix(), Color.GREEN, self.config.use_color) 66 | ) 67 | self.is_syntax_error = True 68 | 69 | try: 70 | yield 71 | finally: 72 | analyzer.clear() 73 | 74 | def get_results(self) -> typing.Iterator[_Result]: 75 | for path in self.config.get_paths(): 76 | source, encoding, newline = utils.read(path) 77 | 78 | with self.analysis(source, path): 79 | unused_imports = list(Import.get_unused_imports(include_star_import=self.config.include_star_import)) 80 | if self.is_unused_imports is False: 81 | self.is_unused_imports = unused_imports != [] 82 | 83 | yield _Result(unused_imports, path, source, encoding, newline) 84 | 85 | def check(self, result: _Result) -> None: 86 | commands.check(result.path, result.unused_imports, self.config.use_color) 87 | 88 | def remove(self, result: _Result, refactor_result): 89 | commands.remove( 90 | result.path, 91 | result.encoding, 92 | result.newline, 93 | refactor_result, 94 | self.config.use_color, 95 | ) 96 | self.refactor_applied = True 97 | 98 | def diff(self, result, refactor_result): 99 | return commands.diff(result.path, result.source, refactor_result, self.config.use_color) 100 | 101 | @staticmethod 102 | def permission(result) -> bool: 103 | return commands.permission(result.path, result.encoding) 104 | 105 | @classmethod 106 | def run(cls, argv: typing.Sequence[str] | None = None) -> Main: 107 | from unimport.refactor import refactor_string 108 | 109 | self = cls(argv) 110 | for result in self.get_results(): 111 | if self.config.check: 112 | self.check(result) 113 | if any((self.config.diff, self.config.remove)): 114 | refactor_result = refactor_string(source=result.source, unused_imports=result.unused_imports) 115 | if self.config.diff: 116 | exists_diff = self.diff(result, refactor_result) 117 | if self.config.permission and exists_diff: 118 | self.config.remove = self.permission(result) 119 | if self.config.remove and result.source != refactor_result: 120 | self.remove(result, refactor_result) 121 | return self 122 | 123 | def exit_code(self): 124 | from unimport.utils import return_exit_code 125 | 126 | return return_exit_code( 127 | is_unused_imports=self.is_unused_imports, 128 | is_syntax_error=self.is_syntax_error, 129 | refactor_applied=self.refactor_applied, 130 | ) 131 | -------------------------------------------------------------------------------- /src/unimport/meta.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | 3 | __all__ = ("MakeSingletonWithParams",) 4 | 5 | 6 | class MakeSingletonWithParams(type): 7 | _instances: Dict = {} 8 | 9 | def __call__(cls, *args, **kwargs): 10 | """Returns the same instance if args and kwargs are the same, 11 | otherwise, creates a new instance.""" 12 | key = (cls, args, frozenset(kwargs.items())) 13 | if key not in cls._instances: 14 | cls._instances[key] = super().__call__(*args, **kwargs) 15 | return cls._instances[key] 16 | -------------------------------------------------------------------------------- /src/unimport/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakancelikdev/unimport/563fb2610888cb903ad4614eaee2e8fe0152535f/src/unimport/py.typed -------------------------------------------------------------------------------- /src/unimport/typing.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import typing 3 | 4 | import libcst as cst 5 | 6 | __all__ = ( 7 | "FunctionT", 8 | "ASTImportableT", 9 | "ASTFunctionT", 10 | "CFNT", 11 | "CSTImportT", 12 | ) 13 | 14 | ASTImportableT = typing.Union[ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef, ast.Attribute, ast.Name, ast.alias] 15 | ASTFunctionT = typing.TypeVar("ASTFunctionT", ast.FunctionDef, ast.AsyncFunctionDef) 16 | 17 | CFNT = typing.TypeVar("CFNT", ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef, ast.Name) 18 | CSTImportT = typing.TypeVar("CSTImportT", cst.Import, cst.ImportFrom) 19 | 20 | FunctionT = typing.TypeVar("FunctionT", bound=typing.Callable[..., typing.Any]) 21 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakancelikdev/unimport/563fb2610888cb903ad4614eaee2e8fe0152535f/tests/__init__.py -------------------------------------------------------------------------------- /tests/cases/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakancelikdev/unimport/563fb2610888cb903ad4614eaee2e8fe0152535f/tests/cases/__init__.py -------------------------------------------------------------------------------- /tests/cases/analyzer/all/module_from_all_set_the_name.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=3, name="__all__", is_all=False), 10 | Name(lineno=8, name="Any", is_all=False), 11 | Name(lineno=4, name="Test", is_all=True), 12 | Name(lineno=5, name="Test2", is_all=True), 13 | ] 14 | IMPORTS: List[Union[Import, ImportFrom]] = [ 15 | ImportFrom(lineno=1, column=1, name="typing", package="typing", star=True, suggestions=["Any"]), 16 | ] 17 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 18 | ImportFrom(lineno=1, column=1, name="typing", package="typing", star=True, suggestions=["Any"]), 19 | ] 20 | -------------------------------------------------------------------------------- /tests/cases/analyzer/as_import/all_unused_all_cases.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [] 9 | IMPORTS: List[Union[Import, ImportFrom]] = [ 10 | ImportFrom(lineno=1, column=1, name="z", package="x", star=False, suggestions=[]), 11 | Import(lineno=2, column=1, name="x", package="x"), 12 | ImportFrom(lineno=3, column=1, name="ss", package="t", star=False, suggestions=[]), 13 | Import(lineno=4, column=1, name="x", package="le"), 14 | ] 15 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 16 | Import(lineno=4, column=1, name="x", package="le"), 17 | ImportFrom(lineno=3, column=1, name="ss", package="t", star=False, suggestions=[]), 18 | Import(lineno=2, column=1, name="x", package="x"), 19 | ImportFrom(lineno=1, column=1, name="z", package="x", star=False, suggestions=[]), 20 | ] 21 | -------------------------------------------------------------------------------- /tests/cases/analyzer/as_import/dotted_module_name_simple.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [] 9 | IMPORTS: List[Union[Import, ImportFrom]] = [ 10 | Import(lineno=1, column=1, name="c", package="a.b"), 11 | ] 12 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 13 | Import(lineno=1, column=1, name="c", package="a.b"), 14 | ] 15 | -------------------------------------------------------------------------------- /tests/cases/analyzer/as_import/multiple_from_as_import.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [] 9 | IMPORTS: List[Union[Import, ImportFrom]] = [ 10 | ImportFrom(lineno=1, column=1, name="c", package="f", star=False, suggestions=[]), 11 | ImportFrom(lineno=1, column=2, name="k", package="f", star=False, suggestions=[]), 12 | ImportFrom(lineno=1, column=3, name="ii", package="f", star=False, suggestions=[]), 13 | ImportFrom( 14 | lineno=2, 15 | column=1, 16 | name="bar", 17 | package="fo", 18 | star=False, 19 | suggestions=[], 20 | ), 21 | ImportFrom(lineno=2, column=2, name="i", package="fo", star=False, suggestions=[]), 22 | ImportFrom(lineno=2, column=3, name="z", package="fo", star=False, suggestions=[]), 23 | ] 24 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 25 | ImportFrom(lineno=2, column=3, name="z", package="fo", star=False, suggestions=[]), 26 | ImportFrom(lineno=2, column=2, name="i", package="fo", star=False, suggestions=[]), 27 | ImportFrom( 28 | lineno=2, 29 | column=1, 30 | name="bar", 31 | package="fo", 32 | star=False, 33 | suggestions=[], 34 | ), 35 | ImportFrom(lineno=1, column=3, name="ii", package="f", star=False, suggestions=[]), 36 | ImportFrom(lineno=1, column=2, name="k", package="f", star=False, suggestions=[]), 37 | ImportFrom(lineno=1, column=1, name="c", package="f", star=False, suggestions=[]), 38 | ] 39 | -------------------------------------------------------------------------------- /tests/cases/analyzer/as_import/multiple_import_name_as_import.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [] 9 | IMPORTS: List[Union[Import, ImportFrom]] = [ 10 | Import(lineno=1, column=1, name="c", package="a"), 11 | Import(lineno=1, column=2, name="k", package="l"), 12 | Import(lineno=1, column=3, name="ii", package="i"), 13 | Import(lineno=2, column=1, name="bar", package="bar"), 14 | Import(lineno=2, column=2, name="i", package="i"), 15 | Import(lineno=2, column=3, name="z", package="x"), 16 | ] 17 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 18 | Import(lineno=2, column=3, name="z", package="x"), 19 | Import(lineno=2, column=2, name="i", package="i"), 20 | Import(lineno=2, column=1, name="bar", package="bar"), 21 | Import(lineno=1, column=3, name="ii", package="i"), 22 | Import(lineno=1, column=2, name="k", package="l"), 23 | Import(lineno=1, column=1, name="c", package="a"), 24 | ] 25 | -------------------------------------------------------------------------------- /tests/cases/analyzer/as_import/multiple_import_name_as_import_duplicate.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=5, name="print", is_all=False), 10 | Name(lineno=5, name="bar", is_all=False), 11 | ] 12 | IMPORTS: List[Union[Import, ImportFrom]] = [ 13 | Import(lineno=1, column=1, name="c", package="a"), 14 | Import(lineno=1, column=2, name="k", package="l"), 15 | Import(lineno=1, column=3, name="ii", package="i"), 16 | Import(lineno=2, column=1, name="bar", package="bar"), 17 | Import(lineno=2, column=2, name="i", package="i"), 18 | Import(lineno=2, column=3, name="z", package="x"), 19 | Import(lineno=3, column=1, name="bar", package="bar"), 20 | Import(lineno=3, column=2, name="i", package="i"), 21 | Import(lineno=3, column=3, name="z", package="x"), 22 | ] 23 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 24 | Import(lineno=3, column=3, name="z", package="x"), 25 | Import(lineno=3, column=2, name="i", package="i"), 26 | Import(lineno=2, column=3, name="z", package="x"), 27 | Import(lineno=2, column=2, name="i", package="i"), 28 | Import(lineno=2, column=1, name="bar", package="bar"), 29 | Import(lineno=1, column=3, name="ii", package="i"), 30 | Import(lineno=1, column=2, name="k", package="l"), 31 | Import(lineno=1, column=1, name="c", package="a"), 32 | ] 33 | -------------------------------------------------------------------------------- /tests/cases/analyzer/as_import/used_all_cases.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=7, name="print", is_all=False), 10 | Name(lineno=7, name="x", is_all=False), 11 | ] 12 | IMPORTS: List[Union[Import, ImportFrom]] = [ 13 | ImportFrom(lineno=1, column=1, name="z", package="x", star=False, suggestions=[]), 14 | Import(lineno=2, column=1, name="x", package="x"), 15 | ImportFrom(lineno=3, column=1, name="ss", package="t", star=False, suggestions=[]), 16 | Import(lineno=4, column=1, name="bar", package="bar"), 17 | Import(lineno=4, column=2, name="i", package="i"), 18 | Import(lineno=4, column=3, name="z", package="x"), 19 | Import(lineno=5, column=1, name="x", package="le"), 20 | ] 21 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 22 | Import(lineno=4, column=3, name="z", package="x"), 23 | Import(lineno=4, column=2, name="i", package="i"), 24 | Import(lineno=4, column=1, name="bar", package="bar"), 25 | ImportFrom(lineno=3, column=1, name="ss", package="t", star=False, suggestions=[]), 26 | Import(lineno=2, column=1, name="x", package="x"), 27 | ImportFrom(lineno=1, column=1, name="z", package="x", star=False, suggestions=[]), 28 | ] 29 | -------------------------------------------------------------------------------- /tests/cases/analyzer/dotted/dotted_module_name_simple.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [] 9 | IMPORTS: List[Union[Import, ImportFrom]] = [ 10 | Import(lineno=1, column=1, name="a.b", package="a.b"), 11 | ] 12 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 13 | Import(lineno=1, column=1, name="a.b", package="a.b"), 14 | ] 15 | -------------------------------------------------------------------------------- /tests/cases/analyzer/duplicate_import/different_duplicate_unused.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [] 9 | IMPORTS: List[Union[Import, ImportFrom]] = [ 10 | ImportFrom(lineno=1, column=1, name="z", package="x", star=False, suggestions=[]), 11 | ImportFrom(lineno=2, column=1, name="z", package="y", star=False, suggestions=[]), 12 | ] 13 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 14 | ImportFrom(lineno=2, column=1, name="z", package="y", star=False, suggestions=[]), 15 | ImportFrom(lineno=1, column=1, name="z", package="x", star=False, suggestions=[]), 16 | ] 17 | -------------------------------------------------------------------------------- /tests/cases/analyzer/duplicate_import/different_duplicate_used.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=4, name="print", is_all=False), 10 | Name(lineno=4, name="z", is_all=False), 11 | ] 12 | IMPORTS: List[Union[Import, ImportFrom]] = [ 13 | ImportFrom(lineno=1, column=1, name="z", package="x", star=False, suggestions=[]), 14 | ImportFrom(lineno=2, column=1, name="z", package="y", star=False, suggestions=[]), 15 | ] 16 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 17 | ImportFrom(lineno=1, column=1, name="z", package="x", star=False, suggestions=[]) 18 | ] 19 | -------------------------------------------------------------------------------- /tests/cases/analyzer/duplicate_import/full_unused.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [] 9 | IMPORTS: List[Union[Import, ImportFrom]] = [ 10 | ImportFrom(lineno=1, column=1, name="y", package="x", star=False, suggestions=[]), 11 | ImportFrom(lineno=2, column=1, name="y", package="x", star=False, suggestions=[]), 12 | ImportFrom(lineno=3, column=1, name="x", package="t", star=False, suggestions=[]), 13 | Import(lineno=4, column=1, name="re", package="re"), 14 | Import(lineno=5, column=1, name="ll", package="ll"), 15 | Import(lineno=6, column=1, name="ll", package="ll"), 16 | ImportFrom(lineno=7, column=1, name="e", package="c", star=False, suggestions=[]), 17 | Import(lineno=8, column=1, name="e", package="e"), 18 | ] 19 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 20 | Import(lineno=8, column=1, name="e", package="e"), 21 | ImportFrom(lineno=7, column=1, name="e", package="c", star=False, suggestions=[]), 22 | Import(lineno=6, column=1, name="ll", package="ll"), 23 | Import(lineno=5, column=1, name="ll", package="ll"), 24 | Import(lineno=4, column=1, name="re", package="re"), 25 | ImportFrom(lineno=3, column=1, name="x", package="t", star=False, suggestions=[]), 26 | ImportFrom(lineno=2, column=1, name="y", package="x", star=False, suggestions=[]), 27 | ImportFrom(lineno=1, column=1, name="y", package="x", star=False, suggestions=[]), 28 | ] 29 | -------------------------------------------------------------------------------- /tests/cases/analyzer/duplicate_import/import_in_function.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=6, name="t", is_all=False), 10 | Name(lineno=8, name="f", is_all=False), 11 | Name(lineno=13, name="print", is_all=False), 12 | Name(lineno=13, name="t", is_all=False), 13 | ] 14 | IMPORTS: List[Union[Import, ImportFrom]] = [ 15 | Import(lineno=1, column=1, name="t", package="t"), 16 | ImportFrom(lineno=2, column=1, name="t", package="l", star=False, suggestions=[]), 17 | ImportFrom(lineno=3, column=1, name="y", package="x", star=False, suggestions=[]), 18 | ImportFrom(lineno=3, column=2, name="z", package="x", star=False, suggestions=[]), 19 | ImportFrom(lineno=3, column=3, name="t", package="x", star=False, suggestions=[]), 20 | Import(lineno=7, column=1, name="x", package="x"), 21 | ImportFrom(lineno=11, column=1, name="ii", package="i", star=False, suggestions=[]), 22 | ImportFrom(lineno=11, column=2, name="t", package="i", star=False, suggestions=[]), 23 | ] 24 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 25 | ImportFrom(lineno=11, column=1, name="ii", package="i", star=False, suggestions=[]), 26 | Import(lineno=7, column=1, name="x", package="x"), 27 | ImportFrom(lineno=3, column=2, name="z", package="x", star=False, suggestions=[]), 28 | ImportFrom(lineno=3, column=1, name="y", package="x", star=False, suggestions=[]), 29 | ImportFrom(lineno=2, column=1, name="t", package="l", star=False, suggestions=[]), 30 | Import(lineno=1, column=1, name="t", package="t"), 31 | ] 32 | -------------------------------------------------------------------------------- /tests/cases/analyzer/duplicate_import/import_in_function_used_two_different.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=3, name="print", is_all=False), 10 | Name(lineno=3, name="t", is_all=False), 11 | Name(lineno=9, name="t", is_all=False), 12 | Name(lineno=12, name="f", is_all=False), 13 | Name(lineno=16, name="print", is_all=False), 14 | Name(lineno=16, name="t", is_all=False), 15 | ] 16 | IMPORTS: List[Union[Import, ImportFrom]] = [ 17 | Import(lineno=1, column=1, name="t", package="t"), 18 | ImportFrom(lineno=6, column=1, name="t", package="l", star=False, suggestions=[]), 19 | ImportFrom(lineno=7, column=1, name="y", package="x", star=False, suggestions=[]), 20 | ImportFrom(lineno=7, column=2, name="z", package="x", star=False, suggestions=[]), 21 | ImportFrom(lineno=7, column=3, name="t", package="x", star=False, suggestions=[]), 22 | Import(lineno=10, column=1, name="x", package="x"), 23 | ImportFrom(lineno=14, column=1, name="ii", package="i", star=False, suggestions=[]), 24 | ImportFrom(lineno=14, column=2, name="t", package="i", star=False, suggestions=[]), 25 | ] 26 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 27 | ImportFrom(lineno=14, column=1, name="ii", package="i", star=False, suggestions=[]), 28 | Import(lineno=10, column=1, name="x", package="x"), 29 | ImportFrom(lineno=7, column=2, name="z", package="x", star=False, suggestions=[]), 30 | ImportFrom(lineno=7, column=1, name="y", package="x", star=False, suggestions=[]), 31 | ImportFrom(lineno=6, column=1, name="t", package="l", star=False, suggestions=[]), 32 | ] 33 | -------------------------------------------------------------------------------- /tests/cases/analyzer/duplicate_import/multi_duplicate.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [] 9 | IMPORTS: List[Union[Import, ImportFrom]] = [ 10 | ImportFrom(lineno=1, column=1, name="y", package="x", star=False, suggestions=[]), 11 | ImportFrom(lineno=1, column=2, name="z", package="x", star=False, suggestions=[]), 12 | ImportFrom(lineno=1, column=3, name="t", package="x", star=False, suggestions=[]), 13 | Import(lineno=2, column=1, name="t", package="t"), 14 | ImportFrom(lineno=3, column=1, name="t", package="l", star=False, suggestions=[]), 15 | ] 16 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 17 | ImportFrom(lineno=3, column=1, name="t", package="l", star=False, suggestions=[]), 18 | Import(lineno=2, column=1, name="t", package="t"), 19 | ImportFrom(lineno=1, column=3, name="t", package="x", star=False, suggestions=[]), 20 | ImportFrom(lineno=1, column=2, name="z", package="x", star=False, suggestions=[]), 21 | ImportFrom(lineno=1, column=1, name="y", package="x", star=False, suggestions=[]), 22 | ] 23 | -------------------------------------------------------------------------------- /tests/cases/analyzer/duplicate_import/multi_duplicate_one_used.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=5, name="print", is_all=False), 10 | Name(lineno=5, name="t", is_all=False), 11 | ] 12 | IMPORTS: List[Union[Import, ImportFrom]] = [ 13 | ImportFrom(lineno=1, column=1, name="y", package="x", star=False, suggestions=[]), 14 | ImportFrom(lineno=1, column=2, name="z", package="x", star=False, suggestions=[]), 15 | ImportFrom(lineno=1, column=3, name="t", package="x", star=False, suggestions=[]), 16 | Import(lineno=2, column=1, name="t", package="t"), 17 | ImportFrom(lineno=3, column=1, name="t", package="l", star=False, suggestions=[]), 18 | ] 19 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 20 | Import(lineno=2, column=1, name="t", package="t"), 21 | ImportFrom(lineno=1, column=3, name="t", package="x", star=False, suggestions=[]), 22 | ImportFrom(lineno=1, column=2, name="z", package="x", star=False, suggestions=[]), 23 | ImportFrom(lineno=1, column=1, name="y", package="x", star=False, suggestions=[]), 24 | ] 25 | -------------------------------------------------------------------------------- /tests/cases/analyzer/duplicate_import/one_used.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=13, name="p", is_all=False), 10 | Name(lineno=13, name="Path", is_all=False), 11 | ] 12 | IMPORTS: List[Union[Import, ImportFrom]] = [ 13 | ImportFrom(lineno=1, column=1, name="y", package="x", star=False, suggestions=[]), 14 | ImportFrom(lineno=2, column=1, name="y", package="x", star=False, suggestions=[]), 15 | ImportFrom(lineno=3, column=1, name="x", package="t", star=False, suggestions=[]), 16 | Import(lineno=4, column=1, name="re", package="re"), 17 | Import(lineno=5, column=1, name="ll", package="ll"), 18 | Import(lineno=6, column=1, name="ll", package="ll"), 19 | ImportFrom(lineno=7, column=1, name="e", package="c", star=False, suggestions=[]), 20 | Import(lineno=8, column=1, name="e", package="e"), 21 | ImportFrom( 22 | lineno=9, 23 | column=1, 24 | name="Path", 25 | package="pathlib", 26 | star=False, 27 | suggestions=[], 28 | ), 29 | ImportFrom( 30 | lineno=10, 31 | column=1, 32 | name="Path", 33 | package="pathlib", 34 | star=False, 35 | suggestions=[], 36 | ), 37 | ] 38 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 39 | ImportFrom( 40 | lineno=9, 41 | column=1, 42 | name="Path", 43 | package="pathlib", 44 | star=False, 45 | suggestions=[], 46 | ), 47 | Import(lineno=8, column=1, name="e", package="e"), 48 | ImportFrom(lineno=7, column=1, name="e", package="c", star=False, suggestions=[]), 49 | Import(lineno=6, column=1, name="ll", package="ll"), 50 | Import(lineno=5, column=1, name="ll", package="ll"), 51 | Import(lineno=4, column=1, name="re", package="re"), 52 | ImportFrom(lineno=3, column=1, name="x", package="t", star=False, suggestions=[]), 53 | ImportFrom(lineno=2, column=1, name="y", package="x", star=False, suggestions=[]), 54 | ImportFrom(lineno=1, column=1, name="y", package="x", star=False, suggestions=[]), 55 | ] 56 | -------------------------------------------------------------------------------- /tests/cases/analyzer/duplicate_import/one_used_bottom_multi_duplicate.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=6, name="print", is_all=False), 10 | Name(lineno=6, name="t", is_all=False), 11 | ] 12 | IMPORTS: List[Union[Import, ImportFrom]] = [ 13 | Import(lineno=1, column=1, name="t", package="t"), 14 | ImportFrom(lineno=2, column=1, name="t", package="l", star=False, suggestions=[]), 15 | ImportFrom(lineno=3, column=1, name="y", package="x", star=False, suggestions=[]), 16 | ImportFrom(lineno=3, column=2, name="z", package="x", star=False, suggestions=[]), 17 | ImportFrom(lineno=3, column=3, name="t", package="x", star=False, suggestions=[]), 18 | ] 19 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 20 | ImportFrom(lineno=3, column=2, name="z", package="x", star=False, suggestions=[]), 21 | ImportFrom(lineno=3, column=1, name="y", package="x", star=False, suggestions=[]), 22 | ImportFrom(lineno=2, column=1, name="t", package="l", star=False, suggestions=[]), 23 | Import(lineno=1, column=1, name="t", package="t"), 24 | ] 25 | -------------------------------------------------------------------------------- /tests/cases/analyzer/duplicate_import/same_line.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=18, name="x", is_all=False), 10 | Name(lineno=18, name="e", is_all=False), 11 | Name(lineno=18, name="u", is_all=False), 12 | Name(lineno=18, name="c", is_all=False), 13 | Name(lineno=18, name="ff", is_all=False), 14 | Name(lineno=18, name="tt", is_all=False), 15 | Name(lineno=18, name="ll", is_all=False), 16 | Name(lineno=18, name="el", is_all=False), 17 | Name(lineno=18, name="tl", is_all=False), 18 | Name(lineno=18, name="si", is_all=False), 19 | Name(lineno=18, name="ug", is_all=False), 20 | Name(lineno=18, name="yt", is_all=False), 21 | ] 22 | IMPORTS: List[Union[Import, ImportFrom]] = [ 23 | Import(lineno=1, column=1, name="x", package="x"), 24 | Import(lineno=1, column=2, name="x", package="x"), 25 | Import(lineno=1, column=3, name="yt", package="yt"), 26 | Import(lineno=2, column=1, name="e", package="e"), 27 | Import(lineno=2, column=2, name="y", package="y"), 28 | Import(lineno=2, column=3, name="e", package="e"), 29 | ImportFrom(lineno=3, column=1, name="u", package="z", star=False, suggestions=[]), 30 | ImportFrom(lineno=3, column=2, name="u", package="z", star=False, suggestions=[]), 31 | ImportFrom(lineno=4, column=1, name="c", package="bb", star=False, suggestions=[]), 32 | ImportFrom(lineno=4, column=2, name="d", package="bb", star=False, suggestions=[]), 33 | ImportFrom(lineno=4, column=3, name="c", package="bb", star=False, suggestions=[]), 34 | ImportFrom(lineno=4, column=4, name="c", package="bb", star=False, suggestions=[]), 35 | Import(lineno=5, column=1, name="ff", package="ff"), 36 | Import(lineno=5, column=2, name="tt", package="tt"), 37 | Import(lineno=5, column=3, name="ff", package="ff"), 38 | Import(lineno=5, column=4, name="ff", package="ff"), 39 | Import(lineno=5, column=5, name="tt", package="tt"), 40 | ImportFrom(lineno=6, column=1, name="ll", package="ee", star=False, suggestions=[]), 41 | ImportFrom(lineno=6, column=2, name="el", package="ee", star=False, suggestions=[]), 42 | ImportFrom(lineno=6, column=3, name="ll", package="ee", star=False, suggestions=[]), 43 | ImportFrom(lineno=6, column=4, name="el", package="ee", star=False, suggestions=[]), 44 | ImportFrom(lineno=6, column=5, name="tl", package="ee", star=False, suggestions=[]), 45 | ImportFrom(lineno=6, column=6, name="tl", package="ee", star=False, suggestions=[]), 46 | Import(lineno=14, column=1, name="si", package="iss"), 47 | Import(lineno=14, column=2, name="si", package="si"), 48 | ImportFrom( 49 | lineno=15, 50 | column=1, 51 | name="ug", 52 | package="gu", 53 | star=False, 54 | suggestions=[], 55 | ), 56 | ImportFrom( 57 | lineno=15, 58 | column=2, 59 | name="ug", 60 | package="gu", 61 | star=False, 62 | suggestions=[], 63 | ), 64 | ] 65 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 66 | ImportFrom( 67 | lineno=15, 68 | column=1, 69 | name="ug", 70 | package="gu", 71 | star=False, 72 | suggestions=[], 73 | ), 74 | Import(lineno=14, column=1, name="si", package="iss"), 75 | ImportFrom(lineno=6, column=5, name="tl", package="ee", star=False, suggestions=[]), 76 | ImportFrom(lineno=6, column=2, name="el", package="ee", star=False, suggestions=[]), 77 | ImportFrom(lineno=6, column=1, name="ll", package="ee", star=False, suggestions=[]), 78 | Import(lineno=5, column=3, name="ff", package="ff"), 79 | Import(lineno=5, column=2, name="tt", package="tt"), 80 | Import(lineno=5, column=1, name="ff", package="ff"), 81 | ImportFrom(lineno=4, column=3, name="c", package="bb", star=False, suggestions=[]), 82 | ImportFrom(lineno=4, column=2, name="d", package="bb", star=False, suggestions=[]), 83 | ImportFrom(lineno=4, column=1, name="c", package="bb", star=False, suggestions=[]), 84 | ImportFrom(lineno=3, column=1, name="u", package="z", star=False, suggestions=[]), 85 | Import(lineno=2, column=2, name="y", package="y"), 86 | Import(lineno=2, column=1, name="e", package="e"), 87 | Import(lineno=1, column=1, name="x", package="x"), 88 | ] 89 | -------------------------------------------------------------------------------- /tests/cases/analyzer/duplicate_import/three_used.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=13, name="p", is_all=False), 10 | Name(lineno=13, name="Path", is_all=False), 11 | Name(lineno=14, name="print", is_all=False), 12 | Name(lineno=14, name="ll", is_all=False), 13 | Name(lineno=17, name="e", is_all=False), 14 | ] 15 | IMPORTS: List[Union[Import, ImportFrom]] = [ 16 | ImportFrom(lineno=1, column=1, name="y", package="x", star=False, suggestions=[]), 17 | ImportFrom(lineno=2, column=1, name="y", package="x", star=False, suggestions=[]), 18 | ImportFrom(lineno=3, column=1, name="x", package="t", star=False, suggestions=[]), 19 | Import(lineno=4, column=1, name="re", package="re"), 20 | Import(lineno=5, column=1, name="ll", package="ll"), 21 | Import(lineno=6, column=1, name="ll", package="ll"), 22 | ImportFrom(lineno=7, column=1, name="e", package="c", star=False, suggestions=[]), 23 | Import(lineno=8, column=1, name="e", package="e"), 24 | ImportFrom( 25 | lineno=9, 26 | column=1, 27 | name="Path", 28 | package="pathlib", 29 | star=False, 30 | suggestions=[], 31 | ), 32 | ImportFrom( 33 | lineno=10, 34 | column=1, 35 | name="Path", 36 | package="pathlib", 37 | star=False, 38 | suggestions=[], 39 | ), 40 | ] 41 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 42 | ImportFrom( 43 | lineno=9, 44 | column=1, 45 | name="Path", 46 | package="pathlib", 47 | star=False, 48 | suggestions=[], 49 | ), 50 | ImportFrom(lineno=7, column=1, name="e", package="c", star=False, suggestions=[]), 51 | Import(lineno=5, column=1, name="ll", package="ll"), 52 | Import(lineno=4, column=1, name="re", package="re"), 53 | ImportFrom(lineno=3, column=1, name="x", package="t", star=False, suggestions=[]), 54 | ImportFrom(lineno=2, column=1, name="y", package="x", star=False, suggestions=[]), 55 | ImportFrom(lineno=1, column=1, name="y", package="x", star=False, suggestions=[]), 56 | ] 57 | -------------------------------------------------------------------------------- /tests/cases/analyzer/duplicate_import/two_multi_duplicate_one_used.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=6, name="print", is_all=False), 10 | Name(lineno=6, name="t", is_all=False), 11 | ] 12 | IMPORTS: List[Union[Import, ImportFrom]] = [ 13 | Import(lineno=1, column=1, name="t", package="t"), 14 | ImportFrom(lineno=2, column=1, name="t", package="l", star=False, suggestions=[]), 15 | ImportFrom(lineno=3, column=1, name="y", package="x", star=False, suggestions=[]), 16 | ImportFrom(lineno=3, column=2, name="z", package="x", star=False, suggestions=[]), 17 | ImportFrom(lineno=3, column=3, name="t", package="x", star=False, suggestions=[]), 18 | ImportFrom(lineno=4, column=1, name="ii", package="i", star=False, suggestions=[]), 19 | ImportFrom(lineno=4, column=2, name="t", package="i", star=False, suggestions=[]), 20 | ] 21 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 22 | ImportFrom(lineno=4, column=1, name="ii", package="i", star=False, suggestions=[]), 23 | ImportFrom(lineno=3, column=3, name="t", package="x", star=False, suggestions=[]), 24 | ImportFrom(lineno=3, column=2, name="z", package="x", star=False, suggestions=[]), 25 | ImportFrom(lineno=3, column=1, name="y", package="x", star=False, suggestions=[]), 26 | ImportFrom(lineno=2, column=1, name="t", package="l", star=False, suggestions=[]), 27 | Import(lineno=1, column=1, name="t", package="t"), 28 | ] 29 | -------------------------------------------------------------------------------- /tests/cases/analyzer/duplicate_import/two_used.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=12, name="p", is_all=False), 10 | Name(lineno=12, name="Path", is_all=False), 11 | Name(lineno=13, name="print", is_all=False), 12 | Name(lineno=13, name="ll", is_all=False), 13 | ] 14 | IMPORTS: List[Union[Import, ImportFrom]] = [ 15 | ImportFrom(lineno=1, column=1, name="y", package="x", star=False, suggestions=[]), 16 | ImportFrom(lineno=2, column=1, name="y", package="x", star=False, suggestions=[]), 17 | ImportFrom(lineno=3, column=1, name="x", package="t", star=False, suggestions=[]), 18 | Import(lineno=4, column=1, name="re", package="re"), 19 | Import(lineno=5, column=1, name="ll", package="ll"), 20 | Import(lineno=6, column=1, name="ll", package="ll"), 21 | ImportFrom(lineno=7, column=1, name="e", package="c", star=False, suggestions=[]), 22 | Import(lineno=8, column=1, name="e", package="e"), 23 | ImportFrom( 24 | lineno=9, 25 | column=1, 26 | name="Path", 27 | package="pathlib", 28 | star=False, 29 | suggestions=[], 30 | ), 31 | ImportFrom( 32 | lineno=10, 33 | column=1, 34 | name="Path", 35 | package="pathlib", 36 | star=False, 37 | suggestions=[], 38 | ), 39 | ] 40 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 41 | ImportFrom( 42 | lineno=9, 43 | column=1, 44 | name="Path", 45 | package="pathlib", 46 | star=False, 47 | suggestions=[], 48 | ), 49 | Import(lineno=8, column=1, name="e", package="e"), 50 | ImportFrom(lineno=7, column=1, name="e", package="c", star=False, suggestions=[]), 51 | Import(lineno=5, column=1, name="ll", package="ll"), 52 | Import(lineno=4, column=1, name="re", package="re"), 53 | ImportFrom(lineno=3, column=1, name="x", package="t", star=False, suggestions=[]), 54 | ImportFrom(lineno=2, column=1, name="y", package="x", star=False, suggestions=[]), 55 | ImportFrom(lineno=1, column=1, name="y", package="x", star=False, suggestions=[]), 56 | ] 57 | -------------------------------------------------------------------------------- /tests/cases/analyzer/error/as_import.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=3, name="ImportError", is_all=False), 10 | Name(lineno=4, name="print", is_all=False), 11 | ] 12 | IMPORTS: List[Union[Import, ImportFrom]] = [] 13 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 14 | -------------------------------------------------------------------------------- /tests/cases/analyzer/error/bad_syntax.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [] 9 | IMPORTS: List[Union[Import, ImportFrom]] = [] 10 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 11 | -------------------------------------------------------------------------------- /tests/cases/analyzer/error/import_else_another.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=3, name="ImportError", is_all=False), 10 | Name(lineno=6, name="print", is_all=False), 11 | Name(lineno=6, name="x", is_all=False), 12 | ] 13 | IMPORTS: List[Union[Import, ImportFrom]] = [] 14 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 15 | -------------------------------------------------------------------------------- /tests/cases/analyzer/error/syntax_error.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [] 9 | IMPORTS: List[Union[Import, ImportFrom]] = [] 10 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 11 | -------------------------------------------------------------------------------- /tests/cases/analyzer/error/try_except.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=3, name="Exception", is_all=False), 10 | Name(lineno=8, name="BaseException", is_all=False), 11 | Name(lineno=13, name="OSError", is_all=False), 12 | Name(lineno=18, name="OSError", is_all=False), 13 | Name(lineno=18, name="AttributeError", is_all=False), 14 | ] 15 | IMPORTS: List[Union[Import, ImportFrom]] = [] 16 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 17 | -------------------------------------------------------------------------------- /tests/cases/analyzer/error/tuple.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=3, name="A", is_all=False), 10 | Name(lineno=3, name="ImportError", is_all=False), 11 | ] 12 | IMPORTS: List[Union[Import, ImportFrom]] = [] 13 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 14 | -------------------------------------------------------------------------------- /tests/cases/analyzer/if_dispatch/case_1.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [Name(lineno=6, name="x", is_all=False)] 9 | IMPORTS: List[Union[Import, ImportFrom]] = [] 10 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 11 | -------------------------------------------------------------------------------- /tests/cases/analyzer/if_dispatch/case_2.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [Name(lineno=6, name="y", is_all=False)] 9 | IMPORTS: List[Union[Import, ImportFrom]] = [] 10 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 11 | -------------------------------------------------------------------------------- /tests/cases/analyzer/if_dispatch/case_3.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [Name(lineno=6, name="y", is_all=False)] 9 | IMPORTS: List[Union[Import, ImportFrom]] = [] 10 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 11 | -------------------------------------------------------------------------------- /tests/cases/analyzer/if_dispatch/case_4.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=3, name="sys.version_info", is_all=False), 10 | Name(lineno=9, name="Literal", is_all=False), 11 | ] 12 | IMPORTS: List[Union[Import, ImportFrom]] = [Import(lineno=1, column=1, name="sys", package="sys")] 13 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 14 | -------------------------------------------------------------------------------- /tests/cases/analyzer/if_dispatch/case_5.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=1, name="condition", is_all=False), 10 | Name(lineno=6, name="ii", is_all=False), 11 | ] 12 | IMPORTS: List[Union[Import, ImportFrom]] = [ 13 | ImportFrom(lineno=2, column=1, name="y", package="x", star=False, suggestions=[]), 14 | Import(lineno=4, column=1, name="yy", package="y"), 15 | ] 16 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 17 | Import(lineno=4, column=1, name="yy", package="y"), 18 | ImportFrom(lineno=2, column=1, name="y", package="x", star=False, suggestions=[]), 19 | ] 20 | -------------------------------------------------------------------------------- /tests/cases/analyzer/if_dispatch/case_6.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=1, name="condition", is_all=False), 10 | Name(lineno=3, name="condition_2", is_all=False), 11 | Name(lineno=8, name="ii", is_all=False), 12 | ] 13 | IMPORTS: List[Union[Import, ImportFrom]] = [ 14 | ImportFrom(lineno=2, column=1, name="y", package="x", star=False, suggestions=[]), 15 | ImportFrom(lineno=2, column=2, name="ii", package="x", star=False, suggestions=[]), 16 | Import(lineno=4, column=2, name="tt", package="x"), 17 | Import(lineno=6, column=1, name="yy", package="y"), 18 | ] 19 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 20 | Import(lineno=6, column=1, name="yy", package="y"), 21 | Import(lineno=4, column=2, name="tt", package="x"), 22 | ImportFrom(lineno=2, column=1, name="y", package="x", star=False, suggestions=[]), 23 | ] 24 | -------------------------------------------------------------------------------- /tests/cases/analyzer/scope/global_import.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [Name(lineno=6, name="x", is_all=False)] 9 | IMPORTS: List[Union[Import, ImportFrom]] = [ 10 | Import(lineno=1, column=1, name="x", package="x"), 11 | Import(lineno=4, column=1, name="x", package="x"), 12 | ] 13 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [Import(lineno=1, column=1, name="x", package="x")] 14 | -------------------------------------------------------------------------------- /tests/cases/analyzer/scope/nonlocal_import.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [Name(lineno=8, name="x", is_all=False)] 9 | IMPORTS: List[Union[Import, ImportFrom]] = [ 10 | Import(lineno=1, column=1, name="x", package="x"), 11 | Import(lineno=4, column=1, name="x", package="x"), 12 | Import(lineno=7, column=1, name="x", package="x"), 13 | ] 14 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 15 | Import(lineno=4, column=1, name="x", package="x"), 16 | Import(lineno=1, column=1, name="x", package="x"), 17 | ] 18 | -------------------------------------------------------------------------------- /tests/cases/analyzer/scope/nonlocal_import_2.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=8, name="x", is_all=False), 10 | Name(lineno=10, name="x", is_all=False), 11 | ] 12 | IMPORTS: List[Union[Import, ImportFrom]] = [ 13 | Import(lineno=1, column=1, name="x", package="x"), 14 | Import(lineno=4, column=1, name="x", package="x"), 15 | Import(lineno=7, column=1, name="x", package="x"), 16 | ] 17 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [Import(lineno=1, column=1, name="x", package="x")] 18 | -------------------------------------------------------------------------------- /tests/cases/analyzer/scope/nonlocal_import_3.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=8, name="x", is_all=False), 10 | Name(lineno=10, name="x", is_all=False), 11 | Name(lineno=12, name="x", is_all=False), 12 | ] 13 | IMPORTS: List[Union[Import, ImportFrom]] = [ 14 | Import(lineno=1, column=1, name="x", package="x"), 15 | Import(lineno=4, column=1, name="x", package="x"), 16 | Import(lineno=7, column=1, name="x", package="x"), 17 | ] 18 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 19 | -------------------------------------------------------------------------------- /tests/cases/analyzer/scope/nonlocal_import_5.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [Name(lineno=5, name="x", is_all=False)] 9 | IMPORTS: List[Union[Import, ImportFrom]] = [Import(lineno=1, column=1, name="x", package="x")] 10 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 11 | -------------------------------------------------------------------------------- /tests/cases/analyzer/star_import/get_source_from_importable_names.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=4, name="CodeRange", is_all=False), 10 | Name(lineno=4, name="PositionProvider", is_all=False), 11 | ] 12 | IMPORTS: List[Union[Import, ImportFrom]] = [ 13 | ImportFrom( 14 | lineno=1, 15 | column=1, 16 | name="libcst.metadata", 17 | package="libcst.metadata", 18 | star=True, 19 | suggestions=["CodeRange", "PositionProvider"], 20 | ) 21 | ] 22 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 23 | ImportFrom( 24 | lineno=1, 25 | column=1, 26 | name="libcst.metadata", 27 | package="libcst.metadata", 28 | star=True, 29 | suggestions=["CodeRange", "PositionProvider"], 30 | ) 31 | ] 32 | -------------------------------------------------------------------------------- /tests/cases/analyzer/star_import/star_imports.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=9, name="print", is_all=False), 10 | Name(lineno=9, name="match", is_all=False), 11 | Name(lineno=10, name="print", is_all=False), 12 | Name(lineno=10, name="search", is_all=False), 13 | Name(lineno=11, name="print", is_all=False), 14 | Name(lineno=11, name="NAME", is_all=False), 15 | ] 16 | IMPORTS: List[Union[Import, ImportFrom]] = [ 17 | ImportFrom(lineno=1, column=1, name="os", package="os", star=True, suggestions=[]), 18 | ImportFrom(lineno=2, column=1, name="y", package="x", star=False, suggestions=[]), 19 | ImportFrom( 20 | lineno=3, 21 | column=1, 22 | name="re", 23 | package="re", 24 | star=True, 25 | suggestions=["match", "search"], 26 | ), 27 | ImportFrom( 28 | lineno=4, 29 | column=1, 30 | name="t.s.d", 31 | package="t.s.d", 32 | star=True, 33 | suggestions=[], 34 | ), 35 | ImportFrom( 36 | lineno=5, 37 | column=1, 38 | name="lib2to3.pgen2.token", 39 | package="lib2to3.pgen2.token", 40 | star=True, 41 | suggestions=["NAME"], 42 | ), 43 | ImportFrom( 44 | lineno=6, 45 | column=1, 46 | name="lib2to3.fixer_util", 47 | package="lib2to3.fixer_util", 48 | star=True, 49 | suggestions=[], 50 | ), 51 | ] 52 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 53 | ImportFrom( 54 | lineno=6, 55 | column=1, 56 | name="lib2to3.fixer_util", 57 | package="lib2to3.fixer_util", 58 | star=True, 59 | suggestions=[], 60 | ), 61 | ImportFrom( 62 | lineno=5, 63 | column=1, 64 | name="lib2to3.pgen2.token", 65 | package="lib2to3.pgen2.token", 66 | star=True, 67 | suggestions=["NAME"], 68 | ), 69 | ImportFrom( 70 | lineno=4, 71 | column=1, 72 | name="t.s.d", 73 | package="t.s.d", 74 | star=True, 75 | suggestions=[], 76 | ), 77 | ImportFrom( 78 | lineno=3, 79 | column=1, 80 | name="re", 81 | package="re", 82 | star=True, 83 | suggestions=["match", "search"], 84 | ), 85 | ImportFrom(lineno=2, column=1, name="y", package="x", star=False, suggestions=[]), 86 | ImportFrom(lineno=1, column=1, name="os", package="os", star=True, suggestions=[]), 87 | ] 88 | -------------------------------------------------------------------------------- /tests/cases/analyzer/star_import/two_suggestions.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=5, name="time", is_all=False), 10 | Name(lineno=6, name="path.join", is_all=False), 11 | ] 12 | IMPORTS: List[Union[Import, ImportFrom]] = [ 13 | ImportFrom( 14 | lineno=1, 15 | column=1, 16 | name="time", 17 | package="time", 18 | star=True, 19 | suggestions=["time"], 20 | ), 21 | ImportFrom( 22 | lineno=2, 23 | column=1, 24 | name="os", 25 | package="os", 26 | star=True, 27 | suggestions=["path"], 28 | ), 29 | ] 30 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 31 | ImportFrom( 32 | lineno=2, 33 | column=1, 34 | name="os", 35 | package="os", 36 | star=True, 37 | suggestions=["path"], 38 | ) 39 | ] 40 | -------------------------------------------------------------------------------- /tests/cases/analyzer/statement/match_statement.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=5, name="sort_by", is_all=False), 10 | Name(lineno=6, name="sort_by", is_all=False), 11 | Name(lineno=7, name="sort_by", is_all=False), 12 | ] 13 | IMPORTS: List[Union[Import, ImportFrom]] = [] 14 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 15 | -------------------------------------------------------------------------------- /tests/cases/analyzer/style/1_vertical.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=11, name="y", is_all=False), 10 | Name(lineno=11, name="q", is_all=False), 11 | Name(lineno=11, name="e", is_all=False), 12 | Name(lineno=11, name="r", is_all=False), 13 | Name(lineno=11, name="t", is_all=False), 14 | ] 15 | IMPORTS: List[Union[Import, ImportFrom]] = [ 16 | ImportFrom(lineno=1, column=1, name="q", package="x", star=False, suggestions=[]), 17 | ImportFrom(lineno=1, column=2, name="e", package="x", star=False, suggestions=[]), 18 | ImportFrom(lineno=1, column=3, name="r", package="x", star=False, suggestions=[]), 19 | ImportFrom(lineno=1, column=4, name="t", package="x", star=False, suggestions=[]), 20 | Import(lineno=7, column=1, name="y", package="y"), 21 | Import(lineno=8, column=1, name="u", package="u"), 22 | ] 23 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [Import(lineno=8, column=1, name="u", package="u")] 24 | -------------------------------------------------------------------------------- /tests/cases/analyzer/style/2_1_vertical.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=11, name="y", is_all=False), 10 | Name(lineno=11, name="q", is_all=False), 11 | Name(lineno=11, name="e", is_all=False), 12 | Name(lineno=11, name="t", is_all=False), 13 | ] 14 | IMPORTS: List[Union[Import, ImportFrom]] = [ 15 | ImportFrom(lineno=1, column=1, name="q", package="x", star=False, suggestions=[]), 16 | ImportFrom(lineno=1, column=2, name="e", package="x", star=False, suggestions=[]), 17 | ImportFrom(lineno=1, column=3, name="r", package="x", star=False, suggestions=[]), 18 | ImportFrom(lineno=1, column=4, name="t", package="x", star=False, suggestions=[]), 19 | Import(lineno=7, column=1, name="y", package="y"), 20 | Import(lineno=8, column=1, name="u", package="u"), 21 | ] 22 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 23 | Import(lineno=8, column=1, name="u", package="u"), 24 | ImportFrom(lineno=1, column=3, name="r", package="x", star=False, suggestions=[]), 25 | ] 26 | -------------------------------------------------------------------------------- /tests/cases/analyzer/style/3_1_vertical.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=11, name="y", is_all=False), 10 | Name(lineno=11, name="q", is_all=False), 11 | Name(lineno=11, name="e", is_all=False), 12 | Name(lineno=11, name="r", is_all=False), 13 | ] 14 | IMPORTS: List[Union[Import, ImportFrom]] = [ 15 | ImportFrom(lineno=1, column=1, name="q", package="x", star=False, suggestions=[]), 16 | ImportFrom(lineno=1, column=2, name="e", package="x", star=False, suggestions=[]), 17 | ImportFrom(lineno=1, column=3, name="r", package="x", star=False, suggestions=[]), 18 | ImportFrom(lineno=1, column=4, name="t", package="x", star=False, suggestions=[]), 19 | Import(lineno=7, column=1, name="y", package="y"), 20 | Import(lineno=8, column=1, name="u", package="u"), 21 | ] 22 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 23 | Import(lineno=8, column=1, name="u", package="u"), 24 | ImportFrom(lineno=1, column=4, name="t", package="x", star=False, suggestions=[]), 25 | ] 26 | -------------------------------------------------------------------------------- /tests/cases/analyzer/style/4_1_paren_horizontal.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=6, name="y", is_all=False), 10 | Name(lineno=6, name="q", is_all=False), 11 | Name(lineno=6, name="e", is_all=False), 12 | Name(lineno=6, name="r", is_all=False), 13 | ] 14 | IMPORTS: List[Union[Import, ImportFrom]] = [ 15 | ImportFrom(lineno=1, column=1, name="q", package="x", star=False, suggestions=[]), 16 | ImportFrom(lineno=1, column=2, name="e", package="x", star=False, suggestions=[]), 17 | ImportFrom(lineno=1, column=3, name="r", package="x", star=False, suggestions=[]), 18 | ImportFrom(lineno=1, column=4, name="t", package="x", star=False, suggestions=[]), 19 | Import(lineno=2, column=1, name="y", package="y"), 20 | Import(lineno=3, column=1, name="u", package="u"), 21 | ] 22 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 23 | Import(lineno=3, column=1, name="u", package="u"), 24 | ImportFrom(lineno=1, column=4, name="t", package="x", star=False, suggestions=[]), 25 | ] 26 | -------------------------------------------------------------------------------- /tests/cases/analyzer/style/vertical_dont_add_extra_line.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=7, name="test_list", is_all=False), 10 | Name(lineno=7, name="List", is_all=False), 11 | Name(lineno=7, name="str", is_all=False), 12 | ] 13 | IMPORTS: List[Union[Import, ImportFrom]] = [ 14 | Import(lineno=1, column=1, name="sys", package="sys"), 15 | ImportFrom( 16 | lineno=2, 17 | column=1, 18 | name="List", 19 | package="typing", 20 | star=False, 21 | suggestions=[], 22 | ), 23 | ] 24 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [Import(lineno=1, column=1, name="sys", package="sys")] 25 | -------------------------------------------------------------------------------- /tests/cases/analyzer/type_variable/type_assing_cast.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=3, name="TYPE_CHECKING", is_all=False), 10 | Name(lineno=7, name="HistoryType", is_all=False), 11 | Name(lineno=7, name="QtWebKit.QWebHistory", is_all=False), 12 | Name(lineno=7, name="cast", is_all=False), 13 | Name(lineno=7, name="return_value", is_all=False), 14 | ] 15 | IMPORTS: List[Union[Import, ImportFrom]] = [ 16 | ImportFrom( 17 | lineno=1, 18 | column=1, 19 | name="TYPE_CHECKING", 20 | package="typing", 21 | star=False, 22 | suggestions=[], 23 | ), 24 | ImportFrom( 25 | lineno=4, 26 | column=1, 27 | name="QtWebKit", 28 | package="PyQt5", 29 | star=False, 30 | suggestions=[], 31 | ), 32 | ] 33 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 34 | -------------------------------------------------------------------------------- /tests/cases/analyzer/type_variable/type_assing_list.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=3, name="TYPE_CHECKING", is_all=False), 10 | Name(lineno=7, name="HistoryType", is_all=False), 11 | Name(lineno=7, name="QWebHistory", is_all=False), 12 | Name(lineno=7, name="List", is_all=False), 13 | ] 14 | IMPORTS: List[Union[Import, ImportFrom]] = [ 15 | ImportFrom( 16 | lineno=1, 17 | column=1, 18 | name="TYPE_CHECKING", 19 | package="typing", 20 | star=False, 21 | suggestions=[], 22 | ), 23 | ImportFrom( 24 | lineno=1, 25 | column=2, 26 | name="List", 27 | package="typing", 28 | star=False, 29 | suggestions=[], 30 | ), 31 | ImportFrom( 32 | lineno=4, 33 | column=1, 34 | name="QWebHistory", 35 | package="PyQt5.QtWebKit", 36 | star=False, 37 | suggestions=[], 38 | ), 39 | ] 40 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 41 | -------------------------------------------------------------------------------- /tests/cases/analyzer/type_variable/type_assing_union.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=3, name="TYPE_CHECKING", is_all=False), 10 | Name(lineno=7, name="HistoryType", is_all=False), 11 | Name(lineno=7, name="QtWebEngineWidgets.QWebEngineHistory", is_all=False), 12 | Name(lineno=7, name="QtWebKit.QWebHistory", is_all=False), 13 | Name(lineno=7, name="Union", is_all=False), 14 | ] 15 | IMPORTS: List[Union[Import, ImportFrom]] = [ 16 | ImportFrom( 17 | lineno=1, 18 | column=1, 19 | name="TYPE_CHECKING", 20 | package="typing", 21 | star=False, 22 | suggestions=[], 23 | ), 24 | ImportFrom( 25 | lineno=1, 26 | column=2, 27 | name="Union", 28 | package="typing", 29 | star=False, 30 | suggestions=[], 31 | ), 32 | ImportFrom( 33 | lineno=4, 34 | column=1, 35 | name="QtWebEngineWidgets", 36 | package="PyQt5", 37 | star=False, 38 | suggestions=[], 39 | ), 40 | ImportFrom( 41 | lineno=4, 42 | column=2, 43 | name="QtWebKit", 44 | package="PyQt5", 45 | star=False, 46 | suggestions=[], 47 | ), 48 | ] 49 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 50 | -------------------------------------------------------------------------------- /tests/cases/analyzer/typing/function_arg.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=4, name="List", is_all=False), 10 | Name(lineno=4, name="Dict", is_all=False), 11 | ] 12 | IMPORTS: List[Union[Import, ImportFrom]] = [ 13 | ImportFrom( 14 | lineno=1, 15 | column=1, 16 | name="Dict", 17 | package="typing", 18 | star=False, 19 | suggestions=[], 20 | ), 21 | ImportFrom( 22 | lineno=1, 23 | column=2, 24 | name="List", 25 | package="typing", 26 | star=False, 27 | suggestions=[], 28 | ), 29 | ] 30 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 31 | -------------------------------------------------------------------------------- /tests/cases/analyzer/typing/function_return.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=4, name="list", is_all=False), 10 | Name(lineno=4, name="List", is_all=False), 11 | Name(lineno=4, name="Dict", is_all=False), 12 | ] 13 | IMPORTS: List[Union[Import, ImportFrom]] = [ 14 | ImportFrom( 15 | lineno=1, 16 | column=1, 17 | name="Dict", 18 | package="typing", 19 | star=False, 20 | suggestions=[], 21 | ), 22 | ImportFrom( 23 | lineno=1, 24 | column=2, 25 | name="List", 26 | package="typing", 27 | star=False, 28 | suggestions=[], 29 | ), 30 | ] 31 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 32 | -------------------------------------------------------------------------------- /tests/cases/analyzer/typing/function_str_arg.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=4, name="Dict", is_all=False), 10 | Name(lineno=4, name="Literal", is_all=False), 11 | Name(lineno=4, name="Dict", is_all=False), 12 | ] 13 | IMPORTS: List[Union[Import, ImportFrom]] = [ 14 | ImportFrom( 15 | lineno=1, 16 | column=1, 17 | name="Dict", 18 | package="typing", 19 | star=False, 20 | suggestions=[], 21 | ), 22 | ImportFrom( 23 | lineno=1, 24 | column=2, 25 | name="Literal", 26 | package="typing", 27 | star=False, 28 | suggestions=[], 29 | ), 30 | ] 31 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 32 | -------------------------------------------------------------------------------- /tests/cases/analyzer/typing/type_comment_funcdef.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=4, name="str", is_all=False), 10 | Name(lineno=4, name="List", is_all=False), 11 | Name(lineno=4, name="str", is_all=False), 12 | ] 13 | IMPORTS: List[Union[Import, ImportFrom]] = [ 14 | ImportFrom( 15 | lineno=1, 16 | column=1, 17 | name="List", 18 | package="typing", 19 | star=False, 20 | suggestions=[], 21 | ) 22 | ] 23 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 24 | -------------------------------------------------------------------------------- /tests/cases/analyzer/typing/type_comment_params.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=5, name="List", is_all=False), 10 | Name(lineno=6, name="str", is_all=False), 11 | ] 12 | IMPORTS: List[Union[Import, ImportFrom]] = [ 13 | ImportFrom(lineno=1, column=1, name="List", package="typing", star=False, suggestions=[]) 14 | ] 15 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 16 | -------------------------------------------------------------------------------- /tests/cases/analyzer/typing/type_comments.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=6, name="Any", is_all=False), 10 | Name(lineno=6, name="str", is_all=False), 11 | Name(lineno=6, name="Union", is_all=False), 12 | Name(lineno=6, name="Tuple", is_all=False), 13 | Name(lineno=6, name="Tuple", is_all=False), 14 | Name(lineno=6, name="str", is_all=False), 15 | Name(lineno=6, name="str", is_all=False), 16 | ] 17 | IMPORTS: List[Union[Import, ImportFrom]] = [ 18 | ImportFrom(lineno=1, column=1, name="Any", package="typing", star=False, suggestions=[]), 19 | ImportFrom(lineno=2, column=1, name="Tuple", package="typing", star=False, suggestions=[]), 20 | ImportFrom(lineno=3, column=1, name="Union", package="typing", star=False, suggestions=[]), 21 | ] 22 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 23 | -------------------------------------------------------------------------------- /tests/cases/analyzer/typing/type_comments_with_variable.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=4, name="List", is_all=False), 10 | Name(lineno=4, name="int", is_all=False), 11 | Name(lineno=4, name="test_variable", is_all=False), 12 | ] 13 | IMPORTS: List[Union[Import, ImportFrom]] = [ 14 | ImportFrom( 15 | lineno=1, 16 | column=1, 17 | name="List", 18 | package="typing", 19 | star=False, 20 | suggestions=[], 21 | ) 22 | ] 23 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 24 | -------------------------------------------------------------------------------- /tests/cases/analyzer/typing/type_parameter_syntax.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=6, name="Point", is_all=False), 10 | Name(lineno=6, name="tuple", is_all=False), 11 | Name(lineno=6, name="x", is_all=False), 12 | Name(lineno=6, name="float", is_all=False), 13 | ] 14 | IMPORTS: List[Union[Import, ImportFrom]] = [ 15 | Import(lineno=3, column=1, name="x", package="x"), 16 | Import(lineno=4, column=1, name="y", package="y"), 17 | ] 18 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 19 | Import(lineno=4, column=1, name="y", package="y"), 20 | ] 21 | -------------------------------------------------------------------------------- /tests/cases/analyzer/typing/variable.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=4, name="test", is_all=False), 10 | Name(lineno=4, name="List", is_all=False), 11 | Name(lineno=4, name="Dict", is_all=False), 12 | ] 13 | IMPORTS: List[Union[Import, ImportFrom]] = [ 14 | ImportFrom( 15 | lineno=1, 16 | column=1, 17 | name="Dict", 18 | package="typing", 19 | star=False, 20 | suggestions=[], 21 | ), 22 | ImportFrom( 23 | lineno=1, 24 | column=2, 25 | name="List", 26 | package="typing", 27 | star=False, 28 | suggestions=[], 29 | ), 30 | ] 31 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 32 | -------------------------------------------------------------------------------- /tests/cases/analyzer/unused/comment.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [Name(lineno=6, name="compile_command", is_all=False)] 9 | IMPORTS: List[Union[Import, ImportFrom]] = [ 10 | ImportFrom( 11 | lineno=3, 12 | column=1, 13 | name="compile_command", 14 | package="codeop", 15 | star=False, 16 | suggestions=[], 17 | ) 18 | ] 19 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 20 | -------------------------------------------------------------------------------- /tests/cases/analyzer/unused/do_not_remove_augmented_imports.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [Name(lineno=4, name="AUTHENTICATION_BACKENDS", is_all=False)] 9 | IMPORTS: List[Union[Import, ImportFrom]] = [ 10 | ImportFrom( 11 | lineno=1, 12 | column=1, 13 | name="AUTHENTICATION_BACKENDS", 14 | package="django.conf.global_settings", 15 | star=False, 16 | suggestions=[], 17 | ), 18 | ImportFrom( 19 | lineno=1, 20 | column=2, 21 | name="TEMPLATE_CONTEXT_PROCESSORS", 22 | package="django.conf.global_settings", 23 | star=False, 24 | suggestions=[], 25 | ), 26 | ] 27 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 28 | ImportFrom( 29 | lineno=1, 30 | column=2, 31 | name="TEMPLATE_CONTEXT_PROCESSORS", 32 | package="django.conf.global_settings", 33 | star=False, 34 | suggestions=[], 35 | ) 36 | ] 37 | -------------------------------------------------------------------------------- /tests/cases/analyzer/unused/future_from_import.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [] 9 | IMPORTS: List[Union[Import, ImportFrom]] = [] 10 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 11 | -------------------------------------------------------------------------------- /tests/cases/analyzer/unused/future_import.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [Name(lineno=4, name="__future__.absolute_import", is_all=False)] 9 | IMPORTS: List[Union[Import, ImportFrom]] = [Import(lineno=1, column=1, name="__future__", package="__future__")] 10 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [] 11 | -------------------------------------------------------------------------------- /tests/cases/analyzer/unused/get_star_imp_none.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [Name(lineno=3, name="ImportError", is_all=False)] 9 | IMPORTS: List[Union[Import, ImportFrom]] = [Import(lineno=7, column=1, name="t", package="t")] 10 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [Import(lineno=7, column=1, name="t", package="t")] 11 | -------------------------------------------------------------------------------- /tests/cases/analyzer/unused/inside_function_unused.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=6, name="print", is_all=False), 10 | Name(lineno=6, name="t", is_all=False), 11 | Name(lineno=7, name="ImportError", is_all=False), 12 | Name(lineno=10, name="math.pi", is_all=False), 13 | ] 14 | IMPORTS: List[Union[Import, ImportFrom]] = [ 15 | ImportFrom(lineno=2, column=1, name="y", package="x", star=False, suggestions=[]), 16 | ImportFrom(lineno=2, column=2, name="z", package="x", star=False, suggestions=[]), 17 | ] 18 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 19 | ImportFrom(lineno=2, column=2, name="z", package="x", star=False, suggestions=[]), 20 | ImportFrom(lineno=2, column=1, name="y", package="x", star=False, suggestions=[]), 21 | ] 22 | -------------------------------------------------------------------------------- /tests/cases/analyzer/unused/local_import.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=9, name="hakan", is_all=False), 10 | Name(lineno=10, name="b", is_all=False), 11 | Name(lineno=11, name="q", is_all=False), 12 | ] 13 | IMPORTS: List[Union[Import, ImportFrom]] = [ 14 | ImportFrom(lineno=1, column=1, name="y", package=".x", star=False, suggestions=[]), 15 | ImportFrom(lineno=2, column=1, name="t", package="..z", star=False, suggestions=[]), 16 | ImportFrom( 17 | lineno=3, 18 | column=1, 19 | name="a", 20 | package="...t", 21 | star=False, 22 | suggestions=[], 23 | ), 24 | ImportFrom(lineno=4, column=1, name="y", package=".x", star=False, suggestions=[]), 25 | ImportFrom( 26 | lineno=4, 27 | column=2, 28 | name="hakan", 29 | package=".x", 30 | star=False, 31 | suggestions=[], 32 | ), 33 | ImportFrom(lineno=5, column=1, name="u", package="..z", star=False, suggestions=[]), 34 | ImportFrom(lineno=5, column=2, name="b", package="..z", star=False, suggestions=[]), 35 | ImportFrom( 36 | lineno=6, 37 | column=1, 38 | name="z", 39 | package="...t", 40 | star=False, 41 | suggestions=[], 42 | ), 43 | ImportFrom( 44 | lineno=6, 45 | column=2, 46 | name="q", 47 | package="...t", 48 | star=False, 49 | suggestions=[], 50 | ), 51 | ] 52 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 53 | ImportFrom( 54 | lineno=6, 55 | column=1, 56 | name="z", 57 | package="...t", 58 | star=False, 59 | suggestions=[], 60 | ), 61 | ImportFrom(lineno=5, column=1, name="u", package="..z", star=False, suggestions=[]), 62 | ImportFrom(lineno=4, column=1, name="y", package=".x", star=False, suggestions=[]), 63 | ImportFrom( 64 | lineno=3, 65 | column=1, 66 | name="a", 67 | package="...t", 68 | star=False, 69 | suggestions=[], 70 | ), 71 | ImportFrom(lineno=2, column=1, name="t", package="..z", star=False, suggestions=[]), 72 | ImportFrom(lineno=1, column=1, name="y", package=".x", star=False, suggestions=[]), 73 | ] 74 | -------------------------------------------------------------------------------- /tests/cases/analyzer/unused/remove_unused_from_imports.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [ 9 | Name(lineno=5, name="print", is_all=False), 10 | Name(lineno=5, name="datetime.datetime.now", is_all=False), 11 | Name(lineno=5, name="datetime.datetime", is_all=False), 12 | ] 13 | IMPORTS: List[Union[Import, ImportFrom]] = [ 14 | Import(lineno=1, column=1, name="datetime", package="datetime"), 15 | ImportFrom( 16 | lineno=2, 17 | column=1, 18 | name="relativedelta", 19 | package="dateutil.relativedelta", 20 | star=False, 21 | suggestions=[], 22 | ), 23 | ] 24 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 25 | ImportFrom( 26 | lineno=2, 27 | column=1, 28 | name="relativedelta", 29 | package="dateutil.relativedelta", 30 | star=False, 31 | suggestions=[], 32 | ) 33 | ] 34 | -------------------------------------------------------------------------------- /tests/cases/analyzer/unused/star.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [Name(lineno=4, name="walk", is_all=False)] 9 | IMPORTS: List[Union[Import, ImportFrom]] = [ 10 | ImportFrom( 11 | lineno=1, 12 | column=1, 13 | name="os", 14 | package="os", 15 | star=True, 16 | suggestions=["walk"], 17 | ) 18 | ] 19 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [ 20 | ImportFrom( 21 | lineno=1, 22 | column=1, 23 | name="os", 24 | package="os", 25 | star=True, 26 | suggestions=["walk"], 27 | ) 28 | ] 29 | -------------------------------------------------------------------------------- /tests/cases/analyzer/unused/startswith_name.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from unimport.statement import Import, ImportFrom, Name 4 | 5 | __all__ = ["NAMES", "IMPORTS", "UNUSED_IMPORTS"] 6 | 7 | 8 | NAMES: List[Name] = [Name(lineno=4, name="xxx", is_all=False)] 9 | IMPORTS: List[Union[Import, ImportFrom]] = [Import(lineno=1, column=1, name="xx", package="xx")] 10 | UNUSED_IMPORTS: List[Union[Import, ImportFrom]] = [Import(lineno=1, column=1, name="xx", package="xx")] 11 | -------------------------------------------------------------------------------- /tests/cases/refactor/all/module_from_all_set_the_name.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | __all__ = ( 4 | "Test", 5 | "Test2" 6 | ) 7 | 8 | Any 9 | -------------------------------------------------------------------------------- /tests/cases/refactor/as_import/all_unused_all_cases.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/cases/refactor/as_import/dotted_module_name_simple.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/cases/refactor/as_import/multiple_from_as_import.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/cases/refactor/as_import/multiple_import_name_as_import.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/cases/refactor/as_import/multiple_import_name_as_import_duplicate.py: -------------------------------------------------------------------------------- 1 | import bar 2 | 3 | print(bar) 4 | -------------------------------------------------------------------------------- /tests/cases/refactor/as_import/used_all_cases.py: -------------------------------------------------------------------------------- 1 | import le as x 2 | 3 | print(x) 4 | -------------------------------------------------------------------------------- /tests/cases/refactor/dotted/dotted_module_name_simple.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/cases/refactor/duplicate_import/different_duplicate_unused.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/cases/refactor/duplicate_import/different_duplicate_used.py: -------------------------------------------------------------------------------- 1 | from y import z 2 | 3 | print(z) 4 | -------------------------------------------------------------------------------- /tests/cases/refactor/duplicate_import/full_unused.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/cases/refactor/duplicate_import/import_in_function.py: -------------------------------------------------------------------------------- 1 | from x import t 2 | 3 | 4 | def function(f=t): 5 | return f 6 | 7 | 8 | from i import t 9 | 10 | print(t) 11 | -------------------------------------------------------------------------------- /tests/cases/refactor/duplicate_import/import_in_function_used_two_different.py: -------------------------------------------------------------------------------- 1 | import t 2 | 3 | print(t) 4 | from x import t 5 | 6 | def function(f=t): 7 | 8 | return f 9 | 10 | from i import t 11 | 12 | print(t) 13 | -------------------------------------------------------------------------------- /tests/cases/refactor/duplicate_import/multi_duplicate.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/cases/refactor/duplicate_import/multi_duplicate_one_used.py: -------------------------------------------------------------------------------- 1 | from l import t 2 | 3 | print(t) 4 | -------------------------------------------------------------------------------- /tests/cases/refactor/duplicate_import/one_used.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | 4 | p = Path() 5 | -------------------------------------------------------------------------------- /tests/cases/refactor/duplicate_import/one_used_bottom_multi_duplicate.py: -------------------------------------------------------------------------------- 1 | from x import t 2 | 3 | 4 | print(t) 5 | -------------------------------------------------------------------------------- /tests/cases/refactor/duplicate_import/same_line.py: -------------------------------------------------------------------------------- 1 | import x, yt 2 | import e 3 | from z import u 4 | from bb import c 5 | import ff, tt 6 | from ee import ( 7 | ll, 8 | el, 9 | tl 10 | ) 11 | import si 12 | from gu import ug 13 | 14 | x, e, u, c, ff, tt, ll, el, tl, si, ug, yt 15 | -------------------------------------------------------------------------------- /tests/cases/refactor/duplicate_import/three_used.py: -------------------------------------------------------------------------------- 1 | import ll 2 | import e 3 | from pathlib import Path 4 | 5 | 6 | p = Path() 7 | print(ll) 8 | 9 | 10 | def function(e=e): pass 11 | -------------------------------------------------------------------------------- /tests/cases/refactor/duplicate_import/two_multi_duplicate_one_used.py: -------------------------------------------------------------------------------- 1 | from i import t 2 | 3 | print(t) 4 | -------------------------------------------------------------------------------- /tests/cases/refactor/duplicate_import/two_used.py: -------------------------------------------------------------------------------- 1 | import ll 2 | from pathlib import Path 3 | 4 | p = Path() 5 | print(ll) 6 | -------------------------------------------------------------------------------- /tests/cases/refactor/error/as_import.py: -------------------------------------------------------------------------------- 1 | try: 2 | import x 3 | except ImportError as err: 4 | print('try this code `pip install x`') 5 | -------------------------------------------------------------------------------- /tests/cases/refactor/error/bad_syntax.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | € = 2 3 | -------------------------------------------------------------------------------- /tests/cases/refactor/error/import_else_another.py: -------------------------------------------------------------------------------- 1 | try: 2 | import x 3 | except ImportError: 4 | import y as x 5 | 6 | print(x) 7 | -------------------------------------------------------------------------------- /tests/cases/refactor/error/syntax_error.py: -------------------------------------------------------------------------------- 1 | a :? = 0 -------------------------------------------------------------------------------- /tests/cases/refactor/error/try_except.py: -------------------------------------------------------------------------------- 1 | try: 2 | import x 3 | except Exception: 4 | pass 5 | 6 | try: 7 | import x 8 | except BaseException: 9 | pass 10 | 11 | try: 12 | import x 13 | except OSError: 14 | pass 15 | 16 | try: 17 | import x 18 | except (OSError, AttributeError): 19 | pass 20 | -------------------------------------------------------------------------------- /tests/cases/refactor/error/tuple.py: -------------------------------------------------------------------------------- 1 | try: 2 | import xa 3 | except (A, ImportError): 4 | pass 5 | -------------------------------------------------------------------------------- /tests/cases/refactor/if_dispatch/case_1.py: -------------------------------------------------------------------------------- 1 | if True: 2 | import x 3 | else: 4 | import y as x 5 | 6 | x 7 | -------------------------------------------------------------------------------- /tests/cases/refactor/if_dispatch/case_2.py: -------------------------------------------------------------------------------- 1 | if True: 2 | from x import y 3 | else: 4 | import y 5 | 6 | y 7 | -------------------------------------------------------------------------------- /tests/cases/refactor/if_dispatch/case_3.py: -------------------------------------------------------------------------------- 1 | if True: 2 | from x import y 3 | else: 4 | from z import a as y 5 | 6 | y 7 | -------------------------------------------------------------------------------- /tests/cases/refactor/if_dispatch/case_4.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if sys.version_info >= (3, 8): 4 | from typing import Literal 5 | else: 6 | from typing_extensions import Literal 7 | 8 | 9 | Literal 10 | -------------------------------------------------------------------------------- /tests/cases/refactor/if_dispatch/case_5.py: -------------------------------------------------------------------------------- 1 | if condition: 2 | from x import i as ii 3 | else: 4 | import r as ii 5 | 6 | ii 7 | -------------------------------------------------------------------------------- /tests/cases/refactor/if_dispatch/case_6.py: -------------------------------------------------------------------------------- 1 | if condition: 2 | from x import i as ii 3 | elif condition_2: 4 | import ii 5 | else: 6 | import r as ii 7 | 8 | ii 9 | -------------------------------------------------------------------------------- /tests/cases/refactor/scope/global_import.py: -------------------------------------------------------------------------------- 1 | 2 | def f(): 3 | import x 4 | 5 | x 6 | 7 | -------------------------------------------------------------------------------- /tests/cases/refactor/scope/nonlocal_import.py: -------------------------------------------------------------------------------- 1 | 2 | def func(): 3 | 4 | def inner(): 5 | import x 6 | x 7 | 8 | -------------------------------------------------------------------------------- /tests/cases/refactor/scope/nonlocal_import_2.py: -------------------------------------------------------------------------------- 1 | 2 | def func(): 3 | import x 4 | 5 | def inner(): 6 | import x 7 | x 8 | 9 | x 10 | 11 | -------------------------------------------------------------------------------- /tests/cases/refactor/scope/nonlocal_import_3.py: -------------------------------------------------------------------------------- 1 | import x 2 | 3 | def func(): 4 | import x 5 | 6 | def inner(): 7 | import x 8 | x 9 | 10 | x 11 | 12 | x 13 | 14 | -------------------------------------------------------------------------------- /tests/cases/refactor/scope/nonlocal_import_5.py: -------------------------------------------------------------------------------- 1 | import x 2 | 3 | def func(): 4 | def inner(): 5 | x 6 | 7 | -------------------------------------------------------------------------------- /tests/cases/refactor/star_import/2.py: -------------------------------------------------------------------------------- 1 | from lib2to3.pgen2.grammar import Grammar 2 | 3 | 4 | print(Grammar) 5 | -------------------------------------------------------------------------------- /tests/cases/refactor/star_import/get_source_from_importable_names.py: -------------------------------------------------------------------------------- 1 | from libcst.metadata import CodeRange, PositionProvider 2 | 3 | 4 | CodeRange, PositionProvider 5 | -------------------------------------------------------------------------------- /tests/cases/refactor/star_import/star_imports.py: -------------------------------------------------------------------------------- 1 | from re import match, search 2 | from lib2to3.pgen2.token import NAME 3 | 4 | 5 | print(match) 6 | print(search) 7 | print(NAME) 8 | -------------------------------------------------------------------------------- /tests/cases/refactor/star_import/two_suggestions.py: -------------------------------------------------------------------------------- 1 | from time import * 2 | from os import path 3 | 4 | 5 | time() # Function from time module. 6 | path.join() 7 | -------------------------------------------------------------------------------- /tests/cases/refactor/statement/match_statement.py: -------------------------------------------------------------------------------- 1 | # pytest.mark.skipif(not PY310_PLUS, reason: "this statement is supported above python 3.10") 2 | 3 | # https://github.com/hakancelikdev/unimport/issues/291 4 | 5 | match sort_by: 6 | case 'date': sort_by = ' updated DESC,' 7 | case _: sort_by = '' 8 | -------------------------------------------------------------------------------- /tests/cases/refactor/style/1_vertical.py: -------------------------------------------------------------------------------- 1 | from x import ( 2 | q, 3 | e, 4 | r, 5 | t 6 | ) 7 | import y 8 | 9 | 10 | y, q, e, r, t 11 | -------------------------------------------------------------------------------- /tests/cases/refactor/style/2_1_vertical.py: -------------------------------------------------------------------------------- 1 | from x import ( 2 | q, 3 | e, 4 | t 5 | ) 6 | import y 7 | 8 | 9 | y, q, e, t 10 | -------------------------------------------------------------------------------- /tests/cases/refactor/style/3_1_vertical.py: -------------------------------------------------------------------------------- 1 | from x import ( 2 | q, 3 | e, 4 | r 5 | ) 6 | import y 7 | 8 | 9 | y, q, e, r 10 | -------------------------------------------------------------------------------- /tests/cases/refactor/style/4_1_paren_horizontal.py: -------------------------------------------------------------------------------- 1 | from x import (q, e, r) 2 | import y 3 | 4 | 5 | y, q, e, r 6 | -------------------------------------------------------------------------------- /tests/cases/refactor/style/vertical_dont_add_extra_line.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | List, 3 | ) 4 | 5 | 6 | test_list: List[str] = ["spam", "eggs"] 7 | -------------------------------------------------------------------------------- /tests/cases/refactor/type_variable/type_assing_cast.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | if TYPE_CHECKING: 4 | from PyQt5 import QtWebKit 5 | 6 | 7 | HistoryType = cast('QtWebKit.QWebHistory', return_value) 8 | 9 | -------------------------------------------------------------------------------- /tests/cases/refactor/type_variable/type_assing_list.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, List 2 | 3 | if TYPE_CHECKING: 4 | from PyQt5.QtWebKit import QWebHistory 5 | 6 | 7 | HistoryType = List['QWebHistory'] 8 | 9 | -------------------------------------------------------------------------------- /tests/cases/refactor/type_variable/type_assing_union.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Union 2 | 3 | if TYPE_CHECKING: 4 | from PyQt5 import QtWebEngineWidgets, QtWebKit 5 | 6 | 7 | HistoryType = Union['QtWebEngineWidgets.QWebEngineHistory', 'QtWebKit.QWebHistory'] 8 | 9 | -------------------------------------------------------------------------------- /tests/cases/refactor/typing/function_arg.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List 2 | 3 | 4 | def test(arg:"List[Dict]") -> None: 5 | pass 6 | -------------------------------------------------------------------------------- /tests/cases/refactor/typing/function_return.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List 2 | 3 | 4 | def test(arg: list) -> "List[Dict]": 5 | pass 6 | -------------------------------------------------------------------------------- /tests/cases/refactor/typing/function_str_arg.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Literal 2 | 3 | 4 | def test(item, when: "Literal['Dict']") -> None: 5 | pass 6 | -------------------------------------------------------------------------------- /tests/cases/refactor/typing/type_comment_funcdef.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | def x(y): 5 | # type: (str) -> List[str] 6 | pass 7 | -------------------------------------------------------------------------------- /tests/cases/refactor/typing/type_comment_params.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | def x( 5 | f, # type:List, 6 | r # type:str 7 | ): 8 | pass 9 | -------------------------------------------------------------------------------- /tests/cases/refactor/typing/type_comments.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | from typing import Tuple 3 | from typing import Union 4 | 5 | 6 | def function(a, b): 7 | # type: (Any, str) -> Union[Tuple[None, None], Tuple[str, str]] 8 | pass 9 | -------------------------------------------------------------------------------- /tests/cases/refactor/typing/type_comments_with_variable.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | test_variable = [2] # type: List[int] 5 | -------------------------------------------------------------------------------- /tests/cases/refactor/typing/type_parameter_syntax.py: -------------------------------------------------------------------------------- 1 | # pytest.mark.skipif(not PY312_PLUS, reason: "type parameter syntax is supported above python 3.12") 2 | 3 | import x 4 | 5 | type Point = tuple[x, float] 6 | -------------------------------------------------------------------------------- /tests/cases/refactor/typing/variable.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List 2 | 3 | 4 | test: "List[Dict]" 5 | -------------------------------------------------------------------------------- /tests/cases/refactor/unused/comment.py: -------------------------------------------------------------------------------- 1 | # This is not unused import, but it is unused import according to unimport. 2 | # CASE 1 3 | from codeop import compile_command 4 | 5 | 6 | compile_command 7 | -------------------------------------------------------------------------------- /tests/cases/refactor/unused/do_not_remove_augmented_imports.py: -------------------------------------------------------------------------------- 1 | from django.conf.global_settings import AUTHENTICATION_BACKENDS 2 | 3 | 4 | AUTHENTICATION_BACKENDS += ('foo.bar.baz.EmailBackend',) 5 | -------------------------------------------------------------------------------- /tests/cases/refactor/unused/future_from_import.py: -------------------------------------------------------------------------------- 1 | from __future__ import ( 2 | absolute_import, division, print_function, unicode_literals 3 | ) 4 | -------------------------------------------------------------------------------- /tests/cases/refactor/unused/future_import.py: -------------------------------------------------------------------------------- 1 | import __future__ 2 | 3 | 4 | __future__.absolute_import 5 | -------------------------------------------------------------------------------- /tests/cases/refactor/unused/get_star_imp_none.py: -------------------------------------------------------------------------------- 1 | try: 2 | from x import * 3 | except ImportError: 4 | pass 5 | -------------------------------------------------------------------------------- /tests/cases/refactor/unused/inside_function_unused.py: -------------------------------------------------------------------------------- 1 | def foo(): 2 | 3 | try: 4 | import t 5 | print(t) 6 | except ImportError as exception: 7 | pass 8 | 9 | return math.pi 10 | -------------------------------------------------------------------------------- /tests/cases/refactor/unused/local_import.py: -------------------------------------------------------------------------------- 1 | from .x import hakan 2 | from ..z import b 3 | from ...t import q 4 | 5 | 6 | hakan 7 | b 8 | q() 9 | -------------------------------------------------------------------------------- /tests/cases/refactor/unused/multiple_imports.py: -------------------------------------------------------------------------------- 1 | some() 2 | calls() 3 | # and comments 4 | def maybe_functions(): 5 | after() 6 | -------------------------------------------------------------------------------- /tests/cases/refactor/unused/remove_unused_from_imports.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | 4 | print(f'The date is {datetime.datetime.now()}.') 5 | -------------------------------------------------------------------------------- /tests/cases/refactor/unused/star.py: -------------------------------------------------------------------------------- 1 | from os import walk 2 | 3 | 4 | walk 5 | -------------------------------------------------------------------------------- /tests/cases/refactor/unused/startswith_name.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | xxx = "test" 4 | -------------------------------------------------------------------------------- /tests/cases/source/all/module_from_all_set_the_name.py: -------------------------------------------------------------------------------- 1 | from typing import * 2 | 3 | __all__ = ( 4 | "Test", 5 | "Test2" 6 | ) 7 | 8 | Any 9 | -------------------------------------------------------------------------------- /tests/cases/source/as_import/all_unused_all_cases.py: -------------------------------------------------------------------------------- 1 | from x import y as z 2 | import x 3 | from t import s as ss 4 | import le as x 5 | -------------------------------------------------------------------------------- /tests/cases/source/as_import/dotted_module_name_simple.py: -------------------------------------------------------------------------------- 1 | import a.b as c 2 | -------------------------------------------------------------------------------- /tests/cases/source/as_import/multiple_from_as_import.py: -------------------------------------------------------------------------------- 1 | from f import a as c, l as k, i as ii 2 | from fo import (bar, i, x as z) 3 | -------------------------------------------------------------------------------- /tests/cases/source/as_import/multiple_import_name_as_import.py: -------------------------------------------------------------------------------- 1 | import a as c, l as k, i as ii 2 | import bar, i, x as z 3 | -------------------------------------------------------------------------------- /tests/cases/source/as_import/multiple_import_name_as_import_duplicate.py: -------------------------------------------------------------------------------- 1 | import a as c, l as k, i as ii 2 | import bar, i, x as z 3 | import bar, i, x as z 4 | 5 | print(bar) 6 | -------------------------------------------------------------------------------- /tests/cases/source/as_import/used_all_cases.py: -------------------------------------------------------------------------------- 1 | from x import y as z 2 | import x 3 | from t import s as ss 4 | import bar, i, x as z 5 | import le as x 6 | 7 | print(x) 8 | -------------------------------------------------------------------------------- /tests/cases/source/dotted/dotted_module_name_simple.py: -------------------------------------------------------------------------------- 1 | import a.b 2 | -------------------------------------------------------------------------------- /tests/cases/source/duplicate_import/different_duplicate_unused.py: -------------------------------------------------------------------------------- 1 | from x import z 2 | from y import z 3 | -------------------------------------------------------------------------------- /tests/cases/source/duplicate_import/different_duplicate_used.py: -------------------------------------------------------------------------------- 1 | from x import z 2 | from y import z 3 | 4 | print(z) 5 | -------------------------------------------------------------------------------- /tests/cases/source/duplicate_import/full_unused.py: -------------------------------------------------------------------------------- 1 | from x import y 2 | from x import y 3 | from t import x 4 | import re 5 | import ll 6 | import ll 7 | from c import e 8 | import e 9 | -------------------------------------------------------------------------------- /tests/cases/source/duplicate_import/import_in_function.py: -------------------------------------------------------------------------------- 1 | import t 2 | from l import t 3 | from x import y, z, t 4 | 5 | 6 | def function(f=t): 7 | import x 8 | return f 9 | 10 | 11 | from i import ii, t 12 | 13 | print(t) 14 | -------------------------------------------------------------------------------- /tests/cases/source/duplicate_import/import_in_function_used_two_different.py: -------------------------------------------------------------------------------- 1 | import t 2 | 3 | print(t) 4 | 5 | 6 | from l import t 7 | from x import y, z, t 8 | 9 | def function(f=t): 10 | import x 11 | 12 | return f 13 | 14 | from i import ii, t 15 | 16 | print(t) 17 | -------------------------------------------------------------------------------- /tests/cases/source/duplicate_import/multi_duplicate.py: -------------------------------------------------------------------------------- 1 | from x import y, z, t 2 | import t 3 | from l import t 4 | -------------------------------------------------------------------------------- /tests/cases/source/duplicate_import/multi_duplicate_one_used.py: -------------------------------------------------------------------------------- 1 | from x import y, z, t 2 | import t 3 | from l import t 4 | 5 | print(t) 6 | -------------------------------------------------------------------------------- /tests/cases/source/duplicate_import/one_used.py: -------------------------------------------------------------------------------- 1 | from x import y 2 | from x import y 3 | from t import x 4 | import re 5 | import ll 6 | import ll 7 | from c import e 8 | import e 9 | from pathlib import Path 10 | from pathlib import Path 11 | 12 | 13 | p = Path() 14 | -------------------------------------------------------------------------------- /tests/cases/source/duplicate_import/one_used_bottom_multi_duplicate.py: -------------------------------------------------------------------------------- 1 | import t 2 | from l import t 3 | from x import y, z, t 4 | 5 | 6 | print(t) 7 | -------------------------------------------------------------------------------- /tests/cases/source/duplicate_import/same_line.py: -------------------------------------------------------------------------------- 1 | import x, x, yt 2 | import e, y, e 3 | from z import u, u 4 | from bb import c, d, c, c 5 | import ff, tt, ff, ff, tt 6 | from ee import ( 7 | ll, 8 | el, 9 | ll, 10 | el, 11 | tl, 12 | tl, 13 | ) 14 | import iss as si, si 15 | from gu import ug,\ 16 | ug 17 | 18 | x, e, u, c, ff, tt, ll, el, tl, si, ug, yt 19 | -------------------------------------------------------------------------------- /tests/cases/source/duplicate_import/three_used.py: -------------------------------------------------------------------------------- 1 | from x import y 2 | from x import y 3 | from t import x 4 | import re 5 | import ll 6 | import ll 7 | from c import e 8 | import e 9 | from pathlib import Path 10 | from pathlib import Path 11 | 12 | 13 | p = Path() 14 | print(ll) 15 | 16 | 17 | def function(e=e): pass 18 | -------------------------------------------------------------------------------- /tests/cases/source/duplicate_import/two_multi_duplicate_one_used.py: -------------------------------------------------------------------------------- 1 | import t 2 | from l import t 3 | from x import y, z, t 4 | from i import ii, t 5 | 6 | print(t) 7 | -------------------------------------------------------------------------------- /tests/cases/source/duplicate_import/two_used.py: -------------------------------------------------------------------------------- 1 | from x import y 2 | from x import y 3 | from t import x 4 | import re 5 | import ll 6 | import ll 7 | from c import e 8 | import e 9 | from pathlib import Path 10 | from pathlib import Path 11 | 12 | p = Path() 13 | print(ll) 14 | -------------------------------------------------------------------------------- /tests/cases/source/error/as_import.py: -------------------------------------------------------------------------------- 1 | try: 2 | import x 3 | except ImportError as err: 4 | print('try this code `pip install x`') 5 | -------------------------------------------------------------------------------- /tests/cases/source/error/bad_syntax.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | € = 2 3 | -------------------------------------------------------------------------------- /tests/cases/source/error/import_else_another.py: -------------------------------------------------------------------------------- 1 | try: 2 | import x 3 | except ImportError: 4 | import y as x 5 | 6 | print(x) 7 | -------------------------------------------------------------------------------- /tests/cases/source/error/syntax_error.py: -------------------------------------------------------------------------------- 1 | a :? = 0 -------------------------------------------------------------------------------- /tests/cases/source/error/try_except.py: -------------------------------------------------------------------------------- 1 | try: 2 | import x 3 | except Exception: 4 | pass 5 | 6 | try: 7 | import x 8 | except BaseException: 9 | pass 10 | 11 | try: 12 | import x 13 | except OSError: 14 | pass 15 | 16 | try: 17 | import x 18 | except (OSError, AttributeError): 19 | pass 20 | -------------------------------------------------------------------------------- /tests/cases/source/error/tuple.py: -------------------------------------------------------------------------------- 1 | try: 2 | import xa 3 | except (A, ImportError): 4 | pass 5 | -------------------------------------------------------------------------------- /tests/cases/source/if_dispatch/case_1.py: -------------------------------------------------------------------------------- 1 | if True: 2 | import x 3 | else: 4 | import y as x 5 | 6 | x 7 | -------------------------------------------------------------------------------- /tests/cases/source/if_dispatch/case_2.py: -------------------------------------------------------------------------------- 1 | if True: 2 | from x import y 3 | else: 4 | import y 5 | 6 | y 7 | -------------------------------------------------------------------------------- /tests/cases/source/if_dispatch/case_3.py: -------------------------------------------------------------------------------- 1 | if True: 2 | from x import y 3 | else: 4 | from z import a as y 5 | 6 | y 7 | -------------------------------------------------------------------------------- /tests/cases/source/if_dispatch/case_4.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if sys.version_info >= (3, 8): 4 | from typing import Literal 5 | else: 6 | from typing_extensions import Literal 7 | 8 | 9 | Literal 10 | -------------------------------------------------------------------------------- /tests/cases/source/if_dispatch/case_5.py: -------------------------------------------------------------------------------- 1 | if condition: 2 | from x import y, i as ii 3 | else: 4 | import y as yy, r as ii 5 | 6 | ii 7 | -------------------------------------------------------------------------------- /tests/cases/source/if_dispatch/case_6.py: -------------------------------------------------------------------------------- 1 | if condition: 2 | from x import y, i as ii 3 | elif condition_2: 4 | import ii, x as tt 5 | else: 6 | import y as yy, r as ii 7 | 8 | ii 9 | -------------------------------------------------------------------------------- /tests/cases/source/scope/global_import.py: -------------------------------------------------------------------------------- 1 | import x 2 | 3 | def f(): 4 | import x 5 | 6 | x 7 | 8 | -------------------------------------------------------------------------------- /tests/cases/source/scope/nonlocal_import.py: -------------------------------------------------------------------------------- 1 | import x 2 | 3 | def func(): 4 | import x 5 | 6 | def inner(): 7 | import x 8 | x 9 | 10 | -------------------------------------------------------------------------------- /tests/cases/source/scope/nonlocal_import_2.py: -------------------------------------------------------------------------------- 1 | import x 2 | 3 | def func(): 4 | import x 5 | 6 | def inner(): 7 | import x 8 | x 9 | 10 | x 11 | 12 | -------------------------------------------------------------------------------- /tests/cases/source/scope/nonlocal_import_3.py: -------------------------------------------------------------------------------- 1 | import x 2 | 3 | def func(): 4 | import x 5 | 6 | def inner(): 7 | import x 8 | x 9 | 10 | x 11 | 12 | x 13 | 14 | -------------------------------------------------------------------------------- /tests/cases/source/scope/nonlocal_import_5.py: -------------------------------------------------------------------------------- 1 | import x 2 | 3 | def func(): 4 | def inner(): 5 | x 6 | 7 | -------------------------------------------------------------------------------- /tests/cases/source/star_import/2.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | Callable, 3 | Iterable, 4 | Iterator, 5 | List, 6 | Optional, 7 | Text, 8 | Tuple, 9 | Pattern, 10 | Union, 11 | cast, 12 | ) 13 | from lib2to3.pgen2.token import * 14 | from lib2to3.pgen2.grammar import * 15 | 16 | 17 | print(Grammar) 18 | -------------------------------------------------------------------------------- /tests/cases/source/star_import/get_source_from_importable_names.py: -------------------------------------------------------------------------------- 1 | from libcst.metadata import * 2 | 3 | 4 | CodeRange, PositionProvider 5 | -------------------------------------------------------------------------------- /tests/cases/source/star_import/star_imports.py: -------------------------------------------------------------------------------- 1 | from os import * 2 | from x import y 3 | from re import * 4 | from t.s.d import * 5 | from lib2to3.pgen2.token import * 6 | from lib2to3.fixer_util import * 7 | 8 | 9 | print(match) 10 | print(search) 11 | print(NAME) 12 | -------------------------------------------------------------------------------- /tests/cases/source/star_import/two_suggestions.py: -------------------------------------------------------------------------------- 1 | from time import * 2 | from os import * 3 | 4 | 5 | time() # Function from time module. 6 | path.join() 7 | -------------------------------------------------------------------------------- /tests/cases/source/statement/match_statement.py: -------------------------------------------------------------------------------- 1 | # pytest.mark.skipif(not PY310_PLUS, reason: "this statement is supported above python 3.10") 2 | 3 | # https://github.com/hakancelikdev/unimport/issues/291 4 | 5 | match sort_by: 6 | case 'date': sort_by = ' updated DESC,' 7 | case _: sort_by = '' 8 | -------------------------------------------------------------------------------- /tests/cases/source/style/1_vertical.py: -------------------------------------------------------------------------------- 1 | from x import ( 2 | q, 3 | e, 4 | r, 5 | t 6 | ) 7 | import y 8 | import u 9 | 10 | 11 | y, q, e, r, t 12 | -------------------------------------------------------------------------------- /tests/cases/source/style/2_1_vertical.py: -------------------------------------------------------------------------------- 1 | from x import ( 2 | q, 3 | e, 4 | r, 5 | t 6 | ) 7 | import y 8 | import u 9 | 10 | 11 | y, q, e, t 12 | -------------------------------------------------------------------------------- /tests/cases/source/style/3_1_vertical.py: -------------------------------------------------------------------------------- 1 | from x import ( 2 | q, 3 | e, 4 | r, 5 | t, 6 | ) 7 | import y 8 | import u 9 | 10 | 11 | y, q, e, r 12 | -------------------------------------------------------------------------------- /tests/cases/source/style/4_1_paren_horizontal.py: -------------------------------------------------------------------------------- 1 | from x import (q, e, r, t) 2 | import y 3 | import u 4 | 5 | 6 | y, q, e, r 7 | -------------------------------------------------------------------------------- /tests/cases/source/style/vertical_dont_add_extra_line.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import ( 3 | List, 4 | ) 5 | 6 | 7 | test_list: List[str] = ["spam", "eggs"] 8 | -------------------------------------------------------------------------------- /tests/cases/source/type_variable/type_assing_cast.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | if TYPE_CHECKING: 4 | from PyQt5 import QtWebKit 5 | 6 | 7 | HistoryType = cast('QtWebKit.QWebHistory', return_value) 8 | 9 | -------------------------------------------------------------------------------- /tests/cases/source/type_variable/type_assing_list.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, List 2 | 3 | if TYPE_CHECKING: 4 | from PyQt5.QtWebKit import QWebHistory 5 | 6 | 7 | HistoryType = List['QWebHistory'] 8 | 9 | -------------------------------------------------------------------------------- /tests/cases/source/type_variable/type_assing_union.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Union 2 | 3 | if TYPE_CHECKING: 4 | from PyQt5 import QtWebEngineWidgets, QtWebKit 5 | 6 | 7 | HistoryType = Union['QtWebEngineWidgets.QWebEngineHistory', 'QtWebKit.QWebHistory'] 8 | 9 | -------------------------------------------------------------------------------- /tests/cases/source/typing/function_arg.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List 2 | 3 | 4 | def test(arg:"List[Dict]") -> None: 5 | pass 6 | -------------------------------------------------------------------------------- /tests/cases/source/typing/function_return.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List 2 | 3 | 4 | def test(arg: list) -> "List[Dict]": 5 | pass 6 | -------------------------------------------------------------------------------- /tests/cases/source/typing/function_str_arg.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Literal 2 | 3 | 4 | def test(item, when: "Literal['Dict']") -> None: 5 | pass 6 | -------------------------------------------------------------------------------- /tests/cases/source/typing/type_comment_funcdef.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | def x(y): 5 | # type: (str) -> List[str] 6 | pass 7 | -------------------------------------------------------------------------------- /tests/cases/source/typing/type_comment_params.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | def x( 5 | f, # type:List, 6 | r # type:str 7 | ): 8 | pass 9 | -------------------------------------------------------------------------------- /tests/cases/source/typing/type_comments.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | from typing import Tuple 3 | from typing import Union 4 | 5 | 6 | def function(a, b): 7 | # type: (Any, str) -> Union[Tuple[None, None], Tuple[str, str]] 8 | pass 9 | -------------------------------------------------------------------------------- /tests/cases/source/typing/type_comments_with_variable.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | test_variable = [2] # type: List[int] 5 | -------------------------------------------------------------------------------- /tests/cases/source/typing/type_parameter_syntax.py: -------------------------------------------------------------------------------- 1 | # pytest.mark.skipif(not PY312_PLUS, reason: "type parameter syntax is supported above python 3.12") 2 | 3 | import x 4 | import y 5 | 6 | type Point = tuple[x, float] 7 | -------------------------------------------------------------------------------- /tests/cases/source/typing/variable.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List 2 | 3 | 4 | test: "List[Dict]" 5 | -------------------------------------------------------------------------------- /tests/cases/source/unused/comment.py: -------------------------------------------------------------------------------- 1 | # This is not unused import, but it is unused import according to unimport. 2 | # CASE 1 3 | from codeop import compile_command 4 | 5 | 6 | compile_command 7 | -------------------------------------------------------------------------------- /tests/cases/source/unused/do_not_remove_augmented_imports.py: -------------------------------------------------------------------------------- 1 | from django.conf.global_settings import AUTHENTICATION_BACKENDS, TEMPLATE_CONTEXT_PROCESSORS 2 | 3 | 4 | AUTHENTICATION_BACKENDS += ('foo.bar.baz.EmailBackend',) 5 | -------------------------------------------------------------------------------- /tests/cases/source/unused/future_from_import.py: -------------------------------------------------------------------------------- 1 | from __future__ import ( 2 | absolute_import, division, print_function, unicode_literals 3 | ) 4 | -------------------------------------------------------------------------------- /tests/cases/source/unused/future_import.py: -------------------------------------------------------------------------------- 1 | import __future__ 2 | 3 | 4 | __future__.absolute_import 5 | -------------------------------------------------------------------------------- /tests/cases/source/unused/get_star_imp_none.py: -------------------------------------------------------------------------------- 1 | try: 2 | from x import * 3 | except ImportError: 4 | pass 5 | 6 | 7 | import t 8 | -------------------------------------------------------------------------------- /tests/cases/source/unused/inside_function_unused.py: -------------------------------------------------------------------------------- 1 | def foo(): 2 | from x import y, z 3 | 4 | try: 5 | import t 6 | print(t) 7 | except ImportError as exception: 8 | pass 9 | 10 | return math.pi 11 | -------------------------------------------------------------------------------- /tests/cases/source/unused/local_import.py: -------------------------------------------------------------------------------- 1 | from .x import y 2 | from ..z import t 3 | from ...t import a 4 | from .x import y, hakan 5 | from ..z import u, b 6 | from ...t import z, q 7 | 8 | 9 | hakan 10 | b 11 | q() 12 | -------------------------------------------------------------------------------- /tests/cases/source/unused/remove_unused_from_imports.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from dateutil.relativedelta import relativedelta 3 | 4 | 5 | print(f'The date is {datetime.datetime.now()}.') 6 | -------------------------------------------------------------------------------- /tests/cases/source/unused/star.py: -------------------------------------------------------------------------------- 1 | from os import * 2 | 3 | 4 | walk 5 | -------------------------------------------------------------------------------- /tests/cases/source/unused/startswith_name.py: -------------------------------------------------------------------------------- 1 | import xx 2 | 3 | 4 | xxx = "test" 5 | -------------------------------------------------------------------------------- /tests/cases/test_cases.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import importlib 3 | import re 4 | from pathlib import Path 5 | 6 | import pytest 7 | 8 | from unimport.analyzers import MainAnalyzer 9 | from unimport.constants import PY310_PLUS, PY312_PLUS # noqa using eval expression 10 | from unimport.refactor import refactor_string 11 | from unimport.statement import Import, Name 12 | from unimport.utils import list_paths 13 | 14 | 15 | @pytest.mark.parametrize("path", list(list_paths(Path("tests/cases/source")))) 16 | def test_cases(path: Path, logger): 17 | refactor_path = Path("tests/cases/refactor") 18 | analyzer_path = Path("tests/cases/analyzer") 19 | 20 | case_path = f"{path.parent.name}/{path.name}" 21 | analyzer_path_ = analyzer_path / case_path 22 | refactor_path_ = refactor_path / case_path 23 | 24 | logger.debug(f"Source path: {path}") 25 | logger.debug(f"Analyzer path: {analyzer_path_}") 26 | logger.debug(f"Refactor path: {refactor_path_}") 27 | 28 | # analyzer tests 29 | analyzer_import_path = ".".join(analyzer_path_.parts[:-1]) 30 | analyzer_import_path += f".{path.stem}" 31 | analyzer = importlib.import_module(analyzer_import_path) 32 | 33 | source = path.read_text() 34 | skip_if = re.search(r"# pytest.mark.skipif\((?P.*), reason: (?P.*)\)", source, re.IGNORECASE) 35 | if skip_if and (condition := skip_if.group("condition")) and condition in ("not PY310_PLUS", "not PY312_PLUS"): 36 | reason = skip_if.group("reason") 37 | pytest.mark.skipif(False, reason, allow_module_level=True) 38 | 39 | with contextlib.suppress(SyntaxError): 40 | with MainAnalyzer(source=source, include_star_import=True): 41 | assert Name.names == analyzer.NAMES 42 | assert Import.imports == analyzer.IMPORTS 43 | assert list(Import.get_unused_imports()) == analyzer.UNUSED_IMPORTS 44 | 45 | # refactor tests 46 | refactor = refactor_string(source, analyzer.UNUSED_IMPORTS) 47 | assert refactor == refactor_path_.read_text() 48 | -------------------------------------------------------------------------------- /tests/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakancelikdev/unimport/563fb2610888cb903ad4614eaee2e8fe0152535f/tests/commands/__init__.py -------------------------------------------------------------------------------- /tests/commands/test_check.py: -------------------------------------------------------------------------------- 1 | import io 2 | import textwrap 3 | from contextlib import redirect_stdout 4 | from pathlib import Path 5 | 6 | import pytest 7 | 8 | from unimport.commands import check 9 | from unimport.statement import Import, ImportFrom 10 | 11 | 12 | @pytest.mark.parametrize("use_color, stdout", [[True, ""], [False, ""]]) 13 | def test_empty_print_check(use_color: bool, stdout: str) -> None: 14 | with redirect_stdout(io.StringIO()) as f: 15 | check(Path("tests/commands/test_check.py"), [], use_color) 16 | assert f.getvalue() == stdout 17 | 18 | 19 | @pytest.mark.parametrize( 20 | "use_color, stdout", 21 | [ 22 | [ 23 | True, 24 | "\x1b[33mz\x1b[0m at \x1b[32mtests/commands/test_check.py\x1b[0m:\x1b[32m1\x1b[0m\n", 25 | ], 26 | [False, "z at tests/commands/test_check.py:1\n"], 27 | ], 28 | ) 29 | def test_import_print_check(use_color: bool, stdout: str) -> None: 30 | with redirect_stdout(io.StringIO()) as f: 31 | check( 32 | Path("tests/commands/test_check.py"), 33 | [ 34 | ImportFrom( 35 | lineno=1, 36 | column=1, 37 | name="z", 38 | package="x", 39 | star=False, 40 | suggestions=[], 41 | ) 42 | ], 43 | use_color, 44 | ) 45 | assert f.getvalue() == stdout 46 | 47 | 48 | @pytest.mark.parametrize( 49 | "use_color, stdout", 50 | [ 51 | [ 52 | True, 53 | textwrap.dedent( 54 | """\ 55 | \x1b[33mss\x1b[0m at \x1b[32mtests/commands/test_check.py\x1b[0m:\x1b[32m3\x1b[0m 56 | \x1b[33mx\x1b[0m at \x1b[32mtests/commands/test_check.py\x1b[0m:\x1b[32m4\x1b[0m 57 | """ 58 | ), 59 | ], 60 | [ 61 | False, 62 | textwrap.dedent( 63 | """\ 64 | ss at tests/commands/test_check.py:3 65 | x at tests/commands/test_check.py:4 66 | """ 67 | ), 68 | ], 69 | ], 70 | ) 71 | def test_import_and_fromimport_print_check(use_color: bool, stdout: str) -> None: 72 | with redirect_stdout(io.StringIO()) as f: 73 | check( 74 | Path("tests/commands/test_check.py"), 75 | [ 76 | ImportFrom( 77 | lineno=3, 78 | column=1, 79 | name="ss", 80 | package="t", 81 | star=False, 82 | suggestions=[], 83 | ), 84 | Import(lineno=4, column=1, name="x", package="le"), 85 | ], 86 | use_color, 87 | ) 88 | assert f.getvalue() == stdout 89 | -------------------------------------------------------------------------------- /tests/commands/test_diff.py: -------------------------------------------------------------------------------- 1 | import io 2 | import textwrap 3 | from contextlib import redirect_stdout 4 | from pathlib import Path 5 | 6 | import pytest 7 | 8 | from unimport.commands import diff 9 | 10 | 11 | @pytest.mark.parametrize("use_color", [True, False]) 12 | def test_diff_not_exits_diff(use_color): 13 | with redirect_stdout(io.StringIO()) as f: 14 | exists_diff = diff(Path("example.py"), "source", "source", use_color) 15 | 16 | assert f.getvalue() == "" 17 | assert exists_diff is False 18 | 19 | 20 | @pytest.mark.parametrize( 21 | ("use_color", "stdout"), 22 | [ 23 | [ 24 | True, 25 | textwrap.dedent( 26 | """\ 27 | \x1b[1;37m--- example.py 28 | \x1b[0m 29 | \x1b[1;37m+++ 30 | \x1b[0m 31 | \x1b[36m@@ -1 +1 @@ 32 | \x1b[0m 33 | \x1b[31m-source\x1b[0m 34 | \x1b[32m+refactor_result\x1b[0m 35 | """ 36 | ), 37 | ], 38 | [ 39 | False, 40 | textwrap.dedent( 41 | """\ 42 | --- example.py 43 | 44 | +++ 45 | 46 | @@ -1 +1 @@ 47 | 48 | -source 49 | +refactor_result 50 | """ 51 | ), 52 | ], 53 | ], 54 | ) 55 | def test_diff_exits_diff(use_color, stdout): 56 | with redirect_stdout(io.StringIO()) as f: 57 | exists_diff = diff(Path("example.py"), "source", "refactor_result", use_color) 58 | 59 | assert f.getvalue() == stdout 60 | assert exists_diff is True 61 | -------------------------------------------------------------------------------- /tests/commands/test_options.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | import pytest 4 | 5 | from unimport.commands import options 6 | 7 | 8 | @pytest.fixture() 9 | def parser() -> argparse.ArgumentParser: 10 | return argparse.ArgumentParser() 11 | 12 | 13 | def test_add_sources_option(parser: argparse.ArgumentParser): 14 | from pathlib import Path 15 | 16 | options.add_sources_option(parser) 17 | 18 | assert vars(parser.parse_args([])) == dict(sources=[Path(".")]) 19 | 20 | 21 | def test_add_check_option(parser: argparse.ArgumentParser): 22 | options.add_check_option(parser) 23 | 24 | assert vars(parser.parse_args([])) == dict(check=False) 25 | assert vars(parser.parse_args(["--check"])) == dict(check=True) 26 | 27 | 28 | def test_add_config_option(parser: argparse.ArgumentParser): 29 | from pathlib import Path 30 | 31 | options.add_config_option(parser) 32 | 33 | assert vars(parser.parse_args([])) == dict(config=None) 34 | assert vars(parser.parse_args(["-c", "config.toml"])) == dict(config=Path("config.toml")) 35 | assert vars(parser.parse_args(["--config", "config.toml"])) == dict(config=Path("config.toml")) 36 | 37 | 38 | def test_add_include_option(parser: argparse.ArgumentParser): 39 | options.add_include_option(parser) 40 | 41 | assert vars(parser.parse_args([])) == dict(include="\\.(py)$") 42 | assert vars(parser.parse_args(["--include", "tests|datasets|t.py"])) == dict(include="tests|datasets|t.py") 43 | 44 | 45 | def test_add_exclude_option(parser: argparse.ArgumentParser): 46 | options.add_exclude_option(parser) 47 | 48 | assert vars(parser.parse_args([])) == dict(exclude="^$") 49 | assert vars(parser.parse_args(["--exclude", "tests|datasets|t.py"])) == dict(exclude="tests|datasets|t.py") 50 | 51 | 52 | def test_add_gitignore_option(parser: argparse.ArgumentParser): 53 | options.add_gitignore_option(parser) 54 | 55 | assert vars(parser.parse_args([])) == dict(gitignore=False) 56 | assert vars(parser.parse_args(["--gitignore"])) == dict(gitignore=True) 57 | 58 | 59 | def test_add_ignore_init_option(parser: argparse.ArgumentParser): 60 | options.add_ignore_init_option(parser) 61 | 62 | assert vars(parser.parse_args([])) == dict(ignore_init=False) 63 | assert vars(parser.parse_args(["--ignore-init"])) == dict(ignore_init=True) 64 | 65 | 66 | def test_add_include_star_import_option(parser: argparse.ArgumentParser): 67 | options.add_include_star_import_option(parser) 68 | 69 | assert vars(parser.parse_args([])) == dict(include_star_import=False) 70 | assert vars(parser.parse_args(["--include-star-import"])) == dict(include_star_import=True) 71 | 72 | 73 | def test_add_diff_option(parser: argparse.ArgumentParser): 74 | options.add_diff_option(parser) 75 | 76 | assert vars(parser.parse_args([])) == dict(diff=False) 77 | assert vars(parser.parse_args(["-d"])) == dict(diff=True) 78 | assert vars(parser.parse_args(["--diff"])) == dict(diff=True) 79 | 80 | 81 | def test_add_remove_option(parser: argparse.ArgumentParser): 82 | options.add_remove_option(parser) # type: ignore 83 | 84 | assert vars(parser.parse_args([])) == dict(remove=False) 85 | assert vars(parser.parse_args(["-r"])) == dict(remove=True) 86 | assert vars(parser.parse_args(["--remove"])) == dict(remove=True) 87 | 88 | 89 | def test_add_permission_option(parser: argparse.ArgumentParser): 90 | options.add_permission_option(parser) # type: ignore 91 | 92 | assert vars(parser.parse_args([])) == dict(permission=False) 93 | assert vars(parser.parse_args(["-p"])) == dict(permission=True) 94 | assert vars(parser.parse_args(["--permission"])) == dict(permission=True) 95 | -------------------------------------------------------------------------------- /tests/commands/test_parser.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from pathlib import Path 3 | 4 | import pytest 5 | 6 | 7 | @pytest.fixture(scope="module") 8 | def parser() -> argparse.ArgumentParser: 9 | from unimport.commands import generate_parser 10 | 11 | return generate_parser() 12 | 13 | 14 | def test_generate_parser_type(parser: argparse.ArgumentParser): 15 | assert isinstance(parser, argparse.ArgumentParser) 16 | 17 | 18 | def test_generate_parser_argument_parser(parser: argparse.ArgumentParser): 19 | assert parser.prog == "unimport" 20 | assert parser.usage is None 21 | assert parser.description == "A linter, formatter for finding and removing unused import statements." 22 | assert parser.formatter_class == argparse.HelpFormatter 23 | assert parser.conflict_handler == "error" 24 | assert parser.add_help is True 25 | 26 | 27 | def test_generate_parser_empty_parse_args(parser: argparse.ArgumentParser): 28 | assert vars(parser.parse_args(["--disable-auto-discovery-config", "--color", "never"])) == dict( 29 | check=False, 30 | color="never", 31 | config=None, 32 | diff=False, 33 | disable_auto_discovery_config=True, 34 | exclude="^$", 35 | gitignore=False, 36 | ignore_init=False, 37 | include="\\.(py)$", 38 | include_star_import=False, 39 | permission=False, 40 | remove=False, 41 | sources=[Path(".")], 42 | ) 43 | -------------------------------------------------------------------------------- /tests/commands/test_permission.py: -------------------------------------------------------------------------------- 1 | # TODO Add related tests 2 | -------------------------------------------------------------------------------- /tests/commands/test_remove.py: -------------------------------------------------------------------------------- 1 | # TODO Add related tests 2 | -------------------------------------------------------------------------------- /tests/config/configs/like-commands/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.unimport] 2 | ignore-init = true 3 | include-star-import = true 4 | -------------------------------------------------------------------------------- /tests/config/configs/like-commands/setup.cfg: -------------------------------------------------------------------------------- 1 | [unimport] 2 | ignore-init = true 3 | include-star-import = true 4 | -------------------------------------------------------------------------------- /tests/config/configs/mistyped/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.unimport] 2 | there-is-no-such-config-key = false 3 | -------------------------------------------------------------------------------- /tests/config/configs/mistyped/setup.cfg: -------------------------------------------------------------------------------- 1 | [unimport] 2 | there-is-no-such-config-key = false 3 | -------------------------------------------------------------------------------- /tests/config/configs/no_unimport/pyproject.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakancelikdev/unimport/563fb2610888cb903ad4614eaee2e8fe0152535f/tests/config/configs/no_unimport/pyproject.toml -------------------------------------------------------------------------------- /tests/config/configs/no_unimport/setup.cfg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakancelikdev/unimport/563fb2610888cb903ad4614eaee2e8fe0152535f/tests/config/configs/no_unimport/setup.cfg -------------------------------------------------------------------------------- /tests/config/configs/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.unimport] 2 | sources = ["path1", "path2"] 3 | exclude = '__init__.py|tests/' 4 | include = 'test|test2|tests.py' 5 | gitignore = false 6 | remove = false 7 | check = false 8 | diff = false 9 | ignore_init = false 10 | -------------------------------------------------------------------------------- /tests/config/configs/setup.cfg: -------------------------------------------------------------------------------- 1 | [unimport] 2 | sources = ["path1", "path2"] 3 | exclude = __init__.py|tests/ 4 | include = test|test2|tests.py 5 | gitignore = false 6 | remove = false 7 | check = false 8 | diff = false 9 | ignore_init = false 10 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | 4 | import pytest 5 | 6 | 7 | @pytest.fixture(scope="session") 8 | def non_native_linesep() -> str: 9 | """Return an end of line character not native to the current platform.""" 10 | return "\r\n" if os.linesep == "\n" else "\n" 11 | 12 | 13 | @pytest.fixture(scope="session") 14 | def logger(): 15 | import logging 16 | 17 | logger = logging.getLogger("unimport/tests") 18 | logger.setLevel(level=logging.DEBUG) 19 | 20 | return logger 21 | 22 | 23 | def pytest_configure(config): 24 | config.addinivalue_line("markers", "change_directory(path:str): mark test to change working directory") 25 | 26 | 27 | def pytest_runtest_setup(item): 28 | for marker in item.iter_markers(name="change_directory"): 29 | item.original_cwd = Path.cwd() 30 | 31 | directory = marker.args[0] 32 | os.chdir(directory) 33 | 34 | 35 | def pytest_runtest_teardown(item, nextitem): 36 | for marker in item.iter_markers(name="change_directory"): 37 | os.chdir(item.original_cwd) 38 | -------------------------------------------------------------------------------- /tests/test_color.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import pytest 4 | 5 | from unimport.color import TERMINAL_SUPPORT_COLOR, Color, paint 6 | 7 | 8 | @pytest.mark.skipif(sys.platform != "win32", reason="Requires Windows") 9 | def test_terminal_support_color_on_win(): 10 | from unimport.color import _enable 11 | 12 | try: 13 | _enable() 14 | except OSError: 15 | assert TERMINAL_SUPPORT_COLOR is False 16 | else: 17 | assert TERMINAL_SUPPORT_COLOR is True 18 | 19 | 20 | @pytest.mark.skipif(sys.platform == "win32", reason="Does not run on Windows") 21 | def test_terminal_support_color(): 22 | assert TERMINAL_SUPPORT_COLOR is True 23 | 24 | 25 | def test_red_paint(): 26 | text = "test text" 27 | 28 | action_text = paint(text, Color.RED) 29 | assert Color.RED + text + Color.RESET == action_text 30 | 31 | 32 | def test_paint_use_color_false(): 33 | text = "test text" 34 | 35 | action_text = paint(text, Color.RED, False) 36 | assert text == action_text 37 | 38 | 39 | def test_use_color_setting_true(): 40 | text = "test text" 41 | 42 | action_text = paint(text, Color.RED, True) 43 | assert Color.RED + text + Color.RESET == action_text 44 | -------------------------------------------------------------------------------- /tests/test_enum.py: -------------------------------------------------------------------------------- 1 | from unimport.enums import Emoji 2 | 3 | 4 | def test_emoji(): 5 | assert Emoji.STAR == "🤩" 6 | assert Emoji.PARTYING_FACE == "🥳" 7 | -------------------------------------------------------------------------------- /tests/test_linesep.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | import unimport.utils 6 | from tests.utils import reopenable_temp_file 7 | from unimport.main import Main 8 | 9 | 10 | @pytest.fixture(scope="module") 11 | def source(): 12 | return "\n".join( 13 | [ 14 | "import os", 15 | "import sys", 16 | "", 17 | "print(sys.executable)\n", 18 | ] 19 | ) 20 | 21 | 22 | @pytest.fixture(scope="module") 23 | def result(): 24 | return "\n".join( 25 | [ 26 | "import sys", 27 | "", 28 | "print(sys.executable)\n", 29 | ] 30 | ) 31 | 32 | 33 | def test_platform_native_linesep(source, result): 34 | with reopenable_temp_file(source, newline=os.linesep) as tmp_path: 35 | Main.run(["--remove", tmp_path.as_posix()]) 36 | with open(tmp_path, encoding="utf-8") as tmp_py_file: 37 | tmp_py_file.read() 38 | assert os.linesep == tmp_py_file.newlines 39 | assert result == unimport.utils.read(tmp_path)[0] 40 | 41 | 42 | def test_platform_non_native_linesep(non_native_linesep, source, result): 43 | with reopenable_temp_file(source, newline=non_native_linesep) as tmp_path: 44 | Main.run(["--remove", tmp_path.as_posix()]) 45 | with open(tmp_path, encoding="utf-8") as tmp_py_file: 46 | tmp_py_file.read() 47 | assert non_native_linesep == tmp_py_file.newlines 48 | assert result == unimport.utils.read(tmp_path)[0] 49 | -------------------------------------------------------------------------------- /tests/test_main.py: -------------------------------------------------------------------------------- 1 | from textwrap import dedent 2 | from unittest import mock 3 | 4 | import pytest 5 | 6 | from tests.utils import reopenable_temp_file 7 | from unimport.config import Config 8 | from unimport.main import Main 9 | 10 | 11 | def test_empty_main(): 12 | main = Main(["--disable-auto-discovery-config"]) 13 | 14 | assert main.argv == ["--disable-auto-discovery-config"] 15 | assert main.config == Config(disable_auto_discovery_config=True) 16 | assert main.is_syntax_error is False 17 | assert main.is_unused_imports is False 18 | assert main.refactor_applied is False 19 | 20 | 21 | def test_main_run_under_path(): 22 | source = dedent( 23 | """\ 24 | import os 25 | 26 | """ 27 | ) 28 | with reopenable_temp_file(source) as temp_file: 29 | main = Main.run(["--disable-auto-discovery-config", "--include", temp_file.as_posix()]) 30 | 31 | assert main.argv == ["--disable-auto-discovery-config", "--include", temp_file.as_posix()] 32 | assert main.config == Config(disable_auto_discovery_config=True, include=temp_file.as_posix()) 33 | assert main.is_syntax_error is False 34 | assert main.is_unused_imports is False 35 | assert main.refactor_applied is False 36 | 37 | 38 | @pytest.mark.parametrize("command_name", ["check", "diff", "permission", "remove"]) 39 | def test_main_command(command_name, monkeypatch): 40 | def mock_command(*args, **kwargs): 41 | if not hasattr(mock_command, "call_count"): 42 | mock_command.call_count = 0 43 | 44 | mock_command.call_count += 1 45 | 46 | monkeypatch.setattr(Main, command_name, mock_command) 47 | 48 | source = dedent( 49 | """\ 50 | import os 51 | 52 | """ 53 | ) 54 | with reopenable_temp_file(source) as temp_file: 55 | Main.run([f"--{command_name}", temp_file.as_posix()]) 56 | 57 | assert mock_command.call_count == 1, f"command_name='{command_name}'" 58 | 59 | 60 | def test_exit_code(): 61 | main = Main([]) 62 | assert main.exit_code() == 0 63 | 64 | main.is_syntax_error = True 65 | assert main.exit_code() == 1 66 | 67 | # reset syntax error 68 | main.is_syntax_error = False 69 | 70 | main.is_unused_imports = True 71 | assert main.exit_code() == 1 72 | 73 | main.refactor_applied = True 74 | assert main.exit_code() == 0 75 | 76 | 77 | @mock.patch("unimport.main.Main.permission") 78 | def test_commands_in_run(mock_permission): 79 | source = dedent( 80 | """\ 81 | import os 82 | 83 | """ 84 | ) 85 | 86 | mock_permission.return_value = False 87 | 88 | assert Main(["--disable-auto-discovery-config"]).config.remove is True 89 | assert Main(["--disable-auto-discovery-config"]).config.permission is False 90 | 91 | with reopenable_temp_file(source) as temp_file: 92 | main = Main.run(["--disable-auto-discovery-config", f"--permission", temp_file.as_posix()]) 93 | 94 | assert main.config.remove is False 95 | assert main.config.permission is True 96 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import textwrap 4 | from pathlib import Path 5 | 6 | import pytest 7 | 8 | from tests.utils import refactor, reopenable_temp_file 9 | from unimport import utils 10 | from unimport.utils import action_to_bool 11 | 12 | 13 | @pytest.mark.parametrize( 14 | "path, count", 15 | [ 16 | ("tests/commands", 7), 17 | ("tests/analyzer", 0), 18 | ("tests/config", 1), 19 | ("tests/config/configs", 0), 20 | ("tests/config/configs/no_unimport", 0), 21 | ("tests/refactor", 0), 22 | ("tests/test_config.py", 1), 23 | ], 24 | ) 25 | def test_list_paths(path, count): 26 | path = Path(path) 27 | 28 | assert len(list(utils.list_paths(path))) == count 29 | 30 | 31 | @pytest.mark.skipif( 32 | sys.platform == "win32", 33 | reason="Patspec version 0.10.1 and above are not supported on Windows", 34 | ) 35 | def test_list_paths_with_gitignore(): 36 | gitignore = textwrap.dedent( 37 | """\ 38 | a 39 | b 40 | spam/** 41 | **/api/ 42 | **/tests 43 | **/ 44 | """ 45 | ) 46 | with reopenable_temp_file(gitignore) as gitignore_path: 47 | gitignore_patterns = utils.get_exclude_list_from_gitignore(gitignore_path) 48 | assert list(utils.list_paths(Path("."), gitignore_patterns=gitignore_patterns)) == [] 49 | 50 | 51 | def test_bad_encoding(): 52 | # Make conflict between BOM and encoding Cookie. 53 | # https://docs.python.org/3/library/tokenize.html#tokenize.detect_encoding 54 | bad_encoding = "\ufeff\n# -*- coding: utf-32 -*-\nbad encoding" 55 | 56 | with reopenable_temp_file(bad_encoding) as tmp_path: 57 | assert refactor(tmp_path) == "" 58 | 59 | 60 | def test_refactor_file(): 61 | with reopenable_temp_file("import os") as tmp_path: 62 | assert refactor(tmp_path) == "" 63 | 64 | 65 | def test_diff_file(): 66 | with reopenable_temp_file("import os") as tmp_path: 67 | source = utils.read(tmp_path)[0] 68 | refactor_result = refactor(tmp_path) 69 | diff_file = utils.diff( 70 | source=source, 71 | refactor_result=refactor_result, 72 | fromfile=tmp_path, 73 | ) 74 | diff = ( 75 | f"--- {tmp_path.as_posix()}\n", 76 | "+++ \n", 77 | "@@ -1 +0,0 @@\n", 78 | "-import os", 79 | ) 80 | assert diff == diff_file 81 | 82 | 83 | def test_read(): 84 | source = "b�se" 85 | 86 | with reopenable_temp_file(source) as tmp_path: 87 | assert (source, "utf-8", None) == utils.read(tmp_path) 88 | 89 | 90 | def test_read_newline_native(): 91 | source = "\n".join( 92 | [ 93 | "import sys", 94 | "", 95 | "print(sys.executable)\n", 96 | ] 97 | ) 98 | 99 | with reopenable_temp_file(source, newline=os.linesep) as tmp_path: 100 | assert (source, "utf-8", os.linesep) == utils.read(tmp_path) 101 | 102 | 103 | def test_read_newline_nonnative(non_native_linesep): 104 | source = "\n".join( 105 | [ 106 | "import sys", 107 | "", 108 | "print(sys.executable)\n", 109 | ] 110 | ) 111 | 112 | with reopenable_temp_file(source, newline=non_native_linesep) as tmp_path: 113 | assert (source, "utf-8", non_native_linesep) == utils.read(tmp_path) 114 | 115 | 116 | @pytest.mark.parametrize( 117 | "is_unused_imports, is_syntax_error, refactor_applied, expected_exit_code", 118 | [ 119 | (True, True, True, 1), 120 | (True, True, False, 1), 121 | (True, False, True, 0), 122 | (True, False, False, 1), 123 | (True, False, False, 1), 124 | (False, True, False, 1), 125 | (False, False, False, 0), 126 | ], 127 | ) 128 | def test_return_exit_code( 129 | is_unused_imports, 130 | is_syntax_error, 131 | refactor_applied, 132 | expected_exit_code, 133 | ): 134 | assert ( 135 | utils.return_exit_code( 136 | is_unused_imports=is_unused_imports, 137 | is_syntax_error=is_syntax_error, 138 | refactor_applied=refactor_applied, 139 | ) 140 | == expected_exit_code 141 | ) 142 | 143 | 144 | def test_action_to_bool(): 145 | yes = ("y", "Y", "yes", "True", "t", "true", "True", "On", "on", "1") 146 | no = ("n", "no", "f", "false", "off", "0", "Off", "No", "N") 147 | 148 | for y in yes: 149 | assert action_to_bool(y) is True 150 | 151 | for n in no: 152 | assert action_to_bool(n) is False 153 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import tempfile 5 | import typing 6 | from contextlib import contextmanager 7 | from pathlib import Path 8 | 9 | from unimport import utils 10 | from unimport.analyzers import MainAnalyzer 11 | from unimport.refactor import refactor_string 12 | from unimport.statement import Import 13 | 14 | 15 | @contextmanager 16 | def reopenable_temp_file(content: str, newline: str | None = None) -> typing.Iterator[Path]: 17 | """Create a reopenable tempfile to supporting multiple reads/writes. 18 | 19 | Required to avoid file locking issues on Windows. For more 20 | information, see: 21 | https://docs.python.org/3/library/tempfile.html#tempfile.NamedTemporaryFile 22 | 23 | :param content: string content to write. 24 | :param newline: Newline character to use, if not platform default. 25 | :yields: tempfile path. 26 | """ 27 | try: 28 | with tempfile.NamedTemporaryFile( 29 | mode="w", 30 | suffix=".py", 31 | encoding="utf-8", 32 | newline=newline, 33 | delete=False, 34 | ) as tmp: 35 | tmp_path = Path(tmp.name) 36 | tmp.write(content) 37 | yield tmp_path 38 | finally: 39 | os.unlink(tmp_path) 40 | 41 | 42 | def refactor(path: Path) -> str: 43 | source = utils.read(path)[0] 44 | 45 | with MainAnalyzer(source=source) as analyzer: 46 | return refactor_string( 47 | source=analyzer.source, 48 | unused_imports=list(Import.get_unused_imports()), 49 | ) 50 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3.8, 3.9, 3.10, 3.11, 3.12, pre-commit 3 | isolated_build = true 4 | 5 | [testenv] 6 | install_command = python -m pip install {opts} {packages} 7 | extras = 8 | test 9 | commands = 10 | python -m pytest -vv --cov unimport {posargs} 11 | 12 | [testenv:pre-commit] 13 | skip_install = true 14 | deps = pre-commit 15 | commands = pre-commit run --all-files 16 | --------------------------------------------------------------------------------