py-wlcli 0.8.0__py3-none-any.whl
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.
- py_wlcli-0.8.0.dist-info/METADATA +139 -0
- py_wlcli-0.8.0.dist-info/RECORD +320 -0
- py_wlcli-0.8.0.dist-info/WHEEL +4 -0
- py_wlcli-0.8.0.dist-info/entry_points.txt +2 -0
- py_wlcli-0.8.0.dist-info/licenses/LICENSE +21 -0
- wl_cli/__init__.py +27 -0
- wl_cli/__main__.py +130 -0
- wl_cli/capabilities.py +79 -0
- wl_cli/cc/__init__.py +5 -0
- wl_cli/cc/checks.py +1019 -0
- wl_cli/cc/cli.py +370 -0
- wl_cli/cc/mutate.py +1350 -0
- wl_cli/cli_click.py +156 -0
- wl_cli/commands/__init__.py +84 -0
- wl_cli/commands/base.py +80 -0
- wl_cli/commands/buildcommands/__init__.py +1 -0
- wl_cli/commands/buildcommands/build.py +71 -0
- wl_cli/commands/buildcommands/build_utils.py +47 -0
- wl_cli/commands/buildcommands/builders/__init__.py +18 -0
- wl_cli/commands/buildcommands/builders/cleanup.py +35 -0
- wl_cli/commands/buildcommands/builders/coordinator.py +122 -0
- wl_cli/commands/buildcommands/builders/environment.py +81 -0
- wl_cli/commands/buildcommands/builders/rust_builder.py +39 -0
- wl_cli/commands/buildcommands/builders/stubs.py +58 -0
- wl_cli/commands/buildcommands/dist.py +326 -0
- wl_cli/commands/buildcommands/test.py +89 -0
- wl_cli/commands/clean/__init__.py +1 -0
- wl_cli/commands/clean/build.py +151 -0
- wl_cli/commands/clean/clean.py +294 -0
- wl_cli/commands/clean/clean_utils.py +77 -0
- wl_cli/commands/clean/core.py +236 -0
- wl_cli/commands/clean/dead_code/__init__.py +0 -0
- wl_cli/commands/clean/dead_code/dead_code_command.py +70 -0
- wl_cli/commands/clean/dead_code/detector.py +161 -0
- wl_cli/commands/clean/dead_code/fixer.py +176 -0
- wl_cli/commands/clean/dead_code/reporter.py +295 -0
- wl_cli/commands/clean/dead_code/utils.py +0 -0
- wl_cli/commands/clean/lfs.py +215 -0
- wl_cli/commands/clean/python.py +81 -0
- wl_cli/commands/clean/rust.py +91 -0
- wl_cli/commands/clean/utils/__init__.py +34 -0
- wl_cli/commands/clean/venv.py +91 -0
- wl_cli/commands/cleanup.py +73 -0
- wl_cli/commands/click_base.py +79 -0
- wl_cli/commands/config/__init__.py +1 -0
- wl_cli/commands/config/config.py +291 -0
- wl_cli/commands/debug/__init__.py +6 -0
- wl_cli/commands/debug/debug.py +309 -0
- wl_cli/commands/debug/debug_cache.py +86 -0
- wl_cli/commands/debug/debug_subagent.py +156 -0
- wl_cli/commands/debug/output_formatter.py +165 -0
- wl_cli/commands/debug/source_hasher.py +92 -0
- wl_cli/commands/format/__init__.py +13 -0
- wl_cli/commands/format/format_cache_adapter.py +125 -0
- wl_cli/commands/format/format_command.py +197 -0
- wl_cli/commands/format/format_coordinator.py +191 -0
- wl_cli/commands/format/format_executor.py +470 -0
- wl_cli/commands/format/format_outcome.py +44 -0
- wl_cli/commands/format/format_path_handler.py +1016 -0
- wl_cli/commands/format/format_progress.py +62 -0
- wl_cli/commands/format/format_reporter.py +31 -0
- wl_cli/commands/format/python_formatter.py +506 -0
- wl_cli/commands/format/rust_formatter.py +106 -0
- wl_cli/commands/gate/__init__.py +1 -0
- wl_cli/commands/gate/gate_command.py +862 -0
- wl_cli/commands/gate/preflight.py +445 -0
- wl_cli/commands/git/__init__.py +1 -0
- wl_cli/commands/git/config.py +169 -0
- wl_cli/commands/git/git_facts.py +60 -0
- wl_cli/commands/git/git_flow.py +86 -0
- wl_cli/commands/git/hook.py +112 -0
- wl_cli/commands/git/policy.py +216 -0
- wl_cli/commands/git/push_update.py +82 -0
- wl_cli/commands/initenv/__init__.py +1 -0
- wl_cli/commands/initenv/common/__init__.py +23 -0
- wl_cli/commands/initenv/common/check_git_bash.py +78 -0
- wl_cli/commands/initenv/common/error_handler.py +68 -0
- wl_cli/commands/initenv/common/exceptions.py +52 -0
- wl_cli/commands/initenv/components/__init__.py +4 -0
- wl_cli/commands/initenv/components/cargo_sync.py +240 -0
- wl_cli/commands/initenv/components/config_manager.py +106 -0
- wl_cli/commands/initenv/components/directory_utils.py +71 -0
- wl_cli/commands/initenv/components/git/__init__.py +9 -0
- wl_cli/commands/initenv/components/git/branch_creator.py +143 -0
- wl_cli/commands/initenv/components/git/git_flow_interface.py +24 -0
- wl_cli/commands/initenv/components/git/git_flow_manager.py +159 -0
- wl_cli/commands/initenv/components/git/git_lfs_manager.py +272 -0
- wl_cli/commands/initenv/components/git/git_utility.py +254 -0
- wl_cli/commands/initenv/components/git/gitignore_manager.py +53 -0
- wl_cli/commands/initenv/components/git_initializer.py +212 -0
- wl_cli/commands/initenv/components/i18n_manager.py +104 -0
- wl_cli/commands/initenv/components/log_manager.py +241 -0
- wl_cli/commands/initenv/components/project_structure/__init__.py +23 -0
- wl_cli/commands/initenv/components/project_structure/config_generator.py +360 -0
- wl_cli/commands/initenv/components/project_structure/directory_creator.py +184 -0
- wl_cli/commands/initenv/components/project_structure/hook_source.py +110 -0
- wl_cli/commands/initenv/components/project_structure/hooks_manager.py +995 -0
- wl_cli/commands/initenv/components/project_structure/readme_handler.py +56 -0
- wl_cli/commands/initenv/components/project_structure/rust_files_handler.py +30 -0
- wl_cli/commands/initenv/components/project_structure/setup_handler.py +103 -0
- wl_cli/commands/initenv/components/project_structure/sop_generator.py +73 -0
- wl_cli/commands/initenv/components/project_structure/template_copier.py +104 -0
- wl_cli/commands/initenv/components/project_structure/template_manager.py +195 -0
- wl_cli/commands/initenv/components/pyproject_generator.py +194 -0
- wl_cli/commands/initenv/components/rust_initializer.py +145 -0
- wl_cli/commands/initenv/components/symlink_cleaner.py +99 -0
- wl_cli/commands/initenv/components/symlink_manager.py +52 -0
- wl_cli/commands/initenv/components/venv_manager.py +118 -0
- wl_cli/commands/initenv/initenv.py +169 -0
- wl_cli/commands/initenv/services/__init__.py +11 -0
- wl_cli/commands/initenv/services/environment_initializer.py +288 -0
- wl_cli/commands/initenv/services/git_lfs_initializer.py +26 -0
- wl_cli/commands/initenv/services/project_structure_initializer.py +52 -0
- wl_cli/commands/lfs/README.md +221 -0
- wl_cli/commands/lfs/__init__.py +5 -0
- wl_cli/commands/lfs/cli.py +106 -0
- wl_cli/commands/lfs/config.py +164 -0
- wl_cli/commands/lfs/hook.py +61 -0
- wl_cli/commands/lfs/initializer.py +531 -0
- wl_cli/commands/lfs/manager.py +95 -0
- wl_cli/commands/lfs/policy.py +165 -0
- wl_cli/commands/lfs/watcher.py +294 -0
- wl_cli/commands/lint/__init__.py +1 -0
- wl_cli/commands/lint/lint_command.py +297 -0
- wl_cli/commands/lint/lint_executor.py +409 -0
- wl_cli/commands/lint/lint_reporter.py +579 -0
- wl_cli/commands/lint/lint_run.py +258 -0
- wl_cli/commands/lint/mypy_config.py +90 -0
- wl_cli/commands/registry.py +103 -0
- wl_cli/commands/self/__init__.py +7 -0
- wl_cli/commands/self/common/__init__.py +8 -0
- wl_cli/commands/self/common/capabilities.py +30 -0
- wl_cli/commands/self/common/config.py +27 -0
- wl_cli/commands/self/common/version_cache.py +85 -0
- wl_cli/commands/self/handlers/__init__.py +19 -0
- wl_cli/commands/self/handlers/base_handler.py +28 -0
- wl_cli/commands/self/handlers/install_handler.py +85 -0
- wl_cli/commands/self/handlers/status_handler.py +66 -0
- wl_cli/commands/self/handlers/uninstall_handler.py +58 -0
- wl_cli/commands/self/handlers/update_handler.py +102 -0
- wl_cli/commands/self/handlers/version_handler.py +16 -0
- wl_cli/commands/self/self_command.py +237 -0
- wl_cli/commands/self/services/__init__.py +15 -0
- wl_cli/commands/self/services/installation_manager.py +187 -0
- wl_cli/commands/self/services/local_installer.py +165 -0
- wl_cli/commands/self/services/pypi_updater.py +278 -0
- wl_cli/commands/self/services/windows_update.py +174 -0
- wl_cli/commands/testcommands/__init__.py +8 -0
- wl_cli/commands/testcommands/base_dependency_analyzer.py +198 -0
- wl_cli/commands/testcommands/batch_processor.py +278 -0
- wl_cli/commands/testcommands/cache_manager.py +1156 -0
- wl_cli/commands/testcommands/coverage_analyzer.py +955 -0
- wl_cli/commands/testcommands/coverage_cache_manager.py +183 -0
- wl_cli/commands/testcommands/progress_manager.py +79 -0
- wl_cli/commands/testcommands/python_env_detector.py +207 -0
- wl_cli/commands/testcommands/report_generator.py +101 -0
- wl_cli/commands/testcommands/single_test_executor.py +250 -0
- wl_cli/commands/testcommands/speed_checker.py +118 -0
- wl_cli/commands/testcommands/test_command_builder.py +165 -0
- wl_cli/commands/testcommands/test_commands.py +786 -0
- wl_cli/commands/testcommands/test_reporter.py +181 -0
- wl_cli/commands/testcommands/test_result_handler.py +96 -0
- wl_cli/commands/testcommands/test_runner.py +1343 -0
- wl_cli/commands/testcommands/test_utils.py +197 -0
- wl_cli/commands/testcommands/utils/__init__.py +17 -0
- wl_cli/commands/testcommands/utils/coverage_parser.py +98 -0
- wl_cli/commands/testcommands/utils/env_info.py +36 -0
- wl_cli/commands/testcommands/utils/junit_parser.py +75 -0
- wl_cli/commands/testcommands/utils/markdown_utils.py +20 -0
- wl_cli/commands/testcommands/utils/project_root.py +61 -0
- wl_cli/commands/update/__init__.py +9 -0
- wl_cli/commands/update/services/__init__.py +21 -0
- wl_cli/commands/update/services/backup_manager.py +207 -0
- wl_cli/commands/update/services/component_manager.py +83 -0
- wl_cli/commands/update/services/conflict_resolver.py +189 -0
- wl_cli/commands/update/services/vendors_updater.py +85 -0
- wl_cli/commands/update/services/version_tracker.py +127 -0
- wl_cli/commands/update/update_command.py +270 -0
- wl_cli/commands/update/utils/__init__.py +3 -0
- wl_cli/commands/version/__init__.py +9 -0
- wl_cli/commands/version/version_command.py +155 -0
- wl_cli/commands/workflow.py +477 -0
- wl_cli/env/__init__.py +5 -0
- wl_cli/env/cli.py +167 -0
- wl_cli/env_status.py +157 -0
- wl_cli/envelope.py +91 -0
- wl_cli/exceptions.py +85 -0
- wl_cli/lib/__init__.py +3 -0
- wl_cli/lib/rust_utils.py +35 -0
- wl_cli/mcp/__init__.py +6 -0
- wl_cli/mcp/capabilities.py +19 -0
- wl_cli/mcp/cli.py +22 -0
- wl_cli/mcp/gitlab.py +553 -0
- wl_cli/mcp/k8s.py +344 -0
- wl_cli/mcp/protocol.py +159 -0
- wl_cli/platform.py +139 -0
- wl_cli/quality/__init__.py +18 -0
- wl_cli/utils/__init__.py +7 -0
- wl_cli/utils/cache.py +491 -0
- wl_cli/utils/cache_ledger.py +355 -0
- wl_cli/utils/cache_store.py +546 -0
- wl_cli/utils/config/__init__.py +27 -0
- wl_cli/utils/config/config_loader.py +163 -0
- wl_cli/utils/config/core.py +862 -0
- wl_cli/utils/config/defaults.py +142 -0
- wl_cli/utils/config/env_exporter.py +86 -0
- wl_cli/utils/config/env_parser.py +108 -0
- wl_cli/utils/config/validation.py +280 -0
- wl_cli/utils/error_handler.py +165 -0
- wl_cli/utils/file_copier.py +56 -0
- wl_cli/utils/file_operations.py +506 -0
- wl_cli/utils/language_utils.py +122 -0
- wl_cli/utils/lazy_import.py +240 -0
- wl_cli/utils/log_levels.py +35 -0
- wl_cli/utils/log_rotators.py +74 -0
- wl_cli/utils/logging.py +458 -0
- wl_cli/utils/output_context.py +142 -0
- wl_cli/utils/performance/__init__.py +1 -0
- wl_cli/utils/performance/decorators.py +111 -0
- wl_cli/utils/performance/exporter.py +38 -0
- wl_cli/utils/performance/models.py +16 -0
- wl_cli/utils/performance/monitor.py +161 -0
- wl_cli/utils/performance_monitor.py +32 -0
- wl_cli/utils/platform_adapter.py +131 -0
- wl_cli/utils/pre_commit.py +266 -0
- wl_cli/utils/pre_commit_config.py +72 -0
- wl_cli/utils/root.py +521 -0
- wl_cli/utils/root_fallback.py +256 -0
- wl_cli/utils/run_evidence.py +236 -0
- wl_cli/utils/structured_logger.py +18 -0
- wl_cli/utils/structured_logging/__init__.py +19 -0
- wl_cli/utils/structured_logging/config.py +337 -0
- wl_cli/utils/structured_logging/core.py +177 -0
- wl_cli/utils/structured_logging/env.py +56 -0
- wl_cli/utils/structured_logging/filtering.py +75 -0
- wl_cli/utils/structured_logging/filters.py +55 -0
- wl_cli/utils/structured_logging/formatters.py +251 -0
- wl_cli/utils/structured_logging/handlers.py +308 -0
- wl_cli/utils/structured_logging/logger.py +354 -0
- wl_cli/utils/subprocess.py +71 -0
- wl_cli/utils/subprocess_utils.py +260 -0
- wl_cli/utils/test_utils/__init__.py +5 -0
- wl_cli/utils/test_utils/test_filter.py +36 -0
- wl_cli/utils/uv_run.py +79 -0
- wl_cli/utils/uv_tool.py +117 -0
- wl_cli/utils/uv_tree.py +71 -0
- wl_cli/utils/uv_workspace.py +174 -0
- wl_cli/utils/version/__init__.py +15 -0
- wl_cli/utils/version/pypi_version_checker.py +95 -0
- wl_cli/utils/version/version_comparator.py +133 -0
- wl_cli/utils/version/version_detectors.py +312 -0
- wl_cli/utils/version/version_service.py +131 -0
- wl_cli/utils/version/version_updater.py +198 -0
- wl_cli/utils/wl_dir_updater.py +1440 -0
- wl_cli/utils/workspace_detector/__init__.py +28 -0
- wl_cli/utils/workspace_detector/core.py +161 -0
- wl_cli/utils/workspace_detector/detection_strategies.py +107 -0
- wl_cli/utils/workspace_detector/exceptions.py +5 -0
- wl_cli/utils/workspace_detector/types.py +17 -0
- wl_cli/utils/workspace_detector/venv_resolver.py +84 -0
- wl_cli/vendors/config/.codespellrc +2 -0
- wl_cli/vendors/config/dependencies.toml +10 -0
- wl_cli/vendors/config/mypy.ini +62 -0
- wl_cli/vendors/config/pyproject_defaults.toml +28 -0
- wl_cli/vendors/config/template_config.toml +24 -0
- wl_cli/vendors/config/tool_configs.toml +76 -0
- wl_cli/vendors/git/.gitignore +122 -0
- wl_cli/vendors/git/.pre-commit-config.yaml +37 -0
- wl_cli/vendors/hooks/applypatch-msg.sample +15 -0
- wl_cli/vendors/hooks/commit-msg.sample +24 -0
- wl_cli/vendors/hooks/fsmonitor-watchman.sample +174 -0
- wl_cli/vendors/hooks/post-update.sample +8 -0
- wl_cli/vendors/hooks/pre-applypatch.sample +14 -0
- wl_cli/vendors/hooks/pre-commit +45 -0
- wl_cli/vendors/hooks/pre-commit.legacy +31 -0
- wl_cli/vendors/hooks/pre-commit.sample +49 -0
- wl_cli/vendors/hooks/pre-merge-commit.sample +13 -0
- wl_cli/vendors/hooks/pre-push.sample +60 -0
- wl_cli/vendors/hooks/pre-rebase.sample +169 -0
- wl_cli/vendors/hooks/pre-receive.sample +24 -0
- wl_cli/vendors/hooks/prepare-commit-msg.sample +42 -0
- wl_cli/vendors/hooks/push-to-checkout.sample +78 -0
- wl_cli/vendors/hooks/sendemail-validate.sample +77 -0
- wl_cli/vendors/hooks/unix/applypatch-msg.sample +15 -0
- wl_cli/vendors/hooks/unix/check_lockfile.py +58 -0
- wl_cli/vendors/hooks/unix/check_lockfile.sh +36 -0
- wl_cli/vendors/hooks/unix/commit-msg.sample +24 -0
- wl_cli/vendors/hooks/unix/fsmonitor-watchman.sample +174 -0
- wl_cli/vendors/hooks/unix/post-update.sample +8 -0
- wl_cli/vendors/hooks/unix/pre-applypatch.sample +14 -0
- wl_cli/vendors/hooks/unix/pre-commit +190 -0
- wl_cli/vendors/hooks/unix/pre-commit.legacy +31 -0
- wl_cli/vendors/hooks/unix/pre-commit.py +81 -0
- wl_cli/vendors/hooks/unix/pre-commit.sample +49 -0
- wl_cli/vendors/hooks/unix/pre-merge-commit.sample +13 -0
- wl_cli/vendors/hooks/unix/pre-push +417 -0
- wl_cli/vendors/hooks/unix/pre-push.sample +53 -0
- wl_cli/vendors/hooks/unix/pre-rebase.sample +169 -0
- wl_cli/vendors/hooks/unix/pre-receive.sample +24 -0
- wl_cli/vendors/hooks/unix/prepare-commit-msg.sample +42 -0
- wl_cli/vendors/hooks/update.sample +128 -0
- wl_cli/vendors/hooks/win/work.bat +63 -0
- wl_cli/vendors/project/.gitignore +90 -0
- wl_cli/vendors/project/.python-version +1 -0
- wl_cli/vendors/project/LICENSE +21 -0
- wl_cli/vendors/project/README.md +0 -0
- wl_cli/vendors/project/pyproject.toml +27 -0
- wl_cli/vendors/project/pyproject_no_workspace.toml +16 -0
- wl_cli/vendors/project/pyproject_workspace.toml +14 -0
- wl_cli/vendors/readme/README.md +120 -0
- wl_cli/vendors/rust/Cargo.toml +14 -0
- wl_cli/vendors/rust/lib/__init__.py +3 -0
- wl_cli/vendors/rust/lib/rust_utils.py +31 -0
- wl_cli/vendors/rust/src/lib.rs +13 -0
- wl_cli/vendors/rust/src/main.rs +3 -0
- wl_cli/vendors/rust/tests/test_rust_fallback.py +40 -0
- wl_cli/vendors/sop_doc/git-dev-sop.md +220 -0
- wl_cli/vendors/sop_doc/industrial-standard-sop.md +145 -0
- wl_cli/vendors/sop_doc/python-sop.md +342 -0
- wl_cli/vendors/sop_doc/rust-readme.md +219 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core cleaning utilities for clean command.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import concurrent.futures
|
|
6
|
+
import glob
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from wl_cli.utils.logging import log_info_pair
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def remove_directories(dirs_to_remove: list[str], dry_run: bool = False) -> None:
|
|
16
|
+
"""Remove specific directories."""
|
|
17
|
+
for dir_name in dirs_to_remove:
|
|
18
|
+
if os.path.exists(dir_name):
|
|
19
|
+
try:
|
|
20
|
+
if dry_run:
|
|
21
|
+
log_info_pair(
|
|
22
|
+
f"Would remove directory: {dir_name}", f"将删除目录: {dir_name}"
|
|
23
|
+
)
|
|
24
|
+
else:
|
|
25
|
+
shutil.rmtree(dir_name)
|
|
26
|
+
log_info_pair(
|
|
27
|
+
f"Removed directory: {dir_name}", f"已删除目录: {dir_name}"
|
|
28
|
+
)
|
|
29
|
+
except Exception as e:
|
|
30
|
+
log_info_pair(
|
|
31
|
+
f"Failed to remove directory {dir_name}: {e}",
|
|
32
|
+
f"删除目录 {dir_name} 失败: {e}",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def remove_files(files_to_remove: list[str], dry_run: bool = False) -> None:
|
|
37
|
+
"""Remove specific files, supporting wildcard patterns."""
|
|
38
|
+
for pattern in files_to_remove:
|
|
39
|
+
# Handle wildcard patterns
|
|
40
|
+
if any(char in pattern for char in "*?[]"):
|
|
41
|
+
matched_files = glob.glob(pattern, recursive=True)
|
|
42
|
+
for file_name in matched_files:
|
|
43
|
+
if os.path.isfile(file_name):
|
|
44
|
+
try:
|
|
45
|
+
if dry_run:
|
|
46
|
+
log_info_pair(
|
|
47
|
+
f"Would remove file: {file_name}",
|
|
48
|
+
f"将删除文件: {file_name}",
|
|
49
|
+
)
|
|
50
|
+
else:
|
|
51
|
+
os.remove(file_name)
|
|
52
|
+
log_info_pair(
|
|
53
|
+
f"Removed file: {file_name}", f"已删除文件: {file_name}"
|
|
54
|
+
)
|
|
55
|
+
except Exception as e:
|
|
56
|
+
log_info_pair(
|
|
57
|
+
f"Failed to remove file {file_name}: {e}",
|
|
58
|
+
f"删除文件 {file_name} 失败: {e}",
|
|
59
|
+
)
|
|
60
|
+
else:
|
|
61
|
+
# Handle exact file paths
|
|
62
|
+
if os.path.exists(pattern) and os.path.isfile(pattern):
|
|
63
|
+
try:
|
|
64
|
+
if dry_run:
|
|
65
|
+
log_info_pair(
|
|
66
|
+
f"Would remove file: {pattern}", f"将删除文件: {pattern}"
|
|
67
|
+
)
|
|
68
|
+
else:
|
|
69
|
+
os.remove(pattern)
|
|
70
|
+
log_info_pair(
|
|
71
|
+
f"Removed file: {pattern}", f"已删除文件: {pattern}"
|
|
72
|
+
)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
log_info_pair(
|
|
75
|
+
f"Failed to remove file {pattern}: {e}",
|
|
76
|
+
f"删除文件 {pattern} 失败: {e}",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _execute_cleaning_task(task: tuple[Callable, dict]) -> tuple[str, bool, Any]:
|
|
81
|
+
"""
|
|
82
|
+
Execute a cleaning task with error handling
|
|
83
|
+
执行清理任务并处理错误
|
|
84
|
+
"""
|
|
85
|
+
task_func, task_args = task
|
|
86
|
+
task_name = task_func.__name__
|
|
87
|
+
|
|
88
|
+
# Get human-readable task name
|
|
89
|
+
task_display_names = {
|
|
90
|
+
"clean_build_artifacts": "Build artifacts",
|
|
91
|
+
"clean_lfs_artifacts": "Git LFS artifacts",
|
|
92
|
+
"clean_rust_artifacts": "Rust artifacts",
|
|
93
|
+
"remove_virtual_environments": "Virtual environments",
|
|
94
|
+
"remove_auto_activation_scripts": "Auto-activation scripts",
|
|
95
|
+
"remove_rust_analyzer_dirs": "Rust analyzer directories",
|
|
96
|
+
"remove_uv_lock": "UV lock file",
|
|
97
|
+
}
|
|
98
|
+
display_name = task_display_names.get(task_name, task_name)
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
log_info_pair(
|
|
102
|
+
f"\n=== Starting: {display_name} ===", f"\n=== 开始: {display_name} ==="
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
task_func(**task_args)
|
|
106
|
+
|
|
107
|
+
log_info_pair(
|
|
108
|
+
f"=== Completed: {display_name} ===", f"=== 完成: {display_name} ==="
|
|
109
|
+
)
|
|
110
|
+
return task_name, True, None
|
|
111
|
+
except Exception as e:
|
|
112
|
+
log_info_pair(
|
|
113
|
+
f"=== Failed: {display_name} ===", f"=== 失败: {display_name} ==="
|
|
114
|
+
)
|
|
115
|
+
log_info_pair(f"Error: {e}", f"错误: {e}")
|
|
116
|
+
return task_name, False, e
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def clean_all_artifacts(
|
|
120
|
+
dry_run: bool = False, config: str | None = None, parallel: bool = False
|
|
121
|
+
) -> None:
|
|
122
|
+
"""
|
|
123
|
+
Clean all artifacts including virtual environment
|
|
124
|
+
清理所有产物,包括虚拟环境
|
|
125
|
+
"""
|
|
126
|
+
log_info_pair("Cleaning all artifacts...", "正在清理所有产物...")
|
|
127
|
+
|
|
128
|
+
# Log dry run mode
|
|
129
|
+
if dry_run:
|
|
130
|
+
log_info_pair(
|
|
131
|
+
"Dry run mode: No files will be deleted.", "Dry run模式:不会删除任何文件。"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Log parallel mode
|
|
135
|
+
if parallel:
|
|
136
|
+
log_info_pair("Parallel execution enabled.", "已启用并行执行。")
|
|
137
|
+
|
|
138
|
+
# Prepare cleaning tasks
|
|
139
|
+
tasks: list[tuple[Callable, dict]] = []
|
|
140
|
+
|
|
141
|
+
# Lazy imports to avoid circular imports (build.py imports from core.py)
|
|
142
|
+
from wl_cli.commands.clean.build import clean_build_artifacts
|
|
143
|
+
from wl_cli.commands.clean.lfs import clean_lfs_artifacts
|
|
144
|
+
from wl_cli.commands.clean.rust import (
|
|
145
|
+
clean_rust_artifacts as _clean_rust_artifacts,
|
|
146
|
+
)
|
|
147
|
+
from wl_cli.commands.clean.rust import (
|
|
148
|
+
remove_rust_analyzer_dirs,
|
|
149
|
+
)
|
|
150
|
+
from wl_cli.commands.clean.venv import (
|
|
151
|
+
remove_auto_activation_scripts,
|
|
152
|
+
remove_uv_lock,
|
|
153
|
+
remove_virtual_environments,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Add build artifacts cleaning
|
|
157
|
+
tasks.append((clean_build_artifacts, {"dry_run": dry_run, "config": config}))
|
|
158
|
+
|
|
159
|
+
# Add LFS cleaning
|
|
160
|
+
tasks.append((clean_lfs_artifacts, {"dry_run": dry_run}))
|
|
161
|
+
|
|
162
|
+
# Add Rust artifacts cleaning
|
|
163
|
+
tasks.append((_clean_rust_artifacts, {"dry_run": dry_run}))
|
|
164
|
+
|
|
165
|
+
# Add virtual environment cleaning
|
|
166
|
+
tasks.append((remove_virtual_environments, {"dry_run": dry_run}))
|
|
167
|
+
|
|
168
|
+
# Add auto-activation scripts removal
|
|
169
|
+
tasks.append((remove_auto_activation_scripts, {"dry_run": dry_run}))
|
|
170
|
+
|
|
171
|
+
# Add rust-analyzer directories removal
|
|
172
|
+
tasks.append((remove_rust_analyzer_dirs, {"dry_run": dry_run}))
|
|
173
|
+
|
|
174
|
+
# Add uv.lock file removal
|
|
175
|
+
tasks.append((remove_uv_lock, {"dry_run": dry_run}))
|
|
176
|
+
|
|
177
|
+
# Execute tasks
|
|
178
|
+
success_count = 0
|
|
179
|
+
failure_count = 0
|
|
180
|
+
total_tasks = len(tasks)
|
|
181
|
+
completed_tasks = 0
|
|
182
|
+
|
|
183
|
+
log_info_pair(
|
|
184
|
+
f"Starting {total_tasks} cleaning tasks...",
|
|
185
|
+
f"开始执行 {total_tasks} 个清理任务...",
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if parallel:
|
|
189
|
+
# Parallel execution
|
|
190
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
|
|
191
|
+
# Submit all tasks
|
|
192
|
+
future_to_task = {
|
|
193
|
+
executor.submit(_execute_cleaning_task, task): task for task in tasks
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
# Process results as they complete
|
|
197
|
+
for future in concurrent.futures.as_completed(future_to_task):
|
|
198
|
+
task_name, success, error = future.result()
|
|
199
|
+
completed_tasks += 1
|
|
200
|
+
|
|
201
|
+
if success:
|
|
202
|
+
success_count += 1
|
|
203
|
+
else:
|
|
204
|
+
failure_count += 1
|
|
205
|
+
|
|
206
|
+
# Show progress
|
|
207
|
+
progress_percent = (completed_tasks / total_tasks) * 100
|
|
208
|
+
log_info_pair(
|
|
209
|
+
f"Progress: {completed_tasks}/{total_tasks} ({progress_percent:.1f}%)",
|
|
210
|
+
f"进度: {completed_tasks}/{total_tasks} ({progress_percent:.1f}%)",
|
|
211
|
+
)
|
|
212
|
+
else:
|
|
213
|
+
# Sequential execution (default)
|
|
214
|
+
for task in tasks:
|
|
215
|
+
task_name, success, error = _execute_cleaning_task(task)
|
|
216
|
+
completed_tasks += 1
|
|
217
|
+
|
|
218
|
+
if success:
|
|
219
|
+
success_count += 1
|
|
220
|
+
else:
|
|
221
|
+
failure_count += 1
|
|
222
|
+
|
|
223
|
+
# Show progress
|
|
224
|
+
progress_percent = (completed_tasks / total_tasks) * 100
|
|
225
|
+
log_info_pair(
|
|
226
|
+
f"Progress: {completed_tasks}/{total_tasks} ({progress_percent:.1f}%)",
|
|
227
|
+
f"进度: {completed_tasks}/{total_tasks} ({progress_percent:.1f}%)",
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Log summary
|
|
231
|
+
log_info_pair(
|
|
232
|
+
f"Cleaning operations completed: {success_count} succeeded, {failure_count} failed",
|
|
233
|
+
f"清理操作完成: {success_count} 成功, {failure_count} 失败",
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
log_info_pair("All artifacts cleaning completed.", "所有产物清理完成。")
|
|
File without changes
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Dead code command module.
|
|
2
|
+
Main entry point for the dead code cleanup functionality.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from ....utils.root import RootKind
|
|
6
|
+
from ....utils.root_fallback import current_root
|
|
7
|
+
from .detector import DeadCodeDetector
|
|
8
|
+
from .fixer import DeadCodeFixer
|
|
9
|
+
from .reporter import DeadCodeReporter, TestSuggestion
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DeadCodeCommand:
|
|
13
|
+
"""Command for dead code cleanup."""
|
|
14
|
+
|
|
15
|
+
def __init__(self) -> None:
|
|
16
|
+
"""Initialize the dead code command."""
|
|
17
|
+
self.detector = DeadCodeDetector()
|
|
18
|
+
self.fixer = DeadCodeFixer()
|
|
19
|
+
|
|
20
|
+
def run(self, dry_run: bool = False, json: bool = False) -> int:
|
|
21
|
+
"""Run the dead code cleanup command.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
dry_run: Whether to only scan without fixing.
|
|
25
|
+
json: Whether to output JSON format.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Exit code (0 for success, 1 for error).
|
|
29
|
+
"""
|
|
30
|
+
# The reporter emits through ``log_*_pair``, which picks the language
|
|
31
|
+
# itself (#167), so no language is threaded through here.
|
|
32
|
+
reporter = DeadCodeReporter()
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
# Find project root: single-root consumer (#160), taken as a bare
|
|
36
|
+
# Path because that is this command's contract.
|
|
37
|
+
project_root = current_root(RootKind.PROJECT).path
|
|
38
|
+
|
|
39
|
+
# Step 1: Detect dead code
|
|
40
|
+
issues = self.detector.detect(project_root)
|
|
41
|
+
test_suggestions: list[TestSuggestion] = []
|
|
42
|
+
|
|
43
|
+
# Step 2: Fix issues
|
|
44
|
+
if not dry_run:
|
|
45
|
+
issues = self.fixer.fix(issues, project_root, dry_run=False)
|
|
46
|
+
|
|
47
|
+
# Step 3: Report
|
|
48
|
+
if json:
|
|
49
|
+
json_output = reporter.report_json(issues, test_suggestions)
|
|
50
|
+
print(json_output)
|
|
51
|
+
else:
|
|
52
|
+
reporter.report_cli(issues, test_suggestions, dry_run=dry_run)
|
|
53
|
+
|
|
54
|
+
return 0
|
|
55
|
+
|
|
56
|
+
except Exception as e:
|
|
57
|
+
from ....utils.logging import log_error_pair
|
|
58
|
+
|
|
59
|
+
if json:
|
|
60
|
+
# For JSON output, print error in JSON format without banners
|
|
61
|
+
import json as json_module
|
|
62
|
+
|
|
63
|
+
error_output = {"error": str(e), "error_zh": "运行死代码清理时出错"}
|
|
64
|
+
print(json_module.dumps(error_output, ensure_ascii=False))
|
|
65
|
+
else:
|
|
66
|
+
log_error_pair(
|
|
67
|
+
f"Error running dead code cleanup: {e}",
|
|
68
|
+
f"运行死代码清理时出错: {e}",
|
|
69
|
+
)
|
|
70
|
+
return 1
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dead code detector module.
|
|
3
|
+
Handles detection of dead code using Ruff.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from ....utils.logging import log_error_pair
|
|
11
|
+
from ....utils.subprocess_utils import SubprocessExecutor, SubprocessResult
|
|
12
|
+
from .reporter import DeadCodeIssue
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DeadCodeDetector:
|
|
16
|
+
"""Detector for dead code issues."""
|
|
17
|
+
|
|
18
|
+
def __init__(self) -> None:
|
|
19
|
+
"""Initialize the dead code detector."""
|
|
20
|
+
self.executor = SubprocessExecutor()
|
|
21
|
+
|
|
22
|
+
def detect(self, project_root: Path) -> list[DeadCodeIssue]:
|
|
23
|
+
"""
|
|
24
|
+
Detect dead code issues.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
project_root: Root directory of the project.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
List of DeadCodeIssue objects.
|
|
31
|
+
"""
|
|
32
|
+
issues: list[DeadCodeIssue] = []
|
|
33
|
+
|
|
34
|
+
# Run Ruff detection
|
|
35
|
+
ruff_issues = self._detect_with_ruff(project_root)
|
|
36
|
+
issues.extend(ruff_issues)
|
|
37
|
+
|
|
38
|
+
return issues
|
|
39
|
+
|
|
40
|
+
def _detect_with_ruff(self, project_root: Path) -> list[DeadCodeIssue]:
|
|
41
|
+
"""
|
|
42
|
+
Detect dead code using Ruff.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
project_root: Root directory of the project.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
List of DeadCodeIssue objects from Ruff.
|
|
49
|
+
"""
|
|
50
|
+
issues: list[DeadCodeIssue] = []
|
|
51
|
+
|
|
52
|
+
# Prepare Ruff command
|
|
53
|
+
cmd = [
|
|
54
|
+
"ruff",
|
|
55
|
+
"check",
|
|
56
|
+
"--select",
|
|
57
|
+
"F401,F841",
|
|
58
|
+
"--output-format",
|
|
59
|
+
"json",
|
|
60
|
+
".",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
# Run Ruff command
|
|
64
|
+
result = self.executor.run(command=cmd, cwd=project_root, quiet=True)
|
|
65
|
+
|
|
66
|
+
# Only a Ruff that never ran has nothing to say. Every other non-zero
|
|
67
|
+
# exit still carries a report: Ruff exits 1 exactly when it *found*
|
|
68
|
+
# violations, which is the case this detector exists for (#150).
|
|
69
|
+
if self._ruff_unavailable(result):
|
|
70
|
+
log_error_pair(
|
|
71
|
+
"Ruff is not installed. Please install Ruff to use dead code detection.",
|
|
72
|
+
"Ruff 未安装。请安装 Ruff 以使用死代码检测功能。",
|
|
73
|
+
)
|
|
74
|
+
return issues
|
|
75
|
+
|
|
76
|
+
# Parse Ruff output
|
|
77
|
+
if result.stdout:
|
|
78
|
+
try:
|
|
79
|
+
ruff_output = json.loads(result.stdout)
|
|
80
|
+
issues = self._parse_ruff_output(ruff_output)
|
|
81
|
+
except json.JSONDecodeError:
|
|
82
|
+
# If JSON parsing fails, return empty list
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
return issues
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def _ruff_unavailable(result: SubprocessResult) -> bool:
|
|
89
|
+
"""Whether the ``ruff`` process never got as far as reporting.
|
|
90
|
+
|
|
91
|
+
Deliberately **not** ``returncode != 0``: Ruff exits 1 whenever it has
|
|
92
|
+
violations, so that test silently discarded every finding (#150). The
|
|
93
|
+
discriminator is :attr:`SubprocessResult.launch_error`, the field set
|
|
94
|
+
only on the ``except`` arm of
|
|
95
|
+
:meth:`SubprocessExecutor._execute_command` -- i.e. when the
|
|
96
|
+
executable could not be started at all (#100/#101). ``127`` is kept as
|
|
97
|
+
a belt-and-braces spelling of a shell's "command not found", which
|
|
98
|
+
Ruff itself never returns.
|
|
99
|
+
|
|
100
|
+
The former ``"not found" in stderr`` sniff is gone with it: a substring
|
|
101
|
+
of a child's *stderr* cannot distinguish "no such tool" from a Ruff
|
|
102
|
+
that ran, reported something and exited 1.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
result: The finished Ruff subprocess result.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
True when Ruff never produced a report.
|
|
109
|
+
"""
|
|
110
|
+
if result.launch_error is not None:
|
|
111
|
+
return True
|
|
112
|
+
return result.returncode == 127
|
|
113
|
+
|
|
114
|
+
def _parse_ruff_output(
|
|
115
|
+
self, ruff_output: list[dict[str, Any]]
|
|
116
|
+
) -> list[DeadCodeIssue]:
|
|
117
|
+
"""
|
|
118
|
+
Parse Ruff JSON output into DeadCodeIssue objects.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
ruff_output: Ruff JSON output as a list of dictionaries.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
List of DeadCodeIssue objects.
|
|
125
|
+
"""
|
|
126
|
+
issues: list[DeadCodeIssue] = []
|
|
127
|
+
|
|
128
|
+
for item in ruff_output:
|
|
129
|
+
code = item.get("code", "")
|
|
130
|
+
filename = item.get("filename", "")
|
|
131
|
+
line = item.get("location", {}).get("row", None)
|
|
132
|
+
message = item.get("message", "")
|
|
133
|
+
|
|
134
|
+
if code == "F401":
|
|
135
|
+
# Unused import
|
|
136
|
+
# Extract import name from message like "`os` imported but unused"
|
|
137
|
+
name = message.split("`")[1] if "`" in message else None
|
|
138
|
+
issue = DeadCodeIssue(
|
|
139
|
+
type="unused_import",
|
|
140
|
+
file=filename,
|
|
141
|
+
line=line,
|
|
142
|
+
name=name,
|
|
143
|
+
severity="safe",
|
|
144
|
+
fixed=False,
|
|
145
|
+
)
|
|
146
|
+
issues.append(issue)
|
|
147
|
+
elif code == "F841":
|
|
148
|
+
# Unused variable
|
|
149
|
+
# Extract variable name from message like "Local variable `x` is assigned to but never used"
|
|
150
|
+
name = message.split("`")[1] if "`" in message else None
|
|
151
|
+
issue = DeadCodeIssue(
|
|
152
|
+
type="unused_variable",
|
|
153
|
+
file=filename,
|
|
154
|
+
line=line,
|
|
155
|
+
name=name,
|
|
156
|
+
severity="safe",
|
|
157
|
+
fixed=False,
|
|
158
|
+
)
|
|
159
|
+
issues.append(issue)
|
|
160
|
+
|
|
161
|
+
return issues
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dead code fixer module.
|
|
3
|
+
Handles automatic fixing of dead code issues using Ruff.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from ....utils.logging import log_error_pair
|
|
9
|
+
from ....utils.subprocess_utils import SubprocessExecutor, SubprocessResult
|
|
10
|
+
from .reporter import DeadCodeIssue
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DeadCodeFixer:
|
|
14
|
+
"""Fixer for dead code issues."""
|
|
15
|
+
|
|
16
|
+
def __init__(self) -> None:
|
|
17
|
+
"""Initialize the dead code fixer."""
|
|
18
|
+
self.executor = SubprocessExecutor()
|
|
19
|
+
|
|
20
|
+
def fix(
|
|
21
|
+
self, issues: list[DeadCodeIssue], project_root: Path, dry_run: bool = False
|
|
22
|
+
) -> list[DeadCodeIssue]:
|
|
23
|
+
"""
|
|
24
|
+
Fix dead code issues.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
issues: List of DeadCodeIssue objects.
|
|
28
|
+
project_root: Root directory of the project.
|
|
29
|
+
dry_run: Whether to only preview fixes without applying them.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
List of DeadCodeIssue objects with fixed flag updated.
|
|
33
|
+
"""
|
|
34
|
+
# Only fix safe issues (unused imports and variables)
|
|
35
|
+
safe_issues = [issue for issue in issues if issue.severity == "safe"]
|
|
36
|
+
|
|
37
|
+
if not safe_issues:
|
|
38
|
+
return issues
|
|
39
|
+
|
|
40
|
+
# Run Ruff with --fix (or not in dry-run)
|
|
41
|
+
result = self._run_ruff_fix(project_root, dry_run)
|
|
42
|
+
|
|
43
|
+
# Check if Ruff is not installed. ``--fix`` still exits 1 when it
|
|
44
|
+
# *repaired* something (or has repairs left to make), so the exit code
|
|
45
|
+
# alone cannot tell "Ruff ran and worked" from "Ruff never ran" -- see
|
|
46
|
+
# :meth:`_ruff_unavailable`, the same discriminator the detector uses
|
|
47
|
+
# (#150/#153).
|
|
48
|
+
if self._ruff_unavailable(result):
|
|
49
|
+
log_error_pair(
|
|
50
|
+
"Ruff is not installed. Please install Ruff to use dead code fixing.",
|
|
51
|
+
"Ruff 未安装。请安装 Ruff 以使用死代码修复功能。",
|
|
52
|
+
)
|
|
53
|
+
return issues
|
|
54
|
+
|
|
55
|
+
# Ruff is installed but it did *not* run to completion: any exit other
|
|
56
|
+
# than 0 (clean) or 1 (ran, and has findings) is a failure -- a bad
|
|
57
|
+
# config, an unusable argument -- that happened before a single repair
|
|
58
|
+
# was attempted. Falling through would be worse than useless: the next
|
|
59
|
+
# step, :meth:`_determine_fixed_issues`, re-runs the *detector*, which
|
|
60
|
+
# fails the same way and reports nothing left, so every safe issue would
|
|
61
|
+
# be re-labelled ``fixed`` with no repair behind it (#155 M1). Before
|
|
62
|
+
# #153 the coarse ``returncode != 0`` test happened to cover this path;
|
|
63
|
+
# the narrowed discriminator needs this branch to cover it again.
|
|
64
|
+
if result.returncode not in (0, 1):
|
|
65
|
+
log_error_pair(
|
|
66
|
+
f"Ruff failed with exit code {result.returncode}; nothing was fixed.",
|
|
67
|
+
f"Ruff 运行失败,退出码 {result.returncode};未修复任何死代码。",
|
|
68
|
+
)
|
|
69
|
+
return issues
|
|
70
|
+
|
|
71
|
+
if dry_run:
|
|
72
|
+
# In dry-run mode, we can't know exactly what was fixed,
|
|
73
|
+
# so we just return the issues as is
|
|
74
|
+
return issues
|
|
75
|
+
|
|
76
|
+
# Determine which issues were fixed by comparing with a fresh detection
|
|
77
|
+
fixed_issues = self._determine_fixed_issues(issues, project_root)
|
|
78
|
+
|
|
79
|
+
return fixed_issues
|
|
80
|
+
|
|
81
|
+
def _run_ruff_fix(self, project_root: Path, dry_run: bool) -> SubprocessResult:
|
|
82
|
+
"""
|
|
83
|
+
Run Ruff fix command.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
project_root: Root directory of the project.
|
|
87
|
+
dry_run: Whether to only preview fixes.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
SubprocessResult from Ruff execution.
|
|
91
|
+
"""
|
|
92
|
+
cmd = ["ruff", "check", "--select", "F401,F841"]
|
|
93
|
+
|
|
94
|
+
if not dry_run:
|
|
95
|
+
cmd.append("--fix")
|
|
96
|
+
|
|
97
|
+
cmd.append(".")
|
|
98
|
+
|
|
99
|
+
return self.executor.run(command=cmd, cwd=project_root, quiet=True)
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def _ruff_unavailable(result: SubprocessResult) -> bool:
|
|
103
|
+
"""Whether the ``ruff`` process never got as far as repairing anything.
|
|
104
|
+
|
|
105
|
+
Deliberately **not** ``returncode != 0``: ``ruff check --fix`` exits
|
|
106
|
+
**1** whenever it repaired something, still has unfixable findings, or
|
|
107
|
+
both -- so the old test short-circuited the fix path exactly when Ruff
|
|
108
|
+
had done its job, and no repaired issue was ever counted as ``fixed``
|
|
109
|
+
(#153, the sibling of the detector's #150). The discriminator is
|
|
110
|
+
:attr:`SubprocessResult.launch_error`, the field set only when the
|
|
111
|
+
executable could not be started at all (#100/#101). ``127`` is kept as
|
|
112
|
+
a belt-and-braces spelling of a shell's "command not found", which Ruff
|
|
113
|
+
itself never returns.
|
|
114
|
+
|
|
115
|
+
The former ``"not found" in stderr`` sniff is gone with it: a substring
|
|
116
|
+
of a child's *stderr* cannot distinguish "no such tool" from a Ruff that
|
|
117
|
+
ran and exited 1.
|
|
118
|
+
|
|
119
|
+
This predicate is spelled out identically on :class:`DeadCodeDetector`,
|
|
120
|
+
and a third copy of the same rule guards the ``uv`` launcher in
|
|
121
|
+
``commands/format/python_formatter.py``. Sharing one helper was weighed
|
|
122
|
+
for #155 (M2) and recorded, not done: the two-line body is the whole
|
|
123
|
+
function, the three call sites sit in two unrelated packages (so no
|
|
124
|
+
single home reads naturally -- the ``uv`` site is not about Ruff at
|
|
125
|
+
all), and the only package-neutral home, ``utils/subprocess_utils.py``,
|
|
126
|
+
is outside the files #155 touches.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
result: The finished Ruff subprocess result.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
True when Ruff never produced a report.
|
|
133
|
+
"""
|
|
134
|
+
if result.launch_error is not None:
|
|
135
|
+
return True
|
|
136
|
+
return result.returncode == 127
|
|
137
|
+
|
|
138
|
+
def _determine_fixed_issues(
|
|
139
|
+
self, original_issues: list[DeadCodeIssue], project_root: Path
|
|
140
|
+
) -> list[DeadCodeIssue]:
|
|
141
|
+
"""
|
|
142
|
+
Determine which issues were fixed by running detection again.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
original_issues: Original list of issues.
|
|
146
|
+
project_root: Root directory of the project.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
List of issues with fixed flag updated.
|
|
150
|
+
"""
|
|
151
|
+
from .detector import DeadCodeDetector
|
|
152
|
+
|
|
153
|
+
detector = DeadCodeDetector()
|
|
154
|
+
new_issues = detector.detect(project_root)
|
|
155
|
+
|
|
156
|
+
# Create a set of remaining issues for quick lookup
|
|
157
|
+
remaining_issues_set = set()
|
|
158
|
+
for issue in new_issues:
|
|
159
|
+
key = (issue.type, issue.file, issue.line, issue.name)
|
|
160
|
+
remaining_issues_set.add(key)
|
|
161
|
+
|
|
162
|
+
# Mark issues that are not in remaining_issues_set as fixed
|
|
163
|
+
result = []
|
|
164
|
+
for issue in original_issues:
|
|
165
|
+
key = (issue.type, issue.file, issue.line, issue.name)
|
|
166
|
+
issue_copy = DeadCodeIssue(
|
|
167
|
+
type=issue.type,
|
|
168
|
+
file=issue.file,
|
|
169
|
+
line=issue.line,
|
|
170
|
+
name=issue.name,
|
|
171
|
+
severity=issue.severity,
|
|
172
|
+
fixed=(key not in remaining_issues_set and issue.severity == "safe"),
|
|
173
|
+
)
|
|
174
|
+
result.append(issue_copy)
|
|
175
|
+
|
|
176
|
+
return result
|