ballpython 2.0.0__tar.gz → 2.0.2__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.
- {ballpython-2.0.0 → ballpython-2.0.2}/PKG-INFO +13 -7
- {ballpython-2.0.0 → ballpython-2.0.2}/README.md +12 -6
- ballpython-2.0.2/ballpython/py.typed +0 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/ballpython.egg-info/PKG-INFO +13 -7
- {ballpython-2.0.0 → ballpython-2.0.2}/ballpython.egg-info/SOURCES.txt +21 -1
- {ballpython-2.0.0 → ballpython-2.0.2}/pycleaner/__init__.py +39 -2
- ballpython-2.0.2/pycleaner/baseline.py +127 -0
- ballpython-2.0.2/pycleaner/cache.py +282 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/pycleaner/cli.py +644 -43
- {ballpython-2.0.0 → ballpython-2.0.2}/pycleaner/complexity_analyzer.py +16 -28
- {ballpython-2.0.0 → ballpython-2.0.2}/pycleaner/config.py +8 -1
- {ballpython-2.0.0 → ballpython-2.0.2}/pycleaner/dead_code_detector.py +275 -38
- {ballpython-2.0.0 → ballpython-2.0.2}/pycleaner/dependency_auditor.py +175 -75
- ballpython-2.0.2/pycleaner/discovery.py +206 -0
- ballpython-2.0.2/pycleaner/explanations.py +323 -0
- ballpython-2.0.2/pycleaner/frameworks/__init__.py +115 -0
- ballpython-2.0.2/pycleaner/frameworks/plugins.py +290 -0
- ballpython-2.0.2/pycleaner/modernizer.py +349 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/pycleaner/pipeline.py +208 -20
- ballpython-2.0.2/pycleaner/py.typed +0 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/pycleaner/security_scanner.py +59 -38
- {ballpython-2.0.0 → ballpython-2.0.2}/pycleaner/syntax_healer.py +188 -10
- {ballpython-2.0.0 → ballpython-2.0.2}/pycleaner/taint_engine.py +8 -8
- {ballpython-2.0.0 → ballpython-2.0.2}/pycleaner/test_generator.py +124 -35
- {ballpython-2.0.0 → ballpython-2.0.2}/pycleaner/type_checker.py +29 -33
- ballpython-2.0.2/pycleaner/verifier.py +495 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/pyproject.toml +45 -2
- ballpython-2.0.2/tests/test_baseline_and_explain.py +90 -0
- ballpython-2.0.2/tests/test_cache.py +81 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/tests/test_cli.py +56 -0
- ballpython-2.0.2/tests/test_cli_prove.py +83 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/tests/test_config.py +34 -0
- ballpython-2.0.2/tests/test_dead_code_fixer.py +90 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/tests/test_dependency_auditor.py +41 -0
- ballpython-2.0.2/tests/test_discovery.py +98 -0
- ballpython-2.0.2/tests/test_frameworks.py +133 -0
- ballpython-2.0.2/tests/test_modernizer.py +106 -0
- ballpython-2.0.2/tests/test_mutation_test_generator.py +52 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/tests/test_syntax_healer.py +55 -0
- ballpython-2.0.2/tests/test_verifier.py +74 -0
- ballpython-2.0.2/tests/test_verifier_pipeline.py +89 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/ballpython/__init__.py +0 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/ballpython/__main__.py +0 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/ballpython/cli.py +0 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/ballpython.egg-info/dependency_links.txt +0 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/ballpython.egg-info/entry_points.txt +0 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/ballpython.egg-info/requires.txt +0 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/ballpython.egg-info/top_level.txt +0 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/pycleaner/__main__.py +0 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/pycleaner/import_resolver.py +0 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/pycleaner/linter_formatter.py +0 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/pycleaner/typeshed_resolver.py +0 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/setup.cfg +0 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/tests/test_complexity_analyzer.py +0 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/tests/test_dead_code_detector.py +0 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/tests/test_import_resolver.py +0 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/tests/test_linter_formatter.py +0 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/tests/test_pipeline.py +0 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/tests/test_security_scanner.py +0 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/tests/test_taint_engine.py +0 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/tests/test_test_generator.py +0 -0
- {ballpython-2.0.0 → ballpython-2.0.2}/tests/test_type_checker.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ballpython
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
4
4
|
Summary: The Ultimate Static Python Intelligence, Healing, Type Verification, and Security Suite
|
|
5
5
|
Author: Developer
|
|
6
6
|
License-Expression: MIT
|
|
@@ -47,6 +47,7 @@ Zero external LLM dependencies, zero mock modes, and built for deterministic dev
|
|
|
47
47
|
- Modernizes deprecated syntax via `pyupgrade` (`UP`).
|
|
48
48
|
- Applies deterministic PEP 8 formatting (`ruff format` / `black` / pure-Python formatter).
|
|
49
49
|
- Wraps long `from ... import (...)` statements exceeding configured `line-length`.
|
|
50
|
+
- Modernizes legacy typing to PEP 585/604 syntax (`List[T]` -> `list[T]`, `Optional[T]` -> `T | None`), singleton comparisons, and `class X(object)` inheritance via `Modernizer` (disable with `--no-modernize`).
|
|
50
51
|
|
|
51
52
|
4. **Dead Code Detector (`DeadCodeDetector`)**:
|
|
52
53
|
- Builds a cross-file symbol definition and reference graph across a project.
|
|
@@ -54,6 +55,7 @@ Zero external LLM dependencies, zero mock modes, and built for deterministic dev
|
|
|
54
55
|
- Respects public exports in `__all__` and framework decorators (`@app.route`, `@pytest.fixture`, `@abstractmethod`, etc.).
|
|
55
56
|
- Detects unreachable code following unconditional `return`, `raise`, `break`, `continue`, or `sys.exit()`.
|
|
56
57
|
- Identifies empty pass blocks with no comments.
|
|
58
|
+
- Auto-prunes unreachable code, redundant `pass` statements, and `if False:` branches via `DeadCodeFixer` (enable with `dead-code --fix`, disable in the pipeline with `--no-dead-code`).
|
|
57
59
|
|
|
58
60
|
5. **Static Security Scanner (`SecurityScanner`)**:
|
|
59
61
|
- Detects dangerous function calls: `eval()`, `exec()`, `compile()`, `pickle.loads()`, `os.system()`, and unsafe `yaml.load()` lacking `SafeLoader`.
|
|
@@ -103,12 +105,13 @@ pip install -e ".[dev,security]"
|
|
|
103
105
|
### Subcommands
|
|
104
106
|
|
|
105
107
|
#### `fix` (Default Action)
|
|
106
|
-
Heals syntax,
|
|
108
|
+
Heals syntax, modernizes legacy typing (PEP 585/604), prunes dead code, resolves imports, fixes lint violations, and formats code:
|
|
107
109
|
```bash
|
|
108
110
|
py -m pycleaner fix src/
|
|
109
111
|
py -m pycleaner fix --diff path/to/script.py
|
|
110
112
|
py -m pycleaner fix --no-backup src/
|
|
111
113
|
py -m pycleaner fix --parallel --workers 4 src/
|
|
114
|
+
py -m pycleaner fix --no-modernize --no-dead-code src/
|
|
112
115
|
```
|
|
113
116
|
|
|
114
117
|
#### `check` (Dry-Run CI Verification)
|
|
@@ -139,6 +142,7 @@ Finds unused functions, unused classes, empty pass branches, and dead code:
|
|
|
139
142
|
```bash
|
|
140
143
|
py -m pycleaner dead-code .
|
|
141
144
|
py -m pycleaner dead-code --json .
|
|
145
|
+
py -m pycleaner dead-code --fix .
|
|
142
146
|
```
|
|
143
147
|
|
|
144
148
|
#### `audit` (Project Dependency Verification)
|
|
@@ -188,7 +192,7 @@ pycleaner path/to/file.py # Equivalent to: pycleaner fix path/to/file.p
|
|
|
188
192
|
|
|
189
193
|
## Configuration
|
|
190
194
|
|
|
191
|
-
`pycleaner` automatically reads configuration from `pyproject.toml` under `[tool.pycleaner]` or from `.pycleaner.toml`.
|
|
195
|
+
`pycleaner` automatically reads configuration from `pyproject.toml` under `[tool.pycleaner]` or from `.pycleaner.toml`. An explicit file can be forced with `pycleaner --config path/to/pyproject.toml <command> ...`, which overrides target-path auto-discovery.
|
|
192
196
|
|
|
193
197
|
### `pyproject.toml` Example
|
|
194
198
|
|
|
@@ -294,12 +298,14 @@ for func in violations:
|
|
|
294
298
|
from pycleaner import DeadCodeDetector
|
|
295
299
|
|
|
296
300
|
detector = DeadCodeDetector()
|
|
297
|
-
report = detector.
|
|
301
|
+
report = detector.scan_project("src/")
|
|
298
302
|
|
|
299
|
-
|
|
300
|
-
print(f"Unused {item.kind} '{item.name}' at {item.filepath}:{item.lineno}")
|
|
303
|
+
print(f"{report.count} dead code item(s) across {report.files_scanned} file(s).")
|
|
301
304
|
|
|
302
|
-
for item in report.
|
|
305
|
+
for item in report.items:
|
|
306
|
+
print(f"Dead {item.kind} '{item.name}' at {item.filepath}:{item.lineno}")
|
|
307
|
+
|
|
308
|
+
for item in report.by_kind("unreachable"):
|
|
303
309
|
print(f"Unreachable code at {item.filepath}:{item.lineno} ({item.reason})")
|
|
304
310
|
```
|
|
305
311
|
|
|
@@ -28,6 +28,7 @@ Zero external LLM dependencies, zero mock modes, and built for deterministic dev
|
|
|
28
28
|
- Modernizes deprecated syntax via `pyupgrade` (`UP`).
|
|
29
29
|
- Applies deterministic PEP 8 formatting (`ruff format` / `black` / pure-Python formatter).
|
|
30
30
|
- Wraps long `from ... import (...)` statements exceeding configured `line-length`.
|
|
31
|
+
- Modernizes legacy typing to PEP 585/604 syntax (`List[T]` -> `list[T]`, `Optional[T]` -> `T | None`), singleton comparisons, and `class X(object)` inheritance via `Modernizer` (disable with `--no-modernize`).
|
|
31
32
|
|
|
32
33
|
4. **Dead Code Detector (`DeadCodeDetector`)**:
|
|
33
34
|
- Builds a cross-file symbol definition and reference graph across a project.
|
|
@@ -35,6 +36,7 @@ Zero external LLM dependencies, zero mock modes, and built for deterministic dev
|
|
|
35
36
|
- Respects public exports in `__all__` and framework decorators (`@app.route`, `@pytest.fixture`, `@abstractmethod`, etc.).
|
|
36
37
|
- Detects unreachable code following unconditional `return`, `raise`, `break`, `continue`, or `sys.exit()`.
|
|
37
38
|
- Identifies empty pass blocks with no comments.
|
|
39
|
+
- Auto-prunes unreachable code, redundant `pass` statements, and `if False:` branches via `DeadCodeFixer` (enable with `dead-code --fix`, disable in the pipeline with `--no-dead-code`).
|
|
38
40
|
|
|
39
41
|
5. **Static Security Scanner (`SecurityScanner`)**:
|
|
40
42
|
- Detects dangerous function calls: `eval()`, `exec()`, `compile()`, `pickle.loads()`, `os.system()`, and unsafe `yaml.load()` lacking `SafeLoader`.
|
|
@@ -84,12 +86,13 @@ pip install -e ".[dev,security]"
|
|
|
84
86
|
### Subcommands
|
|
85
87
|
|
|
86
88
|
#### `fix` (Default Action)
|
|
87
|
-
Heals syntax,
|
|
89
|
+
Heals syntax, modernizes legacy typing (PEP 585/604), prunes dead code, resolves imports, fixes lint violations, and formats code:
|
|
88
90
|
```bash
|
|
89
91
|
py -m pycleaner fix src/
|
|
90
92
|
py -m pycleaner fix --diff path/to/script.py
|
|
91
93
|
py -m pycleaner fix --no-backup src/
|
|
92
94
|
py -m pycleaner fix --parallel --workers 4 src/
|
|
95
|
+
py -m pycleaner fix --no-modernize --no-dead-code src/
|
|
93
96
|
```
|
|
94
97
|
|
|
95
98
|
#### `check` (Dry-Run CI Verification)
|
|
@@ -120,6 +123,7 @@ Finds unused functions, unused classes, empty pass branches, and dead code:
|
|
|
120
123
|
```bash
|
|
121
124
|
py -m pycleaner dead-code .
|
|
122
125
|
py -m pycleaner dead-code --json .
|
|
126
|
+
py -m pycleaner dead-code --fix .
|
|
123
127
|
```
|
|
124
128
|
|
|
125
129
|
#### `audit` (Project Dependency Verification)
|
|
@@ -169,7 +173,7 @@ pycleaner path/to/file.py # Equivalent to: pycleaner fix path/to/file.p
|
|
|
169
173
|
|
|
170
174
|
## Configuration
|
|
171
175
|
|
|
172
|
-
`pycleaner` automatically reads configuration from `pyproject.toml` under `[tool.pycleaner]` or from `.pycleaner.toml`.
|
|
176
|
+
`pycleaner` automatically reads configuration from `pyproject.toml` under `[tool.pycleaner]` or from `.pycleaner.toml`. An explicit file can be forced with `pycleaner --config path/to/pyproject.toml <command> ...`, which overrides target-path auto-discovery.
|
|
173
177
|
|
|
174
178
|
### `pyproject.toml` Example
|
|
175
179
|
|
|
@@ -275,12 +279,14 @@ for func in violations:
|
|
|
275
279
|
from pycleaner import DeadCodeDetector
|
|
276
280
|
|
|
277
281
|
detector = DeadCodeDetector()
|
|
278
|
-
report = detector.
|
|
282
|
+
report = detector.scan_project("src/")
|
|
279
283
|
|
|
280
|
-
|
|
281
|
-
print(f"Unused {item.kind} '{item.name}' at {item.filepath}:{item.lineno}")
|
|
284
|
+
print(f"{report.count} dead code item(s) across {report.files_scanned} file(s).")
|
|
282
285
|
|
|
283
|
-
for item in report.
|
|
286
|
+
for item in report.items:
|
|
287
|
+
print(f"Dead {item.kind} '{item.name}' at {item.filepath}:{item.lineno}")
|
|
288
|
+
|
|
289
|
+
for item in report.by_kind("unreachable"):
|
|
284
290
|
print(f"Unreachable code at {item.filepath}:{item.lineno} ({item.reason})")
|
|
285
291
|
```
|
|
286
292
|
|
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ballpython
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
4
4
|
Summary: The Ultimate Static Python Intelligence, Healing, Type Verification, and Security Suite
|
|
5
5
|
Author: Developer
|
|
6
6
|
License-Expression: MIT
|
|
@@ -47,6 +47,7 @@ Zero external LLM dependencies, zero mock modes, and built for deterministic dev
|
|
|
47
47
|
- Modernizes deprecated syntax via `pyupgrade` (`UP`).
|
|
48
48
|
- Applies deterministic PEP 8 formatting (`ruff format` / `black` / pure-Python formatter).
|
|
49
49
|
- Wraps long `from ... import (...)` statements exceeding configured `line-length`.
|
|
50
|
+
- Modernizes legacy typing to PEP 585/604 syntax (`List[T]` -> `list[T]`, `Optional[T]` -> `T | None`), singleton comparisons, and `class X(object)` inheritance via `Modernizer` (disable with `--no-modernize`).
|
|
50
51
|
|
|
51
52
|
4. **Dead Code Detector (`DeadCodeDetector`)**:
|
|
52
53
|
- Builds a cross-file symbol definition and reference graph across a project.
|
|
@@ -54,6 +55,7 @@ Zero external LLM dependencies, zero mock modes, and built for deterministic dev
|
|
|
54
55
|
- Respects public exports in `__all__` and framework decorators (`@app.route`, `@pytest.fixture`, `@abstractmethod`, etc.).
|
|
55
56
|
- Detects unreachable code following unconditional `return`, `raise`, `break`, `continue`, or `sys.exit()`.
|
|
56
57
|
- Identifies empty pass blocks with no comments.
|
|
58
|
+
- Auto-prunes unreachable code, redundant `pass` statements, and `if False:` branches via `DeadCodeFixer` (enable with `dead-code --fix`, disable in the pipeline with `--no-dead-code`).
|
|
57
59
|
|
|
58
60
|
5. **Static Security Scanner (`SecurityScanner`)**:
|
|
59
61
|
- Detects dangerous function calls: `eval()`, `exec()`, `compile()`, `pickle.loads()`, `os.system()`, and unsafe `yaml.load()` lacking `SafeLoader`.
|
|
@@ -103,12 +105,13 @@ pip install -e ".[dev,security]"
|
|
|
103
105
|
### Subcommands
|
|
104
106
|
|
|
105
107
|
#### `fix` (Default Action)
|
|
106
|
-
Heals syntax,
|
|
108
|
+
Heals syntax, modernizes legacy typing (PEP 585/604), prunes dead code, resolves imports, fixes lint violations, and formats code:
|
|
107
109
|
```bash
|
|
108
110
|
py -m pycleaner fix src/
|
|
109
111
|
py -m pycleaner fix --diff path/to/script.py
|
|
110
112
|
py -m pycleaner fix --no-backup src/
|
|
111
113
|
py -m pycleaner fix --parallel --workers 4 src/
|
|
114
|
+
py -m pycleaner fix --no-modernize --no-dead-code src/
|
|
112
115
|
```
|
|
113
116
|
|
|
114
117
|
#### `check` (Dry-Run CI Verification)
|
|
@@ -139,6 +142,7 @@ Finds unused functions, unused classes, empty pass branches, and dead code:
|
|
|
139
142
|
```bash
|
|
140
143
|
py -m pycleaner dead-code .
|
|
141
144
|
py -m pycleaner dead-code --json .
|
|
145
|
+
py -m pycleaner dead-code --fix .
|
|
142
146
|
```
|
|
143
147
|
|
|
144
148
|
#### `audit` (Project Dependency Verification)
|
|
@@ -188,7 +192,7 @@ pycleaner path/to/file.py # Equivalent to: pycleaner fix path/to/file.p
|
|
|
188
192
|
|
|
189
193
|
## Configuration
|
|
190
194
|
|
|
191
|
-
`pycleaner` automatically reads configuration from `pyproject.toml` under `[tool.pycleaner]` or from `.pycleaner.toml`.
|
|
195
|
+
`pycleaner` automatically reads configuration from `pyproject.toml` under `[tool.pycleaner]` or from `.pycleaner.toml`. An explicit file can be forced with `pycleaner --config path/to/pyproject.toml <command> ...`, which overrides target-path auto-discovery.
|
|
192
196
|
|
|
193
197
|
### `pyproject.toml` Example
|
|
194
198
|
|
|
@@ -294,12 +298,14 @@ for func in violations:
|
|
|
294
298
|
from pycleaner import DeadCodeDetector
|
|
295
299
|
|
|
296
300
|
detector = DeadCodeDetector()
|
|
297
|
-
report = detector.
|
|
301
|
+
report = detector.scan_project("src/")
|
|
298
302
|
|
|
299
|
-
|
|
300
|
-
print(f"Unused {item.kind} '{item.name}' at {item.filepath}:{item.lineno}")
|
|
303
|
+
print(f"{report.count} dead code item(s) across {report.files_scanned} file(s).")
|
|
301
304
|
|
|
302
|
-
for item in report.
|
|
305
|
+
for item in report.items:
|
|
306
|
+
print(f"Dead {item.kind} '{item.name}' at {item.filepath}:{item.lineno}")
|
|
307
|
+
|
|
308
|
+
for item in report.by_kind("unreachable"):
|
|
303
309
|
print(f"Unreachable code at {item.filepath}:{item.lineno} ({item.reason})")
|
|
304
310
|
```
|
|
305
311
|
|
|
@@ -3,6 +3,7 @@ pyproject.toml
|
|
|
3
3
|
ballpython/__init__.py
|
|
4
4
|
ballpython/__main__.py
|
|
5
5
|
ballpython/cli.py
|
|
6
|
+
ballpython/py.typed
|
|
6
7
|
ballpython.egg-info/PKG-INFO
|
|
7
8
|
ballpython.egg-info/SOURCES.txt
|
|
8
9
|
ballpython.egg-info/dependency_links.txt
|
|
@@ -11,30 +12,49 @@ ballpython.egg-info/requires.txt
|
|
|
11
12
|
ballpython.egg-info/top_level.txt
|
|
12
13
|
pycleaner/__init__.py
|
|
13
14
|
pycleaner/__main__.py
|
|
15
|
+
pycleaner/baseline.py
|
|
16
|
+
pycleaner/cache.py
|
|
14
17
|
pycleaner/cli.py
|
|
15
18
|
pycleaner/complexity_analyzer.py
|
|
16
19
|
pycleaner/config.py
|
|
17
20
|
pycleaner/dead_code_detector.py
|
|
18
21
|
pycleaner/dependency_auditor.py
|
|
22
|
+
pycleaner/discovery.py
|
|
23
|
+
pycleaner/explanations.py
|
|
19
24
|
pycleaner/import_resolver.py
|
|
20
25
|
pycleaner/linter_formatter.py
|
|
26
|
+
pycleaner/modernizer.py
|
|
21
27
|
pycleaner/pipeline.py
|
|
28
|
+
pycleaner/py.typed
|
|
22
29
|
pycleaner/security_scanner.py
|
|
23
30
|
pycleaner/syntax_healer.py
|
|
24
31
|
pycleaner/taint_engine.py
|
|
25
32
|
pycleaner/test_generator.py
|
|
26
33
|
pycleaner/type_checker.py
|
|
27
34
|
pycleaner/typeshed_resolver.py
|
|
35
|
+
pycleaner/verifier.py
|
|
36
|
+
pycleaner/frameworks/__init__.py
|
|
37
|
+
pycleaner/frameworks/plugins.py
|
|
38
|
+
tests/test_baseline_and_explain.py
|
|
39
|
+
tests/test_cache.py
|
|
28
40
|
tests/test_cli.py
|
|
41
|
+
tests/test_cli_prove.py
|
|
29
42
|
tests/test_complexity_analyzer.py
|
|
30
43
|
tests/test_config.py
|
|
31
44
|
tests/test_dead_code_detector.py
|
|
45
|
+
tests/test_dead_code_fixer.py
|
|
32
46
|
tests/test_dependency_auditor.py
|
|
47
|
+
tests/test_discovery.py
|
|
48
|
+
tests/test_frameworks.py
|
|
33
49
|
tests/test_import_resolver.py
|
|
34
50
|
tests/test_linter_formatter.py
|
|
51
|
+
tests/test_modernizer.py
|
|
52
|
+
tests/test_mutation_test_generator.py
|
|
35
53
|
tests/test_pipeline.py
|
|
36
54
|
tests/test_security_scanner.py
|
|
37
55
|
tests/test_syntax_healer.py
|
|
38
56
|
tests/test_taint_engine.py
|
|
39
57
|
tests/test_test_generator.py
|
|
40
|
-
tests/test_type_checker.py
|
|
58
|
+
tests/test_type_checker.py
|
|
59
|
+
tests/test_verifier.py
|
|
60
|
+
tests/test_verifier_pipeline.py
|
|
@@ -10,19 +10,38 @@ dataflow taint vulnerabilities, and synthesizes automated behavioral test suites
|
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
from pycleaner.discovery import (
|
|
14
|
+
DEFAULT_IGNORED_DIRS,
|
|
15
|
+
PROTECTED_FILE_PATTERNS,
|
|
16
|
+
collect_project_python_files,
|
|
17
|
+
is_ignored_directory,
|
|
18
|
+
is_protected_file,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__version__ = "2.0.2"
|
|
14
22
|
__all__ = [
|
|
23
|
+
"BaselineFingerprint",
|
|
24
|
+
"BaselineManager",
|
|
15
25
|
"CleanPipeline",
|
|
16
26
|
"CleanResult",
|
|
17
27
|
"ComplexityAnalyzer",
|
|
18
28
|
"ComplexityReport",
|
|
29
|
+
"ContentAddressableCache",
|
|
30
|
+
"CounterExample",
|
|
19
31
|
"DeadCodeDetector",
|
|
32
|
+
"DeadCodeFixResult",
|
|
33
|
+
"DeadCodeFixer",
|
|
20
34
|
"DeadCodeReport",
|
|
21
35
|
"DependencyAuditor",
|
|
22
36
|
"GeneratedTestSuite",
|
|
23
37
|
"ImportResolver",
|
|
38
|
+
"IsolatedDifferentialVerifier",
|
|
24
39
|
"LinterFormatter",
|
|
40
|
+
"ModernizeResult",
|
|
41
|
+
"Modernizer",
|
|
42
|
+
"ProofReceipt",
|
|
25
43
|
"PyCleanerConfig",
|
|
44
|
+
"RuleExplanation",
|
|
26
45
|
"SecurityReport",
|
|
27
46
|
"SecurityScanner",
|
|
28
47
|
"SyntaxHealer",
|
|
@@ -35,15 +54,27 @@ __all__ = [
|
|
|
35
54
|
"TypeFinding",
|
|
36
55
|
"TypeReport",
|
|
37
56
|
"TypeshedResolver",
|
|
57
|
+
"VerificationTier",
|
|
58
|
+
"get_explanation",
|
|
59
|
+
"list_rules",
|
|
38
60
|
"load_config",
|
|
39
61
|
]
|
|
40
62
|
|
|
63
|
+
from pycleaner.baseline import BaselineFingerprint, BaselineManager
|
|
64
|
+
from pycleaner.cache import ContentAddressableCache
|
|
41
65
|
from pycleaner.complexity_analyzer import ComplexityAnalyzer, ComplexityReport
|
|
42
66
|
from pycleaner.config import PyCleanerConfig, load_config
|
|
43
|
-
from pycleaner.dead_code_detector import
|
|
67
|
+
from pycleaner.dead_code_detector import (
|
|
68
|
+
DeadCodeDetector,
|
|
69
|
+
DeadCodeFixer,
|
|
70
|
+
DeadCodeFixResult,
|
|
71
|
+
DeadCodeReport,
|
|
72
|
+
)
|
|
44
73
|
from pycleaner.dependency_auditor import DependencyAuditor
|
|
74
|
+
from pycleaner.explanations import RuleExplanation, get_explanation, list_rules
|
|
45
75
|
from pycleaner.import_resolver import ImportResolver
|
|
46
76
|
from pycleaner.linter_formatter import LinterFormatter
|
|
77
|
+
from pycleaner.modernizer import Modernizer, ModernizeResult
|
|
47
78
|
from pycleaner.pipeline import CleanPipeline, CleanResult
|
|
48
79
|
from pycleaner.security_scanner import SecurityReport, SecurityScanner
|
|
49
80
|
from pycleaner.syntax_healer import SyntaxHealer
|
|
@@ -51,3 +82,9 @@ from pycleaner.taint_engine import TaintEngine, TaintFinding, TaintReport
|
|
|
51
82
|
from pycleaner.test_generator import GeneratedTestSuite, TestCase, TestGenerator
|
|
52
83
|
from pycleaner.type_checker import TypeChecker, TypeFinding, TypeReport
|
|
53
84
|
from pycleaner.typeshed_resolver import TypeshedResolver
|
|
85
|
+
from pycleaner.verifier import (
|
|
86
|
+
CounterExample,
|
|
87
|
+
IsolatedDifferentialVerifier,
|
|
88
|
+
ProofReceipt,
|
|
89
|
+
VerificationTier,
|
|
90
|
+
)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pycleaner.baseline
|
|
3
|
+
==================
|
|
4
|
+
|
|
5
|
+
Technical Debt Ratchet and Baseline Management Engine.
|
|
6
|
+
|
|
7
|
+
Enables incremental adoption of PyCleaner on legacy codebases by snapshotting
|
|
8
|
+
existing diagnostic violations into `.pycleaner/baseline.json`. Future CI/CD
|
|
9
|
+
checks ensure that existing technical debt can only decrease, never increase.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import hashlib
|
|
15
|
+
import json
|
|
16
|
+
import time
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class BaselineFingerprint:
|
|
24
|
+
rule: str
|
|
25
|
+
file: str
|
|
26
|
+
line: int
|
|
27
|
+
symbol: str
|
|
28
|
+
|
|
29
|
+
def to_hash(self) -> str:
|
|
30
|
+
raw = f"{self.rule}:{self.file}:{self.line}:{self.symbol}"
|
|
31
|
+
return hashlib.sha256(raw.encode("utf-8")).hexdigest()[:16]
|
|
32
|
+
|
|
33
|
+
def to_dict(self) -> dict[str, Any]:
|
|
34
|
+
return {
|
|
35
|
+
"rule": self.rule,
|
|
36
|
+
"file": self.file,
|
|
37
|
+
"line": self.line,
|
|
38
|
+
"symbol": self.symbol,
|
|
39
|
+
"hash": self.to_hash(),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class BaselineManager:
|
|
44
|
+
"""Manages baseline generation, loading, and ratchet comparison."""
|
|
45
|
+
|
|
46
|
+
def __init__(self, baseline_path: Path | str = ".pycleaner/baseline.json") -> None:
|
|
47
|
+
self.baseline_path = Path(baseline_path)
|
|
48
|
+
|
|
49
|
+
def load_fingerprints(self) -> set[str]:
|
|
50
|
+
"""Loads baseline issue hashes from disk."""
|
|
51
|
+
if not self.baseline_path.exists():
|
|
52
|
+
return set()
|
|
53
|
+
try:
|
|
54
|
+
data = json.loads(self.baseline_path.read_text(encoding="utf-8"))
|
|
55
|
+
fingerprints = data.get("fingerprints", [])
|
|
56
|
+
return {
|
|
57
|
+
fp.get("hash")
|
|
58
|
+
for fp in fingerprints
|
|
59
|
+
if isinstance(fp, dict) and fp.get("hash")
|
|
60
|
+
}
|
|
61
|
+
except json.JSONDecodeError:
|
|
62
|
+
return set()
|
|
63
|
+
|
|
64
|
+
def save_baseline(
|
|
65
|
+
self,
|
|
66
|
+
fingerprints: list[BaselineFingerprint],
|
|
67
|
+
root_dir: Path,
|
|
68
|
+
) -> Path:
|
|
69
|
+
"""Serializes fingerprints to the baseline JSON file."""
|
|
70
|
+
self.baseline_path.parent.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
payload = {
|
|
72
|
+
"version": 1,
|
|
73
|
+
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
74
|
+
"root_dir": str(root_dir.resolve()),
|
|
75
|
+
"issue_count": len(fingerprints),
|
|
76
|
+
"fingerprints": [fp.to_dict() for fp in fingerprints],
|
|
77
|
+
}
|
|
78
|
+
self.baseline_path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
|
79
|
+
return self.baseline_path
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def create_fingerprint(
|
|
83
|
+
rule: str,
|
|
84
|
+
file_path: Path | str,
|
|
85
|
+
line: int,
|
|
86
|
+
symbol: str,
|
|
87
|
+
root_dir: Path | None = None,
|
|
88
|
+
) -> BaselineFingerprint:
|
|
89
|
+
"""Constructs a deterministic fingerprint with normalized relative path."""
|
|
90
|
+
p = Path(file_path)
|
|
91
|
+
if root_dir is not None:
|
|
92
|
+
try:
|
|
93
|
+
rel = str(p.resolve().relative_to(root_dir.resolve())).replace(
|
|
94
|
+
"\\", "/"
|
|
95
|
+
)
|
|
96
|
+
except ValueError:
|
|
97
|
+
rel = p.name
|
|
98
|
+
else:
|
|
99
|
+
rel = p.name
|
|
100
|
+
|
|
101
|
+
return BaselineFingerprint(
|
|
102
|
+
rule=rule,
|
|
103
|
+
file=rel,
|
|
104
|
+
line=line,
|
|
105
|
+
symbol=symbol,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def filter_new_issues(
|
|
109
|
+
self,
|
|
110
|
+
issues: list[BaselineFingerprint],
|
|
111
|
+
) -> tuple[list[BaselineFingerprint], list[BaselineFingerprint]]:
|
|
112
|
+
"""
|
|
113
|
+
Separates issues into (tolerated_baseline_issues, new_debt_issues).
|
|
114
|
+
Returns:
|
|
115
|
+
(tolerated, new_debt)
|
|
116
|
+
"""
|
|
117
|
+
known_hashes = self.load_fingerprints()
|
|
118
|
+
tolerated: list[BaselineFingerprint] = []
|
|
119
|
+
new_debt: list[BaselineFingerprint] = []
|
|
120
|
+
|
|
121
|
+
for issue in issues:
|
|
122
|
+
if issue.to_hash() in known_hashes:
|
|
123
|
+
tolerated.append(issue)
|
|
124
|
+
else:
|
|
125
|
+
new_debt.append(issue)
|
|
126
|
+
|
|
127
|
+
return tolerated, new_debt
|