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,297 @@
|
|
|
1
|
+
"""Lint command implementation using Click decorators."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from ...utils.logging import log_info_pair
|
|
9
|
+
from ...utils.root import RootKind
|
|
10
|
+
from ...utils.root_fallback import current_root
|
|
11
|
+
from ..format import FormatCommand
|
|
12
|
+
from .lint_executor import LintExecutor
|
|
13
|
+
from .lint_reporter import LintReporter
|
|
14
|
+
from .lint_run import LintRun
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _anchor_scope_path(project_root: Path, token: str) -> str:
|
|
18
|
+
"""Anchor one ``wl lint`` scope *token* on the directory it means (#188).
|
|
19
|
+
|
|
20
|
+
The format prefix runs from the project root, so a bare ``src`` must mean
|
|
21
|
+
``root/src`` no matter where the CLI was started. Three cases, in order:
|
|
22
|
+
|
|
23
|
+
* an absolute token is already unambiguous;
|
|
24
|
+
* a token real under *project_root* becomes ``root/token``;
|
|
25
|
+
* a token real only against the cwd -- ``cd pkg && wl lint commands`` --
|
|
26
|
+
becomes its resolved absolute path, the #179 floor;
|
|
27
|
+
* anything else is returned untouched, so ruff and mypy report it instead
|
|
28
|
+
of lint silently checking less than asked (the #188 drop bug).
|
|
29
|
+
"""
|
|
30
|
+
candidate = Path(token)
|
|
31
|
+
if candidate.is_absolute():
|
|
32
|
+
return token
|
|
33
|
+
rooted = project_root / token
|
|
34
|
+
try:
|
|
35
|
+
if rooted.exists():
|
|
36
|
+
return str(rooted)
|
|
37
|
+
if candidate.exists():
|
|
38
|
+
return str(candidate.resolve())
|
|
39
|
+
except OSError:
|
|
40
|
+
# An unreadable path must not crash command construction; ruff reports
|
|
41
|
+
# it once it receives the token.
|
|
42
|
+
pass
|
|
43
|
+
return token
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class LintFormatter:
|
|
47
|
+
"""Formatter for code before linting."""
|
|
48
|
+
|
|
49
|
+
def format_code(
|
|
50
|
+
self, project_root: Path, paths: list[str] | None, quiet: bool
|
|
51
|
+
) -> int:
|
|
52
|
+
"""Format code before linting; returns the format prefix's exit code.
|
|
53
|
+
|
|
54
|
+
``for_lint`` makes the prefix format only (#180): the coordinator skips
|
|
55
|
+
its pre-commit run, so ``wl lint`` owns that verdict, while a real
|
|
56
|
+
``ruff``/``tool_missing`` failure still comes back non-zero (#166).
|
|
57
|
+
A ``ruff check`` that leaves unfixable violations is *not* such a
|
|
58
|
+
failure (#188): the coordinator maps it to ``0`` so lint reaches its
|
|
59
|
+
own ruff run, mypy, pre-commit and the report.
|
|
60
|
+
|
|
61
|
+
``project_root`` is passed down (#188), so the scope means the same
|
|
62
|
+
directory wherever the CLI was started from. Each token is anchored
|
|
63
|
+
before it leaves: absolute as typed, else under *project_root* when it
|
|
64
|
+
exists there, else resolved against the cwd (``cd pkg && wl lint
|
|
65
|
+
commands``, the #179 floor), else left untouched for ruff to report.
|
|
66
|
+
No token is dropped -- silently linting less than asked was the bug.
|
|
67
|
+
"""
|
|
68
|
+
if not quiet:
|
|
69
|
+
log_info_pair(
|
|
70
|
+
"Formatting code before linting...", "在静态检查前先格式化代码..."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
format_cmd = FormatCommand()
|
|
74
|
+
valid_paths: list[str] | None = None
|
|
75
|
+
if paths:
|
|
76
|
+
valid_paths = [_anchor_scope_path(project_root, p) for p in paths]
|
|
77
|
+
return format_cmd.run(
|
|
78
|
+
quiet=quiet,
|
|
79
|
+
unsafe=True,
|
|
80
|
+
paths=valid_paths,
|
|
81
|
+
for_lint=True,
|
|
82
|
+
project_root=project_root,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class LintCommand:
|
|
87
|
+
"""Implementation of the lint command.
|
|
88
|
+
|
|
89
|
+
:meth:`execute` returns the process exit code instead of calling
|
|
90
|
+
``sys.exit`` itself -- the same contract as ``GateCommand.execute``. Only
|
|
91
|
+
the Click wrapper below turns a non-zero code into a ``SystemExit``, so the
|
|
92
|
+
command stays callable, and testable, as a plain function.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
def __init__(self) -> None:
|
|
96
|
+
"""Initialize the lint command implementation.
|
|
97
|
+
|
|
98
|
+
The ``-> None`` is load-bearing for typing, not decoration: without an
|
|
99
|
+
annotation mypy skips this body, the three collaborators stay ``Any``,
|
|
100
|
+
and :meth:`execute` would be returning an unchecked value from inside a
|
|
101
|
+
``-> int`` signature.
|
|
102
|
+
"""
|
|
103
|
+
self.executor = LintExecutor()
|
|
104
|
+
self.formatter = LintFormatter()
|
|
105
|
+
self.reporter = LintReporter()
|
|
106
|
+
|
|
107
|
+
def execute(
|
|
108
|
+
self,
|
|
109
|
+
paths: tuple[str, ...] = (),
|
|
110
|
+
quiet: bool = False,
|
|
111
|
+
fix: bool = False,
|
|
112
|
+
noreport: bool = False,
|
|
113
|
+
report: bool = False,
|
|
114
|
+
) -> int:
|
|
115
|
+
"""
|
|
116
|
+
Lint code - equivalent to make lint
|
|
117
|
+
代码静态检查 - 等效于 make lint
|
|
118
|
+
|
|
119
|
+
Returns the exit code: the format prefix's, ruff's, or 1 when a tool
|
|
120
|
+
that was asked to run produced no verdict (ruff missing or signalled;
|
|
121
|
+
with the report, a mypy that could not start, was signalled, exited
|
|
122
|
+
with code ``>= 2``, or exited non-zero with no diagnostics too). mypy
|
|
123
|
+
*type errors* do not change the exit code, and an unexpected exception
|
|
124
|
+
anywhere returns 1 (fail-closed).
|
|
125
|
+
"""
|
|
126
|
+
if not quiet:
|
|
127
|
+
self._log_info("Linting code...", "正在进行代码静态检查...")
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
# Single-root consumer (#160): the one fallback policy, taken as a
|
|
131
|
+
# bare Path because that is this command's contract.
|
|
132
|
+
project_root = current_root(RootKind.PROJECT).path
|
|
133
|
+
|
|
134
|
+
format_exit_code = self.formatter.format_code(
|
|
135
|
+
project_root, list(paths) if paths else None, quiet
|
|
136
|
+
)
|
|
137
|
+
if format_exit_code:
|
|
138
|
+
# Exit contract: any non-zero prefix aborts lint, and the only
|
|
139
|
+
# degradation is ``pre_commit``-only -- the coordinator's
|
|
140
|
+
# ``for_lint`` mapping already reduced that verdict to 0. A
|
|
141
|
+
# non-zero code therefore means ruff/tool_missing and lint
|
|
142
|
+
# stops before running ruff again (#166). The catch-all below
|
|
143
|
+
# is fail-closed the same way: an unexpected exception returns
|
|
144
|
+
# 1 instead of running the remaining tools with a partial
|
|
145
|
+
# prefix verdict (#172).
|
|
146
|
+
return format_exit_code
|
|
147
|
+
|
|
148
|
+
# Audit patch 3: the caller picks the tool set. ``--noreport`` runs
|
|
149
|
+
# ruff alone -- no mypy, no pre-commit, no structured output;
|
|
150
|
+
# ``--report`` and the default path also need mypy and pre-commit
|
|
151
|
+
# for the report, plus ruff's structured output to group its
|
|
152
|
+
# findings into the report's per-directory sections. The format
|
|
153
|
+
# prefix above only formats (#180): it never runs pre-commit, so
|
|
154
|
+
# this tool set is the single owner of that hook run -- exactly
|
|
155
|
+
# once by default and with ``--report``, never with ``--noreport``.
|
|
156
|
+
with_report = report or not noreport
|
|
157
|
+
run = self.executor.run_all(
|
|
158
|
+
project_root,
|
|
159
|
+
list(paths) if paths else None,
|
|
160
|
+
quiet=quiet,
|
|
161
|
+
fix=fix,
|
|
162
|
+
with_mypy=with_report,
|
|
163
|
+
with_pre_commit=with_report,
|
|
164
|
+
structured_ruff=with_report,
|
|
165
|
+
format_code=format_exit_code,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
if with_report:
|
|
169
|
+
self.reporter.render(run)
|
|
170
|
+
|
|
171
|
+
self._handle_result(run, quiet)
|
|
172
|
+
|
|
173
|
+
return run.exit_code
|
|
174
|
+
|
|
175
|
+
except FileNotFoundError:
|
|
176
|
+
self._handle_file_not_found_error(quiet)
|
|
177
|
+
return 1
|
|
178
|
+
except Exception as e:
|
|
179
|
+
self._handle_general_error(e, quiet)
|
|
180
|
+
return 1
|
|
181
|
+
|
|
182
|
+
def _log_info(self, en_msg: str, zh_msg: str) -> None:
|
|
183
|
+
"""Log info message in both English and Chinese."""
|
|
184
|
+
log_info_pair(
|
|
185
|
+
en_msg,
|
|
186
|
+
zh_msg,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def _handle_result(self, run: LintRun, quiet: bool) -> None:
|
|
190
|
+
"""Show what failed -- ruff's findings, mypy, hooks -- or clean.
|
|
191
|
+
|
|
192
|
+
A non-zero code caused by a mypy launch failure or signal must not
|
|
193
|
+
announce ruff issues: there is no ruff output to show, so the old
|
|
194
|
+
branch printed the heading and nothing else (#172). When ruff *does*
|
|
195
|
+
have findings, they stay the headline, but a mypy non-verdict is
|
|
196
|
+
still printed under them (review n3).
|
|
197
|
+
|
|
198
|
+
A failing pre-commit hook is printed as its own verdict (#188). It is
|
|
199
|
+
part of :attr:`LintRun.exit_code` now, and the terminal used to print
|
|
200
|
+
"completed successfully!" for exactly the run the report rendered as
|
|
201
|
+
``Failed``; that success line is now reachable only from ``exit_code
|
|
202
|
+
== 0``, i.e. never on a hook failure.
|
|
203
|
+
"""
|
|
204
|
+
if run.exit_code != 0 and not quiet:
|
|
205
|
+
if run.issues_found:
|
|
206
|
+
self._log_info(
|
|
207
|
+
"Linting completed with issues:", "代码静态检查发现以下问题:"
|
|
208
|
+
)
|
|
209
|
+
# ``issue_text`` hands back ruff's ``--output-format full``
|
|
210
|
+
# text verbatim; the reporter owns the rendering (#170).
|
|
211
|
+
issues = self.reporter.issue_text(run)
|
|
212
|
+
if issues:
|
|
213
|
+
print(issues)
|
|
214
|
+
if run.ruff.stderr:
|
|
215
|
+
print(run.ruff.stderr)
|
|
216
|
+
# The report is the source of truth, but a mypy that never
|
|
217
|
+
# produced a verdict should not vanish from the terminal just
|
|
218
|
+
# because ruff also had findings (review n3).
|
|
219
|
+
if run.mypy_failure is not None:
|
|
220
|
+
print(run.mypy_failure)
|
|
221
|
+
if run.pre_commit is not None and run.pre_commit.hooks_failed:
|
|
222
|
+
self._log_info("Pre-commit hooks failed:", "pre-commit 钩子检查未通过:")
|
|
223
|
+
self._print_pre_commit_verdict(run)
|
|
224
|
+
elif run.exit_code == 0 and not quiet:
|
|
225
|
+
self._log_info(
|
|
226
|
+
"Code linting completed successfully!", "代码静态检查成功完成!"
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
def _print_pre_commit_verdict(self, run: LintRun) -> None:
|
|
230
|
+
"""Print the blocks the hook verdict was computed from (#188).
|
|
231
|
+
|
|
232
|
+
``failed_blocks`` is the same source :attr:`PreCommitRun.hooks_failed`
|
|
233
|
+
reads, so the terminal cannot show an auto-fix block the verdict
|
|
234
|
+
ignored. A run that failed before printing any hook block falls back to
|
|
235
|
+
its raw output, the same shape ``wl format`` uses.
|
|
236
|
+
"""
|
|
237
|
+
pre_commit = run.pre_commit
|
|
238
|
+
if pre_commit is None: # pragma: no cover - caller guards on it
|
|
239
|
+
return
|
|
240
|
+
blocks = pre_commit.failed_blocks
|
|
241
|
+
if blocks:
|
|
242
|
+
print("\n\n".join("\n".join(block) for block in blocks))
|
|
243
|
+
elif pre_commit.combined_output.strip():
|
|
244
|
+
print(pre_commit.combined_output.strip())
|
|
245
|
+
|
|
246
|
+
def _handle_file_not_found_error(self, quiet: bool) -> None:
|
|
247
|
+
"""Handle FileNotFoundError."""
|
|
248
|
+
if not quiet:
|
|
249
|
+
self._log_info(
|
|
250
|
+
"Error: ruff is not installed or not found in PATH",
|
|
251
|
+
"错误:未安装 ruff 或在 PATH 中找不到",
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
def _handle_general_error(self, e: Exception, quiet: bool) -> None:
|
|
255
|
+
"""Handle general exceptions."""
|
|
256
|
+
if not quiet:
|
|
257
|
+
self._log_info(f"Error during linting: {e}", f"错误:静态检查期间出错: {e}")
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@click.command()
|
|
261
|
+
@click.argument("paths", nargs=-1)
|
|
262
|
+
@click.option("-q", "--quiet", is_flag=True, help="Suppress detailed output")
|
|
263
|
+
@click.option("--fix", is_flag=True, help="Automatically fix lint errors")
|
|
264
|
+
@click.option(
|
|
265
|
+
"--noreport",
|
|
266
|
+
is_flag=True,
|
|
267
|
+
help="Do not generate lint report in todos folder",
|
|
268
|
+
)
|
|
269
|
+
@click.option(
|
|
270
|
+
"--report",
|
|
271
|
+
is_flag=True,
|
|
272
|
+
help="Generate lint report in todos folder",
|
|
273
|
+
)
|
|
274
|
+
@click.option(
|
|
275
|
+
"--pre-commit-config",
|
|
276
|
+
type=str,
|
|
277
|
+
help="Pre-commit configuration file (for compatibility)",
|
|
278
|
+
)
|
|
279
|
+
def lint_command(
|
|
280
|
+
paths: tuple[str, ...],
|
|
281
|
+
quiet: bool,
|
|
282
|
+
fix: bool,
|
|
283
|
+
noreport: bool,
|
|
284
|
+
report: bool,
|
|
285
|
+
pre_commit_config: str | None = None,
|
|
286
|
+
) -> None:
|
|
287
|
+
"""Lint command Click wrapper."""
|
|
288
|
+
# The command body returns an exit code; turning a non-zero one into the
|
|
289
|
+
# process's own exit status is this wrapper's job alone (the ``wl gate``
|
|
290
|
+
# shape). Zero returns normally, so Click exits 0. Kept as comments, not
|
|
291
|
+
# docstring: Click renders the docstring into ``wl lint --help`` (#142).
|
|
292
|
+
cmd = LintCommand()
|
|
293
|
+
exit_code = cmd.execute(
|
|
294
|
+
paths=paths, quiet=quiet, fix=fix, noreport=noreport, report=report
|
|
295
|
+
)
|
|
296
|
+
if exit_code != 0:
|
|
297
|
+
sys.exit(exit_code)
|