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.
Files changed (320) hide show
  1. py_wlcli-0.8.0.dist-info/METADATA +139 -0
  2. py_wlcli-0.8.0.dist-info/RECORD +320 -0
  3. py_wlcli-0.8.0.dist-info/WHEEL +4 -0
  4. py_wlcli-0.8.0.dist-info/entry_points.txt +2 -0
  5. py_wlcli-0.8.0.dist-info/licenses/LICENSE +21 -0
  6. wl_cli/__init__.py +27 -0
  7. wl_cli/__main__.py +130 -0
  8. wl_cli/capabilities.py +79 -0
  9. wl_cli/cc/__init__.py +5 -0
  10. wl_cli/cc/checks.py +1019 -0
  11. wl_cli/cc/cli.py +370 -0
  12. wl_cli/cc/mutate.py +1350 -0
  13. wl_cli/cli_click.py +156 -0
  14. wl_cli/commands/__init__.py +84 -0
  15. wl_cli/commands/base.py +80 -0
  16. wl_cli/commands/buildcommands/__init__.py +1 -0
  17. wl_cli/commands/buildcommands/build.py +71 -0
  18. wl_cli/commands/buildcommands/build_utils.py +47 -0
  19. wl_cli/commands/buildcommands/builders/__init__.py +18 -0
  20. wl_cli/commands/buildcommands/builders/cleanup.py +35 -0
  21. wl_cli/commands/buildcommands/builders/coordinator.py +122 -0
  22. wl_cli/commands/buildcommands/builders/environment.py +81 -0
  23. wl_cli/commands/buildcommands/builders/rust_builder.py +39 -0
  24. wl_cli/commands/buildcommands/builders/stubs.py +58 -0
  25. wl_cli/commands/buildcommands/dist.py +326 -0
  26. wl_cli/commands/buildcommands/test.py +89 -0
  27. wl_cli/commands/clean/__init__.py +1 -0
  28. wl_cli/commands/clean/build.py +151 -0
  29. wl_cli/commands/clean/clean.py +294 -0
  30. wl_cli/commands/clean/clean_utils.py +77 -0
  31. wl_cli/commands/clean/core.py +236 -0
  32. wl_cli/commands/clean/dead_code/__init__.py +0 -0
  33. wl_cli/commands/clean/dead_code/dead_code_command.py +70 -0
  34. wl_cli/commands/clean/dead_code/detector.py +161 -0
  35. wl_cli/commands/clean/dead_code/fixer.py +176 -0
  36. wl_cli/commands/clean/dead_code/reporter.py +295 -0
  37. wl_cli/commands/clean/dead_code/utils.py +0 -0
  38. wl_cli/commands/clean/lfs.py +215 -0
  39. wl_cli/commands/clean/python.py +81 -0
  40. wl_cli/commands/clean/rust.py +91 -0
  41. wl_cli/commands/clean/utils/__init__.py +34 -0
  42. wl_cli/commands/clean/venv.py +91 -0
  43. wl_cli/commands/cleanup.py +73 -0
  44. wl_cli/commands/click_base.py +79 -0
  45. wl_cli/commands/config/__init__.py +1 -0
  46. wl_cli/commands/config/config.py +291 -0
  47. wl_cli/commands/debug/__init__.py +6 -0
  48. wl_cli/commands/debug/debug.py +309 -0
  49. wl_cli/commands/debug/debug_cache.py +86 -0
  50. wl_cli/commands/debug/debug_subagent.py +156 -0
  51. wl_cli/commands/debug/output_formatter.py +165 -0
  52. wl_cli/commands/debug/source_hasher.py +92 -0
  53. wl_cli/commands/format/__init__.py +13 -0
  54. wl_cli/commands/format/format_cache_adapter.py +125 -0
  55. wl_cli/commands/format/format_command.py +197 -0
  56. wl_cli/commands/format/format_coordinator.py +191 -0
  57. wl_cli/commands/format/format_executor.py +470 -0
  58. wl_cli/commands/format/format_outcome.py +44 -0
  59. wl_cli/commands/format/format_path_handler.py +1016 -0
  60. wl_cli/commands/format/format_progress.py +62 -0
  61. wl_cli/commands/format/format_reporter.py +31 -0
  62. wl_cli/commands/format/python_formatter.py +506 -0
  63. wl_cli/commands/format/rust_formatter.py +106 -0
  64. wl_cli/commands/gate/__init__.py +1 -0
  65. wl_cli/commands/gate/gate_command.py +862 -0
  66. wl_cli/commands/gate/preflight.py +445 -0
  67. wl_cli/commands/git/__init__.py +1 -0
  68. wl_cli/commands/git/config.py +169 -0
  69. wl_cli/commands/git/git_facts.py +60 -0
  70. wl_cli/commands/git/git_flow.py +86 -0
  71. wl_cli/commands/git/hook.py +112 -0
  72. wl_cli/commands/git/policy.py +216 -0
  73. wl_cli/commands/git/push_update.py +82 -0
  74. wl_cli/commands/initenv/__init__.py +1 -0
  75. wl_cli/commands/initenv/common/__init__.py +23 -0
  76. wl_cli/commands/initenv/common/check_git_bash.py +78 -0
  77. wl_cli/commands/initenv/common/error_handler.py +68 -0
  78. wl_cli/commands/initenv/common/exceptions.py +52 -0
  79. wl_cli/commands/initenv/components/__init__.py +4 -0
  80. wl_cli/commands/initenv/components/cargo_sync.py +240 -0
  81. wl_cli/commands/initenv/components/config_manager.py +106 -0
  82. wl_cli/commands/initenv/components/directory_utils.py +71 -0
  83. wl_cli/commands/initenv/components/git/__init__.py +9 -0
  84. wl_cli/commands/initenv/components/git/branch_creator.py +143 -0
  85. wl_cli/commands/initenv/components/git/git_flow_interface.py +24 -0
  86. wl_cli/commands/initenv/components/git/git_flow_manager.py +159 -0
  87. wl_cli/commands/initenv/components/git/git_lfs_manager.py +272 -0
  88. wl_cli/commands/initenv/components/git/git_utility.py +254 -0
  89. wl_cli/commands/initenv/components/git/gitignore_manager.py +53 -0
  90. wl_cli/commands/initenv/components/git_initializer.py +212 -0
  91. wl_cli/commands/initenv/components/i18n_manager.py +104 -0
  92. wl_cli/commands/initenv/components/log_manager.py +241 -0
  93. wl_cli/commands/initenv/components/project_structure/__init__.py +23 -0
  94. wl_cli/commands/initenv/components/project_structure/config_generator.py +360 -0
  95. wl_cli/commands/initenv/components/project_structure/directory_creator.py +184 -0
  96. wl_cli/commands/initenv/components/project_structure/hook_source.py +110 -0
  97. wl_cli/commands/initenv/components/project_structure/hooks_manager.py +995 -0
  98. wl_cli/commands/initenv/components/project_structure/readme_handler.py +56 -0
  99. wl_cli/commands/initenv/components/project_structure/rust_files_handler.py +30 -0
  100. wl_cli/commands/initenv/components/project_structure/setup_handler.py +103 -0
  101. wl_cli/commands/initenv/components/project_structure/sop_generator.py +73 -0
  102. wl_cli/commands/initenv/components/project_structure/template_copier.py +104 -0
  103. wl_cli/commands/initenv/components/project_structure/template_manager.py +195 -0
  104. wl_cli/commands/initenv/components/pyproject_generator.py +194 -0
  105. wl_cli/commands/initenv/components/rust_initializer.py +145 -0
  106. wl_cli/commands/initenv/components/symlink_cleaner.py +99 -0
  107. wl_cli/commands/initenv/components/symlink_manager.py +52 -0
  108. wl_cli/commands/initenv/components/venv_manager.py +118 -0
  109. wl_cli/commands/initenv/initenv.py +169 -0
  110. wl_cli/commands/initenv/services/__init__.py +11 -0
  111. wl_cli/commands/initenv/services/environment_initializer.py +288 -0
  112. wl_cli/commands/initenv/services/git_lfs_initializer.py +26 -0
  113. wl_cli/commands/initenv/services/project_structure_initializer.py +52 -0
  114. wl_cli/commands/lfs/README.md +221 -0
  115. wl_cli/commands/lfs/__init__.py +5 -0
  116. wl_cli/commands/lfs/cli.py +106 -0
  117. wl_cli/commands/lfs/config.py +164 -0
  118. wl_cli/commands/lfs/hook.py +61 -0
  119. wl_cli/commands/lfs/initializer.py +531 -0
  120. wl_cli/commands/lfs/manager.py +95 -0
  121. wl_cli/commands/lfs/policy.py +165 -0
  122. wl_cli/commands/lfs/watcher.py +294 -0
  123. wl_cli/commands/lint/__init__.py +1 -0
  124. wl_cli/commands/lint/lint_command.py +297 -0
  125. wl_cli/commands/lint/lint_executor.py +409 -0
  126. wl_cli/commands/lint/lint_reporter.py +579 -0
  127. wl_cli/commands/lint/lint_run.py +258 -0
  128. wl_cli/commands/lint/mypy_config.py +90 -0
  129. wl_cli/commands/registry.py +103 -0
  130. wl_cli/commands/self/__init__.py +7 -0
  131. wl_cli/commands/self/common/__init__.py +8 -0
  132. wl_cli/commands/self/common/capabilities.py +30 -0
  133. wl_cli/commands/self/common/config.py +27 -0
  134. wl_cli/commands/self/common/version_cache.py +85 -0
  135. wl_cli/commands/self/handlers/__init__.py +19 -0
  136. wl_cli/commands/self/handlers/base_handler.py +28 -0
  137. wl_cli/commands/self/handlers/install_handler.py +85 -0
  138. wl_cli/commands/self/handlers/status_handler.py +66 -0
  139. wl_cli/commands/self/handlers/uninstall_handler.py +58 -0
  140. wl_cli/commands/self/handlers/update_handler.py +102 -0
  141. wl_cli/commands/self/handlers/version_handler.py +16 -0
  142. wl_cli/commands/self/self_command.py +237 -0
  143. wl_cli/commands/self/services/__init__.py +15 -0
  144. wl_cli/commands/self/services/installation_manager.py +187 -0
  145. wl_cli/commands/self/services/local_installer.py +165 -0
  146. wl_cli/commands/self/services/pypi_updater.py +278 -0
  147. wl_cli/commands/self/services/windows_update.py +174 -0
  148. wl_cli/commands/testcommands/__init__.py +8 -0
  149. wl_cli/commands/testcommands/base_dependency_analyzer.py +198 -0
  150. wl_cli/commands/testcommands/batch_processor.py +278 -0
  151. wl_cli/commands/testcommands/cache_manager.py +1156 -0
  152. wl_cli/commands/testcommands/coverage_analyzer.py +955 -0
  153. wl_cli/commands/testcommands/coverage_cache_manager.py +183 -0
  154. wl_cli/commands/testcommands/progress_manager.py +79 -0
  155. wl_cli/commands/testcommands/python_env_detector.py +207 -0
  156. wl_cli/commands/testcommands/report_generator.py +101 -0
  157. wl_cli/commands/testcommands/single_test_executor.py +250 -0
  158. wl_cli/commands/testcommands/speed_checker.py +118 -0
  159. wl_cli/commands/testcommands/test_command_builder.py +165 -0
  160. wl_cli/commands/testcommands/test_commands.py +786 -0
  161. wl_cli/commands/testcommands/test_reporter.py +181 -0
  162. wl_cli/commands/testcommands/test_result_handler.py +96 -0
  163. wl_cli/commands/testcommands/test_runner.py +1343 -0
  164. wl_cli/commands/testcommands/test_utils.py +197 -0
  165. wl_cli/commands/testcommands/utils/__init__.py +17 -0
  166. wl_cli/commands/testcommands/utils/coverage_parser.py +98 -0
  167. wl_cli/commands/testcommands/utils/env_info.py +36 -0
  168. wl_cli/commands/testcommands/utils/junit_parser.py +75 -0
  169. wl_cli/commands/testcommands/utils/markdown_utils.py +20 -0
  170. wl_cli/commands/testcommands/utils/project_root.py +61 -0
  171. wl_cli/commands/update/__init__.py +9 -0
  172. wl_cli/commands/update/services/__init__.py +21 -0
  173. wl_cli/commands/update/services/backup_manager.py +207 -0
  174. wl_cli/commands/update/services/component_manager.py +83 -0
  175. wl_cli/commands/update/services/conflict_resolver.py +189 -0
  176. wl_cli/commands/update/services/vendors_updater.py +85 -0
  177. wl_cli/commands/update/services/version_tracker.py +127 -0
  178. wl_cli/commands/update/update_command.py +270 -0
  179. wl_cli/commands/update/utils/__init__.py +3 -0
  180. wl_cli/commands/version/__init__.py +9 -0
  181. wl_cli/commands/version/version_command.py +155 -0
  182. wl_cli/commands/workflow.py +477 -0
  183. wl_cli/env/__init__.py +5 -0
  184. wl_cli/env/cli.py +167 -0
  185. wl_cli/env_status.py +157 -0
  186. wl_cli/envelope.py +91 -0
  187. wl_cli/exceptions.py +85 -0
  188. wl_cli/lib/__init__.py +3 -0
  189. wl_cli/lib/rust_utils.py +35 -0
  190. wl_cli/mcp/__init__.py +6 -0
  191. wl_cli/mcp/capabilities.py +19 -0
  192. wl_cli/mcp/cli.py +22 -0
  193. wl_cli/mcp/gitlab.py +553 -0
  194. wl_cli/mcp/k8s.py +344 -0
  195. wl_cli/mcp/protocol.py +159 -0
  196. wl_cli/platform.py +139 -0
  197. wl_cli/quality/__init__.py +18 -0
  198. wl_cli/utils/__init__.py +7 -0
  199. wl_cli/utils/cache.py +491 -0
  200. wl_cli/utils/cache_ledger.py +355 -0
  201. wl_cli/utils/cache_store.py +546 -0
  202. wl_cli/utils/config/__init__.py +27 -0
  203. wl_cli/utils/config/config_loader.py +163 -0
  204. wl_cli/utils/config/core.py +862 -0
  205. wl_cli/utils/config/defaults.py +142 -0
  206. wl_cli/utils/config/env_exporter.py +86 -0
  207. wl_cli/utils/config/env_parser.py +108 -0
  208. wl_cli/utils/config/validation.py +280 -0
  209. wl_cli/utils/error_handler.py +165 -0
  210. wl_cli/utils/file_copier.py +56 -0
  211. wl_cli/utils/file_operations.py +506 -0
  212. wl_cli/utils/language_utils.py +122 -0
  213. wl_cli/utils/lazy_import.py +240 -0
  214. wl_cli/utils/log_levels.py +35 -0
  215. wl_cli/utils/log_rotators.py +74 -0
  216. wl_cli/utils/logging.py +458 -0
  217. wl_cli/utils/output_context.py +142 -0
  218. wl_cli/utils/performance/__init__.py +1 -0
  219. wl_cli/utils/performance/decorators.py +111 -0
  220. wl_cli/utils/performance/exporter.py +38 -0
  221. wl_cli/utils/performance/models.py +16 -0
  222. wl_cli/utils/performance/monitor.py +161 -0
  223. wl_cli/utils/performance_monitor.py +32 -0
  224. wl_cli/utils/platform_adapter.py +131 -0
  225. wl_cli/utils/pre_commit.py +266 -0
  226. wl_cli/utils/pre_commit_config.py +72 -0
  227. wl_cli/utils/root.py +521 -0
  228. wl_cli/utils/root_fallback.py +256 -0
  229. wl_cli/utils/run_evidence.py +236 -0
  230. wl_cli/utils/structured_logger.py +18 -0
  231. wl_cli/utils/structured_logging/__init__.py +19 -0
  232. wl_cli/utils/structured_logging/config.py +337 -0
  233. wl_cli/utils/structured_logging/core.py +177 -0
  234. wl_cli/utils/structured_logging/env.py +56 -0
  235. wl_cli/utils/structured_logging/filtering.py +75 -0
  236. wl_cli/utils/structured_logging/filters.py +55 -0
  237. wl_cli/utils/structured_logging/formatters.py +251 -0
  238. wl_cli/utils/structured_logging/handlers.py +308 -0
  239. wl_cli/utils/structured_logging/logger.py +354 -0
  240. wl_cli/utils/subprocess.py +71 -0
  241. wl_cli/utils/subprocess_utils.py +260 -0
  242. wl_cli/utils/test_utils/__init__.py +5 -0
  243. wl_cli/utils/test_utils/test_filter.py +36 -0
  244. wl_cli/utils/uv_run.py +79 -0
  245. wl_cli/utils/uv_tool.py +117 -0
  246. wl_cli/utils/uv_tree.py +71 -0
  247. wl_cli/utils/uv_workspace.py +174 -0
  248. wl_cli/utils/version/__init__.py +15 -0
  249. wl_cli/utils/version/pypi_version_checker.py +95 -0
  250. wl_cli/utils/version/version_comparator.py +133 -0
  251. wl_cli/utils/version/version_detectors.py +312 -0
  252. wl_cli/utils/version/version_service.py +131 -0
  253. wl_cli/utils/version/version_updater.py +198 -0
  254. wl_cli/utils/wl_dir_updater.py +1440 -0
  255. wl_cli/utils/workspace_detector/__init__.py +28 -0
  256. wl_cli/utils/workspace_detector/core.py +161 -0
  257. wl_cli/utils/workspace_detector/detection_strategies.py +107 -0
  258. wl_cli/utils/workspace_detector/exceptions.py +5 -0
  259. wl_cli/utils/workspace_detector/types.py +17 -0
  260. wl_cli/utils/workspace_detector/venv_resolver.py +84 -0
  261. wl_cli/vendors/config/.codespellrc +2 -0
  262. wl_cli/vendors/config/dependencies.toml +10 -0
  263. wl_cli/vendors/config/mypy.ini +62 -0
  264. wl_cli/vendors/config/pyproject_defaults.toml +28 -0
  265. wl_cli/vendors/config/template_config.toml +24 -0
  266. wl_cli/vendors/config/tool_configs.toml +76 -0
  267. wl_cli/vendors/git/.gitignore +122 -0
  268. wl_cli/vendors/git/.pre-commit-config.yaml +37 -0
  269. wl_cli/vendors/hooks/applypatch-msg.sample +15 -0
  270. wl_cli/vendors/hooks/commit-msg.sample +24 -0
  271. wl_cli/vendors/hooks/fsmonitor-watchman.sample +174 -0
  272. wl_cli/vendors/hooks/post-update.sample +8 -0
  273. wl_cli/vendors/hooks/pre-applypatch.sample +14 -0
  274. wl_cli/vendors/hooks/pre-commit +45 -0
  275. wl_cli/vendors/hooks/pre-commit.legacy +31 -0
  276. wl_cli/vendors/hooks/pre-commit.sample +49 -0
  277. wl_cli/vendors/hooks/pre-merge-commit.sample +13 -0
  278. wl_cli/vendors/hooks/pre-push.sample +60 -0
  279. wl_cli/vendors/hooks/pre-rebase.sample +169 -0
  280. wl_cli/vendors/hooks/pre-receive.sample +24 -0
  281. wl_cli/vendors/hooks/prepare-commit-msg.sample +42 -0
  282. wl_cli/vendors/hooks/push-to-checkout.sample +78 -0
  283. wl_cli/vendors/hooks/sendemail-validate.sample +77 -0
  284. wl_cli/vendors/hooks/unix/applypatch-msg.sample +15 -0
  285. wl_cli/vendors/hooks/unix/check_lockfile.py +58 -0
  286. wl_cli/vendors/hooks/unix/check_lockfile.sh +36 -0
  287. wl_cli/vendors/hooks/unix/commit-msg.sample +24 -0
  288. wl_cli/vendors/hooks/unix/fsmonitor-watchman.sample +174 -0
  289. wl_cli/vendors/hooks/unix/post-update.sample +8 -0
  290. wl_cli/vendors/hooks/unix/pre-applypatch.sample +14 -0
  291. wl_cli/vendors/hooks/unix/pre-commit +190 -0
  292. wl_cli/vendors/hooks/unix/pre-commit.legacy +31 -0
  293. wl_cli/vendors/hooks/unix/pre-commit.py +81 -0
  294. wl_cli/vendors/hooks/unix/pre-commit.sample +49 -0
  295. wl_cli/vendors/hooks/unix/pre-merge-commit.sample +13 -0
  296. wl_cli/vendors/hooks/unix/pre-push +417 -0
  297. wl_cli/vendors/hooks/unix/pre-push.sample +53 -0
  298. wl_cli/vendors/hooks/unix/pre-rebase.sample +169 -0
  299. wl_cli/vendors/hooks/unix/pre-receive.sample +24 -0
  300. wl_cli/vendors/hooks/unix/prepare-commit-msg.sample +42 -0
  301. wl_cli/vendors/hooks/update.sample +128 -0
  302. wl_cli/vendors/hooks/win/work.bat +63 -0
  303. wl_cli/vendors/project/.gitignore +90 -0
  304. wl_cli/vendors/project/.python-version +1 -0
  305. wl_cli/vendors/project/LICENSE +21 -0
  306. wl_cli/vendors/project/README.md +0 -0
  307. wl_cli/vendors/project/pyproject.toml +27 -0
  308. wl_cli/vendors/project/pyproject_no_workspace.toml +16 -0
  309. wl_cli/vendors/project/pyproject_workspace.toml +14 -0
  310. wl_cli/vendors/readme/README.md +120 -0
  311. wl_cli/vendors/rust/Cargo.toml +14 -0
  312. wl_cli/vendors/rust/lib/__init__.py +3 -0
  313. wl_cli/vendors/rust/lib/rust_utils.py +31 -0
  314. wl_cli/vendors/rust/src/lib.rs +13 -0
  315. wl_cli/vendors/rust/src/main.rs +3 -0
  316. wl_cli/vendors/rust/tests/test_rust_fallback.py +40 -0
  317. wl_cli/vendors/sop_doc/git-dev-sop.md +220 -0
  318. wl_cli/vendors/sop_doc/industrial-standard-sop.md +145 -0
  319. wl_cli/vendors/sop_doc/python-sop.md +342 -0
  320. 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)