|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in |
| 4 | +this repository. |
| 5 | + |
| 6 | +## Project Overview |
| 7 | + |
| 8 | +Unimport is a Python linter and formatter that detects and removes unused import |
| 9 | +statements. It uses `ast` for analysis and `libcst` for refactoring (preserving |
| 10 | +formatting). Supports Python 3.9–3.13. |
| 11 | + |
| 12 | +## Common Commands |
| 13 | + |
| 14 | +```bash |
| 15 | +# Install for development |
| 16 | +pip install -e ".[test]" |
| 17 | + |
| 18 | +# Run all tests |
| 19 | +pytest tests -x -v --disable-warnings |
| 20 | + |
| 21 | +# Run a single test file |
| 22 | +pytest tests/cases/test_cases.py -x -v |
| 23 | + |
| 24 | +# Run a single test by name |
| 25 | +pytest tests -x -v -k "test_name" |
| 26 | + |
| 27 | +# Run with coverage (used by tox) |
| 28 | +pytest -vv --cov unimport |
| 29 | + |
| 30 | +# Run the tool itself |
| 31 | +unimport [sources] |
| 32 | +``` |
| 33 | + |
| 34 | +Linting uses pre-commit (black, isort, mypy, docformatter). Line length is 120 |
| 35 | +characters. |
| 36 | + |
| 37 | +## Architecture |
| 38 | + |
| 39 | +### Pipeline |
| 40 | + |
| 41 | +The core flow is: **parse source → analyze AST → identify unused imports → refactor with |
| 42 | +libcst**. |
| 43 | + |
| 44 | +`Main.run()` in `src/unimport/main.py` orchestrates this: |
| 45 | + |
| 46 | +1. `Config` resolves settings from CLI args + config files (pyproject.toml/setup.cfg) |
| 47 | +2. `Config.get_paths()` yields Python files matching include/exclude/gitignore rules |
| 48 | +3. For each file, `MainAnalyzer.traverse()` parses and analyzes the AST |
| 49 | +4. `Import.get_unused_imports()` returns unused imports from class-level state |
| 50 | +5. `refactor_string()` uses libcst to produce the cleaned source |
| 51 | + |
| 52 | +### Statement Module (`src/unimport/statement.py`) |
| 53 | + |
| 54 | +Central data model using **class-level mutable state** (important pattern to |
| 55 | +understand): |
| 56 | + |
| 57 | +- `Import.imports` (ClassVar list) — all registered imports for current file |
| 58 | +- `Name.names` (ClassVar list) — all registered name usages for current file |
| 59 | +- `Scope.scopes` / `Scope.current_scope` (ClassVar lists) — scope tracking |
| 60 | + |
| 61 | +These are populated during analysis and cleared via `MainAnalyzer.clear()` after each |
| 62 | +file. The `MainAnalyzer` context manager handles this lifecycle. |
| 63 | + |
| 64 | +### Analyzers (`src/unimport/analyzers/`) |
| 65 | + |
| 66 | +Three AST visitors run in sequence during `MainAnalyzer.traverse()`: |
| 67 | + |
| 68 | +1. **`NameAnalyzer`** — collects all name usages (identifiers, attributes, type |
| 69 | + comments, string annotations) |
| 70 | +2. **`ImportableNameWithScopeAnalyzer`** — collects names from `__all__` definitions |
| 71 | + (for star import suggestions) |
| 72 | +3. **`ImportAnalyzer`** — collects import statements, handles `if`/`try` dispatch, |
| 73 | + generates star import suggestions |
| 74 | + |
| 75 | +### Refactoring (`src/unimport/refactor.py`) |
| 76 | + |
| 77 | +Uses `libcst` with `_RemoveUnusedImportTransformer` (a `CSTTransformer` with |
| 78 | +`PositionProvider` metadata) to surgically remove unused imports while preserving |
| 79 | +formatting. |
| 80 | + |
| 81 | +### Commands (`src/unimport/commands/`) |
| 82 | + |
| 83 | +CLI actions: `check` (report), `diff` (show changes), `remove` (apply changes), |
| 84 | +`permission` (interactive prompt). The `--remove` and `--permission` options are |
| 85 | +mutually exclusive. |
| 86 | + |
| 87 | +### Config (`src/unimport/config.py`) |
| 88 | + |
| 89 | +Auto-discovers `setup.cfg` or `pyproject.toml` (under `[tool.unimport]`). Config keys |
| 90 | +support both underscore (`include_star_import`) and hyphen (`include-star-import`) |
| 91 | +forms. |
| 92 | + |
| 93 | +## Test Structure |
| 94 | + |
| 95 | +Tests in `tests/cases/` use a **three-directory convention**: |
| 96 | + |
| 97 | +- `tests/cases/source/<category>/<case>.py` — input Python source |
| 98 | +- `tests/cases/analyzer/<category>/<case>.py` — expected analysis results (`NAMES`, |
| 99 | + `IMPORTS`, `UNUSED_IMPORTS` lists) |
| 100 | +- `tests/cases/refactor/<category>/<case>.py` — expected output after refactoring |
| 101 | + |
| 102 | +`test_cases.py` parametrizes over all source files and validates both analysis and |
| 103 | +refactoring. To add a new test case, create matching files in all three directories. |
| 104 | + |
| 105 | +The `# unimport: skip_file` comment in source files tells unimport to skip analysis. |
0 commit comments