dead-cst 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dead_cst-0.1.0/.claude/settings.json +20 -0
- dead_cst-0.1.0/.github/workflows/ci.yml +55 -0
- dead_cst-0.1.0/.github/workflows/publish.yml +68 -0
- dead_cst-0.1.0/.gitignore +210 -0
- dead_cst-0.1.0/.pre-commit-config.yaml +38 -0
- dead_cst-0.1.0/.python-version +1 -0
- dead_cst-0.1.0/CHANGELOG.md +122 -0
- dead_cst-0.1.0/CONTRIBUTING.md +154 -0
- dead_cst-0.1.0/LICENSE +21 -0
- dead_cst-0.1.0/PKG-INFO +223 -0
- dead_cst-0.1.0/README.md +197 -0
- dead_cst-0.1.0/ROADMAP.md +150 -0
- dead_cst-0.1.0/codecov.yml +82 -0
- dead_cst-0.1.0/dead_cst/__init__.py +99 -0
- dead_cst-0.1.0/dead_cst/_analyze.py +258 -0
- dead_cst-0.1.0/dead_cst/_branches.py +203 -0
- dead_cst-0.1.0/dead_cst/_codemod.py +135 -0
- dead_cst-0.1.0/dead_cst/_flow.py +173 -0
- dead_cst-0.1.0/dead_cst/_fqn.py +30 -0
- dead_cst-0.1.0/dead_cst/_plugins/__init__.py +106 -0
- dead_cst-0.1.0/dead_cst/_plugins/_core.py +524 -0
- dead_cst-0.1.0/dead_cst/_plugins/click.py +181 -0
- dead_cst-0.1.0/dead_cst/_plugins/explicit.py +58 -0
- dead_cst-0.1.0/dead_cst/_plugins/fastapi.py +154 -0
- dead_cst-0.1.0/dead_cst/_plugins/flask.py +172 -0
- dead_cst-0.1.0/dead_cst/_plugins/init_subclass.py +296 -0
- dead_cst-0.1.0/dead_cst/_plugins/main_block.py +72 -0
- dead_cst-0.1.0/dead_cst/_plugins/module_dunders.py +55 -0
- dead_cst-0.1.0/dead_cst/_plugins/project_scripts.py +60 -0
- dead_cst-0.1.0/dead_cst/_plugins/pytest.py +127 -0
- dead_cst-0.1.0/dead_cst/_plugins/typer.py +101 -0
- dead_cst-0.1.0/dead_cst/_plugins/unittest.py +171 -0
- dead_cst-0.1.0/dead_cst/_resolve.py +232 -0
- dead_cst-0.1.0/dead_cst/_resolvers/__init__.py +58 -0
- dead_cst-0.1.0/dead_cst/_resolvers/_core.py +52 -0
- dead_cst-0.1.0/dead_cst/_resolvers/_exports.py +111 -0
- dead_cst-0.1.0/dead_cst/_resolvers/pyproject.py +49 -0
- dead_cst-0.1.0/dead_cst/_resolvers/uv_workspace.py +104 -0
- dead_cst-0.1.0/dead_cst/_resolvers/venv.py +117 -0
- dead_cst-0.1.0/dead_cst/_symbols.py +177 -0
- dead_cst-0.1.0/dead_cst/_version.py +24 -0
- dead_cst-0.1.0/dead_cst/_visitor.py +542 -0
- dead_cst-0.1.0/dead_cst/cli.py +556 -0
- dead_cst-0.1.0/dead_cst/py.typed +0 -0
- dead_cst-0.1.0/examples/README.md +30 -0
- dead_cst-0.1.0/examples/scripts-and-all/README.md +72 -0
- dead_cst-0.1.0/examples/scripts-and-all/pyproject.toml +12 -0
- dead_cst-0.1.0/examples/scripts-and-all/src/reportkit/__init__.py +0 -0
- dead_cst-0.1.0/examples/scripts-and-all/src/reportkit/cli.py +6 -0
- dead_cst-0.1.0/examples/scripts-and-all/src/reportkit/formatters.py +21 -0
- dead_cst-0.1.0/examples/scripts-and-all/src/reportkit/legacy_writer.py +8 -0
- dead_cst-0.1.0/examples/scripts-and-all/src/reportkit/renderer.py +9 -0
- dead_cst-0.1.0/examples/simple-app/README.md +50 -0
- dead_cst-0.1.0/examples/simple-app/pyproject.toml +9 -0
- dead_cst-0.1.0/examples/simple-app/simple_app/__init__.py +0 -0
- dead_cst-0.1.0/examples/simple-app/simple_app/__main__.py +11 -0
- dead_cst-0.1.0/examples/simple-app/simple_app/core.py +11 -0
- dead_cst-0.1.0/examples/simple-app/simple_app/utils.py +8 -0
- dead_cst-0.1.0/examples/uv-workspace/README.md +117 -0
- dead_cst-0.1.0/examples/uv-workspace/packages/app/app/__init__.py +0 -0
- dead_cst-0.1.0/examples/uv-workspace/packages/app/app/cli.py +9 -0
- dead_cst-0.1.0/examples/uv-workspace/packages/app/app/helpers.py +4 -0
- dead_cst-0.1.0/examples/uv-workspace/packages/app/pyproject.toml +13 -0
- dead_cst-0.1.0/examples/uv-workspace/packages/app/tests/__init__.py +0 -0
- dead_cst-0.1.0/examples/uv-workspace/packages/app/tests/conftest.py +8 -0
- dead_cst-0.1.0/examples/uv-workspace/packages/core/core/__init__.py +0 -0
- dead_cst-0.1.0/examples/uv-workspace/packages/core/core/api.py +7 -0
- dead_cst-0.1.0/examples/uv-workspace/packages/core/pyproject.toml +12 -0
- dead_cst-0.1.0/examples/uv-workspace/packages/core/tests/__init__.py +0 -0
- dead_cst-0.1.0/examples/uv-workspace/packages/core/tests/conftest.py +8 -0
- dead_cst-0.1.0/examples/uv-workspace/pyproject.toml +12 -0
- dead_cst-0.1.0/examples/uv-workspace/uv.lock +31 -0
- dead_cst-0.1.0/pyproject.toml +106 -0
- dead_cst-0.1.0/tests/conftest.py +114 -0
- dead_cst-0.1.0/tests/test_branches.py +327 -0
- dead_cst-0.1.0/tests/test_cli.py +658 -0
- dead_cst-0.1.0/tests/test_codemod.py +680 -0
- dead_cst-0.1.0/tests/test_declarations.py +1258 -0
- dead_cst-0.1.0/tests/test_flow_sensitive_filter.py +352 -0
- dead_cst-0.1.0/tests/test_imports.py +329 -0
- dead_cst-0.1.0/tests/test_limitations.py +87 -0
- dead_cst-0.1.0/tests/test_plugins/__init__.py +0 -0
- dead_cst-0.1.0/tests/test_plugins/conftest.py +17 -0
- dead_cst-0.1.0/tests/test_plugins/test_click.py +664 -0
- dead_cst-0.1.0/tests/test_plugins/test_core.py +401 -0
- dead_cst-0.1.0/tests/test_plugins/test_explicit.py +45 -0
- dead_cst-0.1.0/tests/test_plugins/test_fastapi.py +440 -0
- dead_cst-0.1.0/tests/test_plugins/test_file_cache.py +188 -0
- dead_cst-0.1.0/tests/test_plugins/test_flask.py +646 -0
- dead_cst-0.1.0/tests/test_plugins/test_init_subclass.py +378 -0
- dead_cst-0.1.0/tests/test_plugins/test_main_block.py +51 -0
- dead_cst-0.1.0/tests/test_plugins/test_module_dunders.py +96 -0
- dead_cst-0.1.0/tests/test_plugins/test_project_scripts.py +26 -0
- dead_cst-0.1.0/tests/test_plugins/test_pytest.py +162 -0
- dead_cst-0.1.0/tests/test_plugins/test_typer.py +449 -0
- dead_cst-0.1.0/tests/test_plugins/test_unittest.py +207 -0
- dead_cst-0.1.0/tests/test_resolvers/__init__.py +0 -0
- dead_cst-0.1.0/tests/test_resolvers/test_core.py +66 -0
- dead_cst-0.1.0/tests/test_resolvers/test_exports.py +132 -0
- dead_cst-0.1.0/tests/test_resolvers/test_pyproject.py +41 -0
- dead_cst-0.1.0/tests/test_resolvers/test_uv_workspace.py +405 -0
- dead_cst-0.1.0/tests/test_resolvers/test_venv.py +38 -0
- dead_cst-0.1.0/tests/test_scope_provider_contract.py +259 -0
- dead_cst-0.1.0/tests/test_top_level_only.py +130 -0
- dead_cst-0.1.0/tests/test_unreachable_branches.py +272 -0
- dead_cst-0.1.0/uv.lock +922 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/claude-code-settings.json",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"PostToolUse": [
|
|
5
|
+
{
|
|
6
|
+
"matcher": "Write|Edit|MultiEdit",
|
|
7
|
+
"hooks": [
|
|
8
|
+
{
|
|
9
|
+
"type": "command",
|
|
10
|
+
"command": "jq -r '.tool_input.file_path // .tool_response.filePath // empty' | { read -r f; [ -z \"$f\" ] && exit 0; case \"$f\" in *.py) cd /home/user/dead-cst && uv run --quiet ruff format \"$f\" && uv run --quiet ruff check --fix --unfixable F401 \"$f\" ;; esac; } 2>/dev/null || true"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"type": "command",
|
|
14
|
+
"command": "jq -r '.tool_input.file_path // .tool_response.filePath // empty' | { read -r f; [ -z \"$f\" ] && exit 0; case \"$f\" in *.py) cd /home/user/dead-cst && uv run --quiet ty check ;; esac; } 2>/dev/null || true"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
lint:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v6
|
|
13
|
+
with:
|
|
14
|
+
fetch-depth: 0
|
|
15
|
+
- uses: astral-sh/setup-uv@v7
|
|
16
|
+
with:
|
|
17
|
+
python-version: 3.12
|
|
18
|
+
activate-environment: true
|
|
19
|
+
enable-cache: true
|
|
20
|
+
cache-dependency-glob: "**/uv.lock"
|
|
21
|
+
- uses: actions/cache@v5
|
|
22
|
+
with:
|
|
23
|
+
path: ~/.cache/prek
|
|
24
|
+
key: prek-${{ hashFiles('.pre-commit-config.yaml') }}
|
|
25
|
+
- run: uv sync --frozen
|
|
26
|
+
- run: uv run prek run --show-diff-on-failure --color=always --all-files
|
|
27
|
+
env:
|
|
28
|
+
RUFF_OUTPUT_FORMAT: github
|
|
29
|
+
CONFTEST_OUTPUT: github
|
|
30
|
+
|
|
31
|
+
test:
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
strategy:
|
|
34
|
+
matrix:
|
|
35
|
+
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@v6
|
|
38
|
+
with:
|
|
39
|
+
fetch-depth: 0
|
|
40
|
+
- uses: astral-sh/setup-uv@v7
|
|
41
|
+
with:
|
|
42
|
+
python-version: ${{ matrix.python-version }}
|
|
43
|
+
activate-environment: true
|
|
44
|
+
enable-cache: true
|
|
45
|
+
cache-dependency-glob: "**/uv.lock"
|
|
46
|
+
- run: uv sync --frozen
|
|
47
|
+
- name: Run tests
|
|
48
|
+
run: uv run pytest ${{ matrix.python-version == '3.13' && '--cov=dead_cst --cov-branch --cov-report=xml' || '' }}
|
|
49
|
+
- name: Upload coverage to Codecov
|
|
50
|
+
if: matrix.python-version == '3.13'
|
|
51
|
+
uses: codecov/codecov-action@v5
|
|
52
|
+
with:
|
|
53
|
+
files: ./coverage.xml
|
|
54
|
+
fail_ci_if_error: false
|
|
55
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
tags: ["v*"]
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
id-token: write
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v6
|
|
17
|
+
with:
|
|
18
|
+
fetch-depth: 0
|
|
19
|
+
- uses: astral-sh/setup-uv@v7
|
|
20
|
+
- run: uv build
|
|
21
|
+
- uses: actions/upload-artifact@v4
|
|
22
|
+
with:
|
|
23
|
+
name: dist
|
|
24
|
+
path: dist/
|
|
25
|
+
|
|
26
|
+
publish-testpypi:
|
|
27
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
28
|
+
needs: build
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
environment: testpypi
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/download-artifact@v4
|
|
33
|
+
with:
|
|
34
|
+
name: dist
|
|
35
|
+
path: dist/
|
|
36
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
37
|
+
with:
|
|
38
|
+
repository-url: https://test.pypi.org/legacy/
|
|
39
|
+
|
|
40
|
+
publish-pypi:
|
|
41
|
+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
|
|
42
|
+
needs: build
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
environment: pypi
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/download-artifact@v4
|
|
47
|
+
with:
|
|
48
|
+
name: dist
|
|
49
|
+
path: dist/
|
|
50
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
51
|
+
|
|
52
|
+
github-release:
|
|
53
|
+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
|
|
54
|
+
needs: publish-pypi
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
permissions:
|
|
57
|
+
contents: write
|
|
58
|
+
steps:
|
|
59
|
+
- uses: actions/download-artifact@v4
|
|
60
|
+
with:
|
|
61
|
+
name: dist
|
|
62
|
+
path: dist/
|
|
63
|
+
- uses: softprops/action-gh-release@v2
|
|
64
|
+
with:
|
|
65
|
+
generate_release_notes: true
|
|
66
|
+
files: dist/*
|
|
67
|
+
prerelease: true
|
|
68
|
+
fail_on_unmatched_files: true
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
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
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
#uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
#poetry.lock
|
|
109
|
+
#poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
#pdm.lock
|
|
116
|
+
#pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
#pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# SageMath parsed files
|
|
135
|
+
*.sage.py
|
|
136
|
+
|
|
137
|
+
# Environments
|
|
138
|
+
.env
|
|
139
|
+
.envrc
|
|
140
|
+
.venv
|
|
141
|
+
env/
|
|
142
|
+
venv/
|
|
143
|
+
ENV/
|
|
144
|
+
env.bak/
|
|
145
|
+
venv.bak/
|
|
146
|
+
|
|
147
|
+
# Spyder project settings
|
|
148
|
+
.spyderproject
|
|
149
|
+
.spyproject
|
|
150
|
+
|
|
151
|
+
# Rope project settings
|
|
152
|
+
.ropeproject
|
|
153
|
+
|
|
154
|
+
# mkdocs documentation
|
|
155
|
+
/site
|
|
156
|
+
|
|
157
|
+
# mypy
|
|
158
|
+
.mypy_cache/
|
|
159
|
+
.dmypy.json
|
|
160
|
+
dmypy.json
|
|
161
|
+
|
|
162
|
+
# Pyre type checker
|
|
163
|
+
.pyre/
|
|
164
|
+
|
|
165
|
+
# pytype static type analyzer
|
|
166
|
+
.pytype/
|
|
167
|
+
|
|
168
|
+
# Cython debug symbols
|
|
169
|
+
cython_debug/
|
|
170
|
+
|
|
171
|
+
# PyCharm
|
|
172
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
173
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
174
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
175
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
176
|
+
#.idea/
|
|
177
|
+
|
|
178
|
+
# Abstra
|
|
179
|
+
# Abstra is an AI-powered process automation framework.
|
|
180
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
181
|
+
# Learn more at https://abstra.io/docs
|
|
182
|
+
.abstra/
|
|
183
|
+
|
|
184
|
+
# Visual Studio Code
|
|
185
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
186
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
188
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
189
|
+
# .vscode/
|
|
190
|
+
|
|
191
|
+
# hatch-vcs generated version file
|
|
192
|
+
dead_cst/_version.py
|
|
193
|
+
|
|
194
|
+
# Ruff stuff:
|
|
195
|
+
.ruff_cache/
|
|
196
|
+
|
|
197
|
+
# PyPI configuration file
|
|
198
|
+
.pypirc
|
|
199
|
+
|
|
200
|
+
# Cursor
|
|
201
|
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
202
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
203
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
204
|
+
.cursorignore
|
|
205
|
+
.cursorindexingignore
|
|
206
|
+
|
|
207
|
+
# Marimo
|
|
208
|
+
marimo/_static/
|
|
209
|
+
marimo/_lsp/
|
|
210
|
+
__marimo__/
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v5.0.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: trailing-whitespace
|
|
6
|
+
types: [python]
|
|
7
|
+
- id: end-of-file-fixer
|
|
8
|
+
types: [python]
|
|
9
|
+
- id: check-merge-conflict
|
|
10
|
+
- id: check-case-conflict
|
|
11
|
+
- id: check-json
|
|
12
|
+
- id: check-toml
|
|
13
|
+
- id: check-yaml
|
|
14
|
+
- id: check-ast
|
|
15
|
+
- id: debug-statements
|
|
16
|
+
- id: check-docstring-first
|
|
17
|
+
|
|
18
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
19
|
+
rev: v0.11.2
|
|
20
|
+
hooks:
|
|
21
|
+
- id: ruff
|
|
22
|
+
args: [--fix, --exit-non-zero-on-fix]
|
|
23
|
+
- id: ruff-format
|
|
24
|
+
|
|
25
|
+
- repo: https://github.com/astral-sh/uv-pre-commit
|
|
26
|
+
rev: 0.7.2
|
|
27
|
+
hooks:
|
|
28
|
+
- id: uv-lock
|
|
29
|
+
|
|
30
|
+
- repo: local
|
|
31
|
+
hooks:
|
|
32
|
+
- id: ty
|
|
33
|
+
name: ty check
|
|
34
|
+
entry: uv run --frozen ty check
|
|
35
|
+
language: system
|
|
36
|
+
types_or: [python, pyi]
|
|
37
|
+
pass_filenames: false
|
|
38
|
+
require_serial: true
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `dead-cst` will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
Until the first stable release the public API and CLI may change between any
|
|
8
|
+
two versions.
|
|
9
|
+
|
|
10
|
+
## [Unreleased]
|
|
11
|
+
|
|
12
|
+
## [0.1.0] - 2026-04-28
|
|
13
|
+
|
|
14
|
+
Initial alpha release. `dead-cst` is pre-1.0 software: the public Python API,
|
|
15
|
+
CLI flags, and output formats may change without notice between any two
|
|
16
|
+
versions until the first stable release.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- Symbol-level reachability analysis built on LibCST's
|
|
20
|
+
`FullyQualifiedNameProvider` and `ScopeProvider`.
|
|
21
|
+
- Resolution of relative imports, aliased imports, and re-export chains
|
|
22
|
+
through `__init__.py`.
|
|
23
|
+
- `dead-cst analyze` CLI for reporting unreachable symbols and unreachable
|
|
24
|
+
branches (`if False:`, raise-only suites, etc.), with `text` and `json`
|
|
25
|
+
output formats.
|
|
26
|
+
- `dead-cst why-alive` CLI for explaining why a symbol is kept alive.
|
|
27
|
+
- `dead-cst remove` CLI that rewrites files in place via a LibCST codemod,
|
|
28
|
+
with import pruning when the last local user of an import is deleted and
|
|
29
|
+
position-aware shadowing so a shadowed dead binding no longer drags its
|
|
30
|
+
live sibling out with it.
|
|
31
|
+
- `dead-cst unused-exports` CLI command: report `__all__` entries whose
|
|
32
|
+
targets are kept alive only because they are listed in `__all__`.
|
|
33
|
+
- `dead-cst dependencies` CLI command: list third-party distributions and
|
|
34
|
+
files imported by the codebase, surfaced as synthetic
|
|
35
|
+
`[external dist] <name>` / `[external file] <name>` graph nodes.
|
|
36
|
+
- Multi-package / monorepo support via the `-p base:dep1,dep2` search-path
|
|
37
|
+
spec, with topological ordering of bases.
|
|
38
|
+
- Edge plugin architecture (`EdgePlugin`, `CSTAwareEdgePlugin`,
|
|
39
|
+
`PluginContext`, `GraphOp`/`AddNode`/`AddEdge`/`RemoveEdge`, `apply_ops`,
|
|
40
|
+
`synthetic_node`). Built-in plugins: `MainBlockPlugin`,
|
|
41
|
+
`ProjectScriptsPlugin`, `ExplicitEntrypointPlugin`, `ModuleDundersPlugin`,
|
|
42
|
+
`PytestPlugin`, `FastAPIPlugin`, `FlaskPlugin`, `TyperPlugin`,
|
|
43
|
+
`ClickPlugin`, `UnittestPlugin`, `InitSubclassPlugin`. Third-party
|
|
44
|
+
plugins register under the `dead_cst.plugins` entry-point group and load
|
|
45
|
+
via `load_plugin`.
|
|
46
|
+
- `PytestPlugin` (`--plugin pytest`): keep pytest-discovered tests,
|
|
47
|
+
`conftest.py` decls, and `@pytest.fixture` functions alive.
|
|
48
|
+
- `FastAPIPlugin` (`--plugin fastapi`): detect top-level `FastAPI()` and
|
|
49
|
+
`APIRouter()` instances (including factory-style apps), mark `FastAPI`
|
|
50
|
+
apps as entrypoints, and emit `instance -> handler` edges for
|
|
51
|
+
`@app.get(...)`-style decorators (HTTP methods, websockets, middleware,
|
|
52
|
+
exception handlers, `on_event`). Routers stay pass-through, so an
|
|
53
|
+
`APIRouter` that's never `include_router`'d remains dead.
|
|
54
|
+
- `FlaskPlugin` (`--plugin flask`): detect top-level `Flask()` /
|
|
55
|
+
`Blueprint()` instances (including factory-style apps) and emit
|
|
56
|
+
`instance -> handler` edges for `@app.route(...)`, HTTP-verb shortcuts,
|
|
57
|
+
request-lifecycle hooks (`before_request`, `after_request`,
|
|
58
|
+
`teardown_*`), error handlers, template helpers (`context_processor`,
|
|
59
|
+
`template_filter`, ...), and URL processors. `Flask` apps are seeded as
|
|
60
|
+
entrypoints (WSGI servers load `module:app`); `Blueprint`s stay
|
|
61
|
+
pass-through, so a blueprint never `register_blueprint`'d remains dead,
|
|
62
|
+
mirroring the `APIRouter` behavior in `FastAPIPlugin`.
|
|
63
|
+
- `TyperPlugin` (`--plugin typer`): detect top-level `Typer()` instances
|
|
64
|
+
and emit `instance -> handler` edges for `@app.command(...)` and
|
|
65
|
+
`@app.callback(...)` decorators. Typer apps are pass-through;
|
|
66
|
+
reachability is expected through `[project.scripts]` or a `__main__`
|
|
67
|
+
block, after which every registered command and callback stays alive.
|
|
68
|
+
Sub-typers that are never `add_typer`'d remain dead.
|
|
69
|
+
- `ClickPlugin` (`--plugin click`): detect top-level Click `Group`
|
|
70
|
+
instances (functions decorated `@click.group(...)` / `@click.Group(...)`,
|
|
71
|
+
`X = click.Group(...)` constructor calls, and inline sub-groups
|
|
72
|
+
registered via `@<group>.group(...)`, all resolved via fixpoint so a
|
|
73
|
+
chain of nested groups is fully discovered) and emit
|
|
74
|
+
`instance -> handler` edges for `@<group>.command(...)`,
|
|
75
|
+
`@<group>.group(...)`, and `@<group>.result_callback(...)` decorators.
|
|
76
|
+
Click groups stay pass-through; reachability is expected through
|
|
77
|
+
`[project.scripts]` or a `__main__` block, mirroring `TyperPlugin`.
|
|
78
|
+
- `UnittestPlugin` (`--plugin unittest`): mark stdlib `unittest.TestCase`
|
|
79
|
+
and `unittest.IsolatedAsyncioTestCase` subclasses, plus module-level
|
|
80
|
+
`setUpModule` / `tearDownModule` / `load_tests` hooks, as entrypoints.
|
|
81
|
+
Discovery is CST-based and prefiltered to files whose import nodes
|
|
82
|
+
reference `unittest`. Supports `import unittest` (with alias),
|
|
83
|
+
`from unittest import TestCase` (with alias), and module-prefixed base
|
|
84
|
+
references. Only direct base-class matches are recognised; transitive
|
|
85
|
+
subclasses through a project-local mixin need an explicit `-e`
|
|
86
|
+
entrypoint or coverage from `PytestPlugin`'s filename heuristics.
|
|
87
|
+
`from unittest import *`-only files are skipped (the resolver doesn't
|
|
88
|
+
surface stdlib star imports as graph nodes); use a non-star import.
|
|
89
|
+
- `InitSubclassPlugin` (`--plugin init_subclass`): detect classes that
|
|
90
|
+
define `__init_subclass__` and route reachability through a synthetic
|
|
91
|
+
marker node `<__init_subclass__>:<parent.fqname>` with edges
|
|
92
|
+
`parent -> marker -> subclass` for every transitive first-party
|
|
93
|
+
subclass. Registry-pattern subclasses stay alive whenever the parent
|
|
94
|
+
class does; the marker shows up in `why-alive` chains as a labeled
|
|
95
|
+
breadcrumb. Parents are pass-through, so a registry base nobody else
|
|
96
|
+
uses still surfaces as dead code.
|
|
97
|
+
- `ModuleDundersPlugin`: keep module-level dunder variables (`__all__`,
|
|
98
|
+
`__version__`, `__future__` imports, etc.) alive. Always registered by
|
|
99
|
+
the CLI.
|
|
100
|
+
- Path resolver architecture (`PathResolver`, `merge_paths`). Built-in
|
|
101
|
+
resolvers: `VenvResolver`, `PyprojectResolver`, `UvWorkspaceResolver`
|
|
102
|
+
(parses `uv.lock` to discover workspace members and inter-member
|
|
103
|
+
edges, including virtual workspace members that don't ship as wheels).
|
|
104
|
+
Third-party resolvers register under `dead_cst.resolvers` and load via
|
|
105
|
+
`load_resolver`.
|
|
106
|
+
- `exported_roots(base)` in `dead_cst._resolvers`: inspect a base's
|
|
107
|
+
`pyproject.toml` (src-layout, hatchling/setuptools/poetry/pdm/flit
|
|
108
|
+
backends, name-match fallback) to determine which subdirs the build
|
|
109
|
+
backend would actually ship, so internal dirs like `tests/` stay scoped
|
|
110
|
+
to their owning workspace member during cross-member import resolution.
|
|
111
|
+
- `--resolver` and `--plugin` flags on `analyze`, `why-alive`,
|
|
112
|
+
`unused-exports`, and `remove` for selecting path resolvers and edge
|
|
113
|
+
plugins.
|
|
114
|
+
- Public Python API: `build_symbol_graph`, `find_reachable`,
|
|
115
|
+
`count_nodes`, `order_paths`, `remove_code`, plus a `position` field
|
|
116
|
+
on `SymbolNode`.
|
|
117
|
+
- `py.typed` marker for downstream type-checking.
|
|
118
|
+
- `README.md`, `CONTRIBUTING.md`, `CHANGELOG.md`, and `ROADMAP.md` with a
|
|
119
|
+
stack-ranked plan from alpha to 1.0.
|
|
120
|
+
|
|
121
|
+
[Unreleased]: https://github.com/lpetre/dead-cst/compare/v0.1.0...HEAD
|
|
122
|
+
[0.1.0]: https://github.com/lpetre/dead-cst/releases/tag/v0.1.0
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Contributing to dead-cst
|
|
2
|
+
|
|
3
|
+
Thanks for your interest. `dead-cst` is small and pre-release — bug reports with minimal repros, real-world test cases, and PRs are all welcome. Expect APIs and CLI flags to keep moving until the first stable release. See [`ROADMAP.md`](ROADMAP.md) for the planned trajectory.
|
|
4
|
+
|
|
5
|
+
## Development setup
|
|
6
|
+
|
|
7
|
+
`dead-cst` uses [uv](https://github.com/astral-sh/uv) for environment management.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
git clone https://github.com/lpetre/dead-cst
|
|
11
|
+
cd dead-cst
|
|
12
|
+
uv sync
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
That installs the package in editable mode along with the `dev` group: `pytest`, `ruff`, `prek`, and `ty`.
|
|
16
|
+
|
|
17
|
+
## Running tests
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv run pytest
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
For a tight inner loop while editing the visitor, resolver, or a plugin, `pytest-watcher` is in the dev group:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uv run ptw
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
To collect coverage locally:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
uv run pytest --cov=dead_cst --cov-branch --cov-report=term-missing
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
CI uploads branch coverage from the 3.13 matrix entry to Codecov on every push and PR.
|
|
36
|
+
|
|
37
|
+
## Coverage policy
|
|
38
|
+
|
|
39
|
+
Per-component thresholds reflect blast radius, not total LOC. The full
|
|
40
|
+
configuration lives in `codecov.yml`; the targets are:
|
|
41
|
+
|
|
42
|
+
| Component | Target | Why |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `_codemod.py` | 95% | Rewrites user files. Regressions corrupt source. |
|
|
45
|
+
| `cli.py` | 80% | User-visible trust surface. |
|
|
46
|
+
| `_plugins/**` | 85% | Each plugin is a framework promise. |
|
|
47
|
+
| `_resolvers/**` | 85% | Path resolution drives every analysis. |
|
|
48
|
+
| `_analyze.py`, `_visitor.py`, `_resolve.py`, `_flow.py`, `_branches.py`, `_symbols.py`, `_fqn.py` | 80% | Analytical core. |
|
|
49
|
+
|
|
50
|
+
Per-component statuses are **informational** until Tier 1 of `ROADMAP.md`
|
|
51
|
+
(CLI integration tests, remaining framework presets) lands and the numbers
|
|
52
|
+
clear the bars above. The patch-coverage gate on new code is **enforcing**
|
|
53
|
+
at 90% — regressions are caught at the diff, not in aggregate.
|
|
54
|
+
|
|
55
|
+
`if TYPE_CHECKING:` blocks, `@overload` stubs, and `_version.py` are
|
|
56
|
+
excluded; see `[tool.coverage.*]` in `pyproject.toml`.
|
|
57
|
+
|
|
58
|
+
## Linting and formatting
|
|
59
|
+
|
|
60
|
+
Ruff (lint + format), `ty` (type check), and a small set of hooks run on every commit via [`prek`](https://github.com/j178/prek), a fast Rust-based drop-in replacement for `pre-commit` that reads the same `.pre-commit-config.yaml`.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
uv run prek install # one-time, sets up the git hook
|
|
64
|
+
uv run prek run --all-files
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
CI runs `prek run --all-files` on every push and pull request, so running it locally before committing avoids round-trips.
|
|
68
|
+
|
|
69
|
+
## Project layout
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
dead_cst/
|
|
73
|
+
__init__.py # public API surface
|
|
74
|
+
_analyze.py # build_symbol_graph, find_reachable, count_nodes, order_paths
|
|
75
|
+
_codemod.py # remove_code -- LibCST transformer + import pruner
|
|
76
|
+
_resolve.py # import resolution (stdlib / first-party / third-party)
|
|
77
|
+
_resolvers/ # path resolvers:
|
|
78
|
+
venv.py # sibling .venv -> site-packages
|
|
79
|
+
pyproject.py # [tool.dead-cst] paths or src/ fallback
|
|
80
|
+
uv_workspace.py # uv.lock workspace members + inter-member deps
|
|
81
|
+
_exports.py # exported_roots: hide internal dirs from consumers
|
|
82
|
+
_symbols.py # SymbolNode, SymbolTrie data classes
|
|
83
|
+
_visitor.py # SymbolVisitor -- walks each file and emits symbols + edges
|
|
84
|
+
_flow.py # flow-sensitive live-at-exit analysis for shadowing
|
|
85
|
+
_branches.py # statically-dead suite detection
|
|
86
|
+
_fqn.py # FullyQualifiedNameProvider patches / wrappers
|
|
87
|
+
_plugins/ # edge plugins:
|
|
88
|
+
main_block.py # if __name__ == "__main__"
|
|
89
|
+
project_scripts.py # pyproject.toml [project.scripts]
|
|
90
|
+
explicit.py # user-supplied -e specs
|
|
91
|
+
module_dunders.py # __all__, __version__, ...
|
|
92
|
+
pytest.py # conftest, test_*.py, @pytest.fixture
|
|
93
|
+
fastapi.py # FastAPI / APIRouter route handlers
|
|
94
|
+
cli.py # Typer entrypoints: analyze, why-alive, unused-exports,
|
|
95
|
+
# dependencies, remove
|
|
96
|
+
tests/ # pytest suite, fixture-driven from inline source snippets
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Modules prefixed with `_` are internal; only the names re-exported from `dead_cst/__init__.py` are part of the supported API. Within the package, plugins and resolvers are stable extension points — see below.
|
|
100
|
+
|
|
101
|
+
## Adding a plugin
|
|
102
|
+
|
|
103
|
+
A plugin is any class that satisfies `EdgePlugin` (or `CSTAwareEdgePlugin` if it needs LibCST metadata). Implement `name: str` and a `contribute(ctx)` method that yields `AddNode`, `AddEdge`, or `RemoveEdge` ops.
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from dead_cst import AddEdge, AddNode, GraphOp, PluginContext, synthetic_node
|
|
107
|
+
|
|
108
|
+
class FlaskRoutesPlugin:
|
|
109
|
+
name = "flask_routes"
|
|
110
|
+
|
|
111
|
+
def contribute(self, ctx: PluginContext):
|
|
112
|
+
for node in ctx.graph.nodes:
|
|
113
|
+
... # detect @app.route handlers and emit AddNode(..., entrypoint=True)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Built-in plugins live in `dead_cst/_plugins/<name>.py`; out-of-tree plugins register under the `dead_cst.plugins` entry-point group:
|
|
117
|
+
|
|
118
|
+
```toml
|
|
119
|
+
[project.entry-points."dead_cst.plugins"]
|
|
120
|
+
flask_routes = "myproj.plugins:FlaskRoutesPlugin"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`FastAPIPlugin` is a good full-featured reference — it walks each module's CST, recognises the `FastAPI()` / `APIRouter()` instance pattern, and wires `instance -> handler` edges through decorator detection.
|
|
124
|
+
|
|
125
|
+
## Adding a resolver
|
|
126
|
+
|
|
127
|
+
A resolver implements `PathResolver`: a `name: str` attribute and a `resolve(project_root)` method returning a `{base: [dep_paths]}` dict. Built-in resolvers live in `_resolvers.py`; third-party resolvers register under `dead_cst.resolvers`.
|
|
128
|
+
|
|
129
|
+
## Adding a test
|
|
130
|
+
|
|
131
|
+
Tests use the `build_decl_graph` fixture (in `tests/conftest.py`), which writes a dict of `{filename: source}` to a tmpdir, runs `build_symbol_graph` on it, and returns the resulting graph. The `assert_edges` fixture compares edges as `"src.fqname -> dst.fqname"` strings.
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
def test_something(build_decl_graph, assert_edges):
|
|
135
|
+
graph = build_decl_graph({"mod.py": "def a(): pass\na()"})
|
|
136
|
+
assert_edges(graph, {"mod.a -> mod", "mod -> mod.a"})
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Plugin tests follow the same pattern in `tests/test_plugins/`. For codemod regressions, `tests/test_codemod.py` writes a source snippet, runs `remove_code`, and asserts on the rewritten text.
|
|
140
|
+
|
|
141
|
+
## Reporting bugs
|
|
142
|
+
|
|
143
|
+
A good bug report contains a minimal `.py` file (or pair of files) and the entrypoint flag you ran, plus the actual vs. expected dead-symbol output. The smaller the repro, the faster it gets fixed.
|
|
144
|
+
|
|
145
|
+
## Pull requests
|
|
146
|
+
|
|
147
|
+
- Keep PRs focused — one logical change per PR.
|
|
148
|
+
- Add or update tests for behaviour changes.
|
|
149
|
+
- Run `prek run --all-files` and `pytest` before pushing.
|
|
150
|
+
- If your change is user-visible, add an entry to `CHANGELOG.md` under `[Unreleased]`.
|
|
151
|
+
|
|
152
|
+
## Releasing
|
|
153
|
+
|
|
154
|
+
Releases are tag-driven. Pushing a `vX.Y.Z` tag triggers `.github/workflows/publish.yml`, which builds the package with `uv build` and publishes to PyPI via OIDC. The version is read from the tag by `hatch-vcs`, so no manual version bumps in source files are needed.
|