thailint 0.15.8__py3-none-any.whl → 0.17.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 (52) hide show
  1. src/cli/config.py +4 -12
  2. src/cli/linters/__init__.py +13 -3
  3. src/cli/linters/code_patterns.py +42 -38
  4. src/cli/linters/code_smells.py +8 -17
  5. src/cli/linters/documentation.py +3 -6
  6. src/cli/linters/performance.py +4 -10
  7. src/cli/linters/rust.py +177 -0
  8. src/cli/linters/shared.py +2 -7
  9. src/cli/linters/structure.py +4 -11
  10. src/cli/linters/structure_quality.py +4 -11
  11. src/cli/main.py +9 -12
  12. src/cli/utils.py +7 -16
  13. src/core/__init__.py +14 -0
  14. src/core/base.py +30 -0
  15. src/core/constants.py +1 -0
  16. src/core/linter_utils.py +42 -1
  17. src/core/rule_aliases.py +84 -0
  18. src/linter_config/rule_matcher.py +53 -8
  19. src/linters/blocking_async/__init__.py +31 -0
  20. src/linters/blocking_async/config.py +67 -0
  21. src/linters/blocking_async/linter.py +183 -0
  22. src/linters/blocking_async/rust_analyzer.py +419 -0
  23. src/linters/blocking_async/violation_builder.py +97 -0
  24. src/linters/clone_abuse/__init__.py +31 -0
  25. src/linters/clone_abuse/config.py +65 -0
  26. src/linters/clone_abuse/linter.py +183 -0
  27. src/linters/clone_abuse/rust_analyzer.py +356 -0
  28. src/linters/clone_abuse/violation_builder.py +94 -0
  29. src/linters/magic_numbers/linter.py +92 -0
  30. src/linters/magic_numbers/rust_analyzer.py +148 -0
  31. src/linters/magic_numbers/violation_builder.py +31 -0
  32. src/linters/nesting/linter.py +50 -0
  33. src/linters/nesting/rust_analyzer.py +118 -0
  34. src/linters/nesting/violation_builder.py +32 -0
  35. src/linters/print_statements/__init__.py +23 -11
  36. src/linters/print_statements/conditional_verbose_analyzer.py +200 -0
  37. src/linters/print_statements/conditional_verbose_rule.py +254 -0
  38. src/linters/print_statements/linter.py +2 -2
  39. src/linters/srp/class_analyzer.py +49 -0
  40. src/linters/srp/linter.py +22 -0
  41. src/linters/srp/rust_analyzer.py +206 -0
  42. src/linters/unwrap_abuse/__init__.py +30 -0
  43. src/linters/unwrap_abuse/config.py +59 -0
  44. src/linters/unwrap_abuse/linter.py +166 -0
  45. src/linters/unwrap_abuse/rust_analyzer.py +118 -0
  46. src/linters/unwrap_abuse/violation_builder.py +89 -0
  47. src/templates/thailint_config_template.yaml +88 -0
  48. {thailint-0.15.8.dist-info → thailint-0.17.0.dist-info}/METADATA +7 -3
  49. {thailint-0.15.8.dist-info → thailint-0.17.0.dist-info}/RECORD +52 -30
  50. {thailint-0.15.8.dist-info → thailint-0.17.0.dist-info}/WHEEL +0 -0
  51. {thailint-0.15.8.dist-info → thailint-0.17.0.dist-info}/entry_points.txt +0 -0
  52. {thailint-0.15.8.dist-info → thailint-0.17.0.dist-info}/licenses/LICENSE +0 -0
src/cli/config.py CHANGED
@@ -21,22 +21,18 @@ Implementation: Uses Click decorators for command definition, supports multiple
21
21
  validates configuration changes before saving, uses template file for init-config generation
22
22
  """
23
23
 
24
- import logging
25
24
  import sys
26
25
  from pathlib import Path
27
26
 
28
27
  import click
29
28
  import yaml
29
+ from loguru import logger
30
30
 
31
31
  from src.config import ConfigError, save_config, validate_config
32
32
 
33
33
  from .config_merge import perform_merge
34
34
  from .main import cli
35
35
 
36
- # Configure module logger
37
- logger = logging.getLogger(__name__)
38
-
39
-
40
36
  # =============================================================================
41
37
  # Config Command Group
42
38
  # =============================================================================
@@ -177,8 +173,7 @@ def _save_and_report_success(
177
173
  """Save configuration and report success."""
178
174
  save_config(cfg, config_path)
179
175
  click.echo(f"Set {key} = {value}")
180
- if verbose:
181
- logger.info(f"Configuration updated: {key}={value}")
176
+ logger.debug(f"Configuration updated: {key}={value}")
182
177
 
183
178
 
184
179
  @config.command("set")
@@ -256,8 +251,7 @@ def config_reset(ctx: click.Context, yes: bool) -> None:
256
251
  save_config(DEFAULT_CONFIG.copy(), config_path)
257
252
  click.echo("Configuration reset to defaults")
258
253
 
259
- if ctx.obj.get("verbose"):
260
- logger.info("Configuration reset to defaults")
254
+ logger.debug("Configuration reset to defaults")
261
255
  except ConfigError as e:
262
256
  click.echo(f"Error resetting configuration: {e}", err=True)
263
257
  sys.exit(1)
@@ -462,7 +456,6 @@ def hello(ctx: click.Context, name: str, uppercase: bool) -> None:
462
456
  thai-lint hello --name Bob --uppercase
463
457
  """
464
458
  config = ctx.obj["config"]
465
- verbose = ctx.obj.get("verbose", False)
466
459
 
467
460
  # Get greeting from config or use default
468
461
  greeting_template = config.get("greeting", "Hello")
@@ -476,5 +469,4 @@ def hello(ctx: click.Context, name: str, uppercase: bool) -> None:
476
469
  # Output greeting
477
470
  click.echo(message)
478
471
 
479
- if verbose:
480
- logger.info(f"Greeted {name} with template '{greeting_template}'")
472
+ logger.debug(f"Greeted {name} with template '{greeting_template}'")
@@ -1,13 +1,15 @@
1
1
  """
2
2
  Purpose: CLI linters package that registers all linter commands to the main CLI group
3
3
 
4
- Scope: Export and registration of all linter CLI commands (nesting, srp, dry, magic-numbers, etc.)
4
+ Scope: Export and registration of all linter CLI commands (nesting, srp, dry, magic-numbers,
5
+ unwrap-abuse, clone-abuse, blocking-async, etc.)
5
6
 
6
7
  Overview: Package initialization that imports all linter command modules to trigger their registration
7
8
  with the main CLI group via Click decorators. Each submodule defines commands using @cli.command()
8
9
  decorators that automatically register with the CLI when imported. Organized by logical grouping:
9
- structure_quality (nesting, srp), code_smells (dry, magic-numbers), code_patterns (print-statements,
10
+ structure_quality (nesting, srp), code_smells (dry, magic-numbers), code_patterns (improper-logging,
10
11
  method-property, stateless-class), structure (file-placement, pipeline), documentation (file-header).
12
+ Note: print-statements is a deprecated alias for improper-logging.
11
13
 
12
14
  Dependencies: Click for CLI framework, src.cli.main for CLI group, individual linter modules
13
15
 
@@ -28,12 +30,14 @@ from src.cli.linters import ( # noqa: F401
28
30
  code_smells,
29
31
  documentation,
30
32
  performance,
33
+ rust,
31
34
  structure,
32
35
  structure_quality,
33
36
  )
34
37
 
35
38
  # Re-export command functions for testing and reference
36
39
  from src.cli.linters.code_patterns import (
40
+ improper_logging,
37
41
  method_property,
38
42
  print_statements,
39
43
  stateless_class,
@@ -41,6 +45,7 @@ from src.cli.linters.code_patterns import (
41
45
  from src.cli.linters.code_smells import dry, magic_numbers
42
46
  from src.cli.linters.documentation import file_header
43
47
  from src.cli.linters.performance import perf, regex_in_loop, string_concat_loop
48
+ from src.cli.linters.rust import blocking_async, clone_abuse, unwrap_abuse
44
49
  from src.cli.linters.structure import file_placement, pipeline
45
50
  from src.cli.linters.structure_quality import nesting, srp
46
51
 
@@ -52,7 +57,8 @@ __all__ = [
52
57
  "dry",
53
58
  "magic_numbers",
54
59
  # Code pattern commands
55
- "print_statements",
60
+ "improper_logging",
61
+ "print_statements", # deprecated alias for improper_logging
56
62
  "method_property",
57
63
  "stateless_class",
58
64
  # Structure commands
@@ -64,4 +70,8 @@ __all__ = [
64
70
  "perf",
65
71
  "string_concat_loop",
66
72
  "regex_in_loop",
73
+ # Rust commands
74
+ "unwrap_abuse",
75
+ "clone_abuse",
76
+ "blocking_async",
67
77
  ]
@@ -1,29 +1,31 @@
1
1
  """
2
- Purpose: CLI commands for code pattern linters (print-statements, method-property, stateless-class, lazy-ignores, lbyl)
2
+ Purpose: CLI commands for code pattern linters (improper-logging, method-property, stateless-class, lazy-ignores, lbyl)
3
3
 
4
4
  Scope: Commands that detect code patterns and anti-patterns in Python code
5
5
 
6
- Overview: Provides CLI commands for code pattern linting: print-statements detects print() and
7
- console.log calls that should use proper logging, method-property finds methods that should be
8
- @property decorators, stateless-class detects classes without state that should be module
9
- functions, lazy-ignores detects unjustified linting suppressions, and lbyl detects Look Before
10
- You Leap anti-patterns. Each command supports standard options (config, format, recursive) and
11
- integrates with the orchestrator for execution.
6
+ Overview: Provides CLI commands for code pattern linting: improper-logging detects print() and
7
+ console.log calls that should use proper logging as well as conditional verbose patterns,
8
+ method-property finds methods that should be @property decorators, stateless-class detects
9
+ classes without state that should be module functions, lazy-ignores detects unjustified linting
10
+ suppressions, and lbyl detects Look Before You Leap anti-patterns. Each command supports
11
+ standard options (config, format, recursive) and integrates with the orchestrator for execution.
12
+ Note: print-statements is a deprecated alias for improper-logging.
12
13
 
13
14
  Dependencies: click for CLI framework, src.cli.main for CLI group, src.cli.utils for shared utilities
14
15
 
15
- Exports: print_statements, method_property, stateless_class, lazy_ignores, lbyl commands
16
+ Exports: improper_logging, print_statements (alias), method_property, stateless_class, lazy_ignores, lbyl commands
16
17
 
17
18
  Interfaces: Click CLI commands registered to main CLI group
18
19
 
19
20
  Implementation: Click decorators for command definition, orchestrator-based linting execution
20
21
  """
21
22
 
22
- import logging
23
23
  import sys
24
24
  from pathlib import Path
25
25
  from typing import TYPE_CHECKING, NoReturn
26
26
 
27
+ from loguru import logger
28
+
27
29
  from src.cli.linters.shared import ExecuteParams, create_linter_command
28
30
  from src.cli.utils import execute_linting_on_paths, setup_base_orchestrator, validate_paths_exist
29
31
  from src.core.cli_utils import format_violations
@@ -32,53 +34,59 @@ from src.core.types import Violation
32
34
  if TYPE_CHECKING:
33
35
  from src.orchestrator.core import Orchestrator
34
36
 
35
- # Configure module logger
36
- logger = logging.getLogger(__name__)
37
-
38
37
 
39
38
  # =============================================================================
40
- # Print Statements Command
39
+ # Improper Logging Command (formerly print-statements)
41
40
  # =============================================================================
42
41
 
43
42
 
44
- def _setup_print_statements_orchestrator(
43
+ def _setup_improper_logging_orchestrator(
45
44
  path_objs: list[Path], config_file: str | None, verbose: bool, project_root: Path | None = None
46
45
  ) -> "Orchestrator":
47
- """Set up orchestrator for print-statements command."""
46
+ """Set up orchestrator for improper-logging command."""
48
47
  return setup_base_orchestrator(path_objs, config_file, verbose, project_root)
49
48
 
50
49
 
51
- def _run_print_statements_lint(
50
+ def _run_improper_logging_lint(
52
51
  orchestrator: "Orchestrator", path_objs: list[Path], recursive: bool, parallel: bool = False
53
52
  ) -> list[Violation]:
54
- """Execute print-statements lint on files or directories."""
53
+ """Execute improper-logging lint on files or directories."""
55
54
  all_violations = execute_linting_on_paths(orchestrator, path_objs, recursive, parallel)
56
- return [v for v in all_violations if "print-statement" in v.rule_id]
55
+ return [v for v in all_violations if v.rule_id.startswith("improper-logging.")]
57
56
 
58
57
 
59
- def _execute_print_statements_lint(params: ExecuteParams) -> NoReturn:
60
- """Execute print-statements lint."""
58
+ def _execute_improper_logging_lint(params: ExecuteParams) -> NoReturn:
59
+ """Execute improper-logging lint."""
61
60
  validate_paths_exist(params.path_objs)
62
- orchestrator = _setup_print_statements_orchestrator(
61
+ orchestrator = _setup_improper_logging_orchestrator(
63
62
  params.path_objs, params.config_file, params.verbose, params.project_root
64
63
  )
65
- print_statements_violations = _run_print_statements_lint(
64
+ improper_logging_violations = _run_improper_logging_lint(
66
65
  orchestrator, params.path_objs, params.recursive, params.parallel
67
66
  )
68
67
 
69
- if params.verbose:
70
- logger.info(f"Found {len(print_statements_violations)} print statement violation(s)")
68
+ logger.debug(f"Found {len(improper_logging_violations)} improper logging violation(s)")
71
69
 
72
- format_violations(print_statements_violations, params.format)
73
- sys.exit(1 if print_statements_violations else 0)
70
+ format_violations(improper_logging_violations, params.format)
71
+ sys.exit(1 if improper_logging_violations else 0)
74
72
 
75
73
 
74
+ # Primary command
75
+ improper_logging = create_linter_command(
76
+ "improper-logging",
77
+ _execute_improper_logging_lint,
78
+ "Check for improper logging patterns in code.",
79
+ "Detects print()/console statements and conditional verbose patterns that should\n"
80
+ " be replaced with proper logging configuration.",
81
+ )
82
+
83
+ # Backward-compatible alias (deprecated)
76
84
  print_statements = create_linter_command(
77
85
  "print-statements",
78
- _execute_print_statements_lint,
79
- "Check for print/console statements in code.",
80
- "Detects print() calls in Python and console.log/warn/error/debug/info calls\n"
81
- " in TypeScript/JavaScript that should be replaced with proper logging.",
86
+ _execute_improper_logging_lint, # Same executor as improper-logging
87
+ "Alias for improper-logging (deprecated).",
88
+ "DEPRECATED: Use 'improper-logging' instead. Detects print() calls in Python and\n"
89
+ " console.log/warn/error/debug/info calls in TypeScript/JavaScript.",
82
90
  )
83
91
 
84
92
 
@@ -112,8 +120,7 @@ def _execute_method_property_lint(params: ExecuteParams) -> NoReturn:
112
120
  orchestrator, params.path_objs, params.recursive, params.parallel
113
121
  )
114
122
 
115
- if params.verbose:
116
- logger.info(f"Found {len(method_property_violations)} method-property violation(s)")
123
+ logger.debug(f"Found {len(method_property_violations)} method-property violation(s)")
117
124
 
118
125
  format_violations(method_property_violations, params.format)
119
126
  sys.exit(1 if method_property_violations else 0)
@@ -159,8 +166,7 @@ def _execute_stateless_class_lint(params: ExecuteParams) -> NoReturn:
159
166
  orchestrator, params.path_objs, params.recursive, params.parallel
160
167
  )
161
168
 
162
- if params.verbose:
163
- logger.info(f"Found {len(stateless_class_violations)} stateless-class violation(s)")
169
+ logger.debug(f"Found {len(stateless_class_violations)} stateless-class violation(s)")
164
170
 
165
171
  format_violations(stateless_class_violations, params.format)
166
172
  sys.exit(1 if stateless_class_violations else 0)
@@ -206,8 +212,7 @@ def _execute_lazy_ignores_lint(params: ExecuteParams) -> NoReturn:
206
212
  orchestrator, params.path_objs, params.recursive, params.parallel
207
213
  )
208
214
 
209
- if params.verbose:
210
- logger.info(f"Found {len(lazy_ignores_violations)} lazy-ignores violation(s)")
215
+ logger.debug(f"Found {len(lazy_ignores_violations)} lazy-ignores violation(s)")
211
216
 
212
217
  format_violations(lazy_ignores_violations, params.format)
213
218
  sys.exit(1 if lazy_ignores_violations else 0)
@@ -253,8 +258,7 @@ def _execute_lbyl_lint(params: ExecuteParams) -> NoReturn:
253
258
  orchestrator, params.path_objs, params.recursive, params.parallel
254
259
  )
255
260
 
256
- if params.verbose:
257
- logger.info(f"Found {len(lbyl_violations)} LBYL violation(s)")
261
+ logger.debug(f"Found {len(lbyl_violations)} LBYL violation(s)")
258
262
 
259
263
  format_violations(lbyl_violations, params.format)
260
264
  sys.exit(1 if lbyl_violations else 0)
@@ -23,13 +23,13 @@ Suppressions:
23
23
  many parameters by framework design (dry command has 8 params for extra options)
24
24
  """
25
25
 
26
- import logging
27
26
  import sys
28
27
  from pathlib import Path
29
28
  from typing import TYPE_CHECKING, Any, NoReturn
30
29
 
31
30
  import click
32
31
  import yaml
32
+ from loguru import logger
33
33
 
34
34
  from src.cli.linters.shared import (
35
35
  ExecuteParams,
@@ -53,10 +53,6 @@ from src.core.types import Violation
53
53
  if TYPE_CHECKING:
54
54
  from src.orchestrator.core import Orchestrator
55
55
 
56
- # Configure module logger
57
- logger = logging.getLogger(__name__)
58
-
59
-
60
56
  # =============================================================================
61
57
  # DRY Command (custom options - cannot use create_linter_command)
62
58
  # =============================================================================
@@ -87,8 +83,7 @@ def _load_dry_config_file(orchestrator: "Orchestrator", config_file: str, verbos
87
83
  except KeyError:
88
84
  return # No DRY config in file
89
85
  orchestrator.config.update({"dry": dry_config})
90
- if verbose:
91
- logger.info(f"Loaded DRY config from {config_file}")
86
+ logger.debug(f"Loaded DRY config from {config_file}")
92
87
 
93
88
 
94
89
  def _apply_dry_config_override(
@@ -108,10 +103,9 @@ def _clear_dry_cache(orchestrator: "Orchestrator", verbose: bool) -> None:
108
103
 
109
104
  if cache_path.exists():
110
105
  cache_path.unlink()
111
- if verbose:
112
- logger.info(f"Cleared cache: {cache_path}")
113
- elif verbose:
114
- logger.info("Cache file does not exist, nothing to clear")
106
+ logger.debug(f"Cleared cache: {cache_path}")
107
+ else:
108
+ logger.debug("Cache file does not exist, nothing to clear")
115
109
 
116
110
 
117
111
  def _run_dry_lint(
@@ -242,8 +236,7 @@ def _execute_dry_lint( # pylint: disable=too-many-arguments,too-many-positional
242
236
 
243
237
  dry_violations = _run_dry_lint(orchestrator, path_objs, recursive, parallel)
244
238
 
245
- if verbose:
246
- logger.info(f"Found {len(dry_violations)} DRY violation(s)")
239
+ logger.debug(f"Found {len(dry_violations)} DRY violation(s)")
247
240
 
248
241
  format_violations(dry_violations, format)
249
242
  sys.exit(1 if dry_violations else 0)
@@ -279,8 +272,7 @@ def _execute_magic_numbers_lint(params: ExecuteParams) -> NoReturn:
279
272
  orchestrator, params.path_objs, params.recursive, params.parallel
280
273
  )
281
274
 
282
- if params.verbose:
283
- logger.info(f"Found {len(magic_numbers_violations)} magic number violation(s)")
275
+ logger.debug(f"Found {len(magic_numbers_violations)} magic number violation(s)")
284
276
 
285
277
  format_violations(magic_numbers_violations, params.format)
286
278
  sys.exit(1 if magic_numbers_violations else 0)
@@ -325,8 +317,7 @@ def _execute_stringly_typed_lint(params: ExecuteParams) -> NoReturn:
325
317
  orchestrator, params.path_objs, params.recursive, params.parallel
326
318
  )
327
319
 
328
- if params.verbose:
329
- logger.info(f"Found {len(stringly_violations)} stringly-typed violation(s)")
320
+ logger.debug(f"Found {len(stringly_violations)} stringly-typed violation(s)")
330
321
 
331
322
  format_violations(stringly_violations, params.format)
332
323
  sys.exit(1 if stringly_violations else 0)
@@ -18,11 +18,12 @@ Interfaces: Click CLI commands registered to main CLI group
18
18
  Implementation: Click decorators for command definition, orchestrator-based linting execution
19
19
  """
20
20
 
21
- import logging
22
21
  import sys
23
22
  from pathlib import Path
24
23
  from typing import TYPE_CHECKING, NoReturn
25
24
 
25
+ from loguru import logger
26
+
26
27
  from src.cli.linters.shared import ExecuteParams, create_linter_command
27
28
  from src.cli.utils import execute_linting_on_paths, setup_base_orchestrator, validate_paths_exist
28
29
  from src.core.cli_utils import format_violations
@@ -31,9 +32,6 @@ from src.core.types import Violation
31
32
  if TYPE_CHECKING:
32
33
  from src.orchestrator.core import Orchestrator
33
34
 
34
- # Configure module logger
35
- logger = logging.getLogger(__name__)
36
-
37
35
 
38
36
  # =============================================================================
39
37
  # File Header Command
@@ -65,8 +63,7 @@ def _execute_file_header_lint(params: ExecuteParams) -> NoReturn:
65
63
  orchestrator, params.path_objs, params.recursive, params.parallel
66
64
  )
67
65
 
68
- if params.verbose:
69
- logger.info(f"Found {len(file_header_violations)} file header violation(s)")
66
+ logger.debug(f"Found {len(file_header_violations)} file header violation(s)")
70
67
 
71
68
  format_violations(file_header_violations, params.format)
72
69
  sys.exit(1 if file_header_violations else 0)
@@ -24,12 +24,12 @@ Suppressions:
24
24
  --rule option for 6 total parameters - framework design requirement for CLI extensibility.
25
25
  """
26
26
 
27
- import logging
28
27
  import sys
29
28
  from pathlib import Path
30
29
  from typing import TYPE_CHECKING, NoReturn
31
30
 
32
31
  import click
32
+ from loguru import logger
33
33
 
34
34
  from src.cli.linters.shared import (
35
35
  ExecuteParams,
@@ -46,9 +46,6 @@ from src.core.types import Violation
46
46
  if TYPE_CHECKING:
47
47
  from src.orchestrator.core import Orchestrator
48
48
 
49
- # Configure module logger
50
- logger = logging.getLogger(__name__)
51
-
52
49
 
53
50
  # =============================================================================
54
51
  # String Concat Loop Command
@@ -88,8 +85,7 @@ def _execute_string_concat_lint(params: ExecuteParams) -> NoReturn:
88
85
  orchestrator, params.path_objs, params.recursive, params.parallel
89
86
  )
90
87
 
91
- if params.verbose:
92
- logger.info(f"Found {len(violations)} string-concat-loop violation(s)")
88
+ logger.debug(f"Found {len(violations)} string-concat-loop violation(s)")
93
89
 
94
90
  format_violations(violations, params.format)
95
91
  sys.exit(1 if violations else 0)
@@ -124,8 +120,7 @@ def _execute_regex_in_loop_lint(params: ExecuteParams) -> NoReturn:
124
120
  orchestrator, params.path_objs, params.recursive, params.parallel
125
121
  )
126
122
 
127
- if params.verbose:
128
- logger.info(f"Found {len(violations)} regex-in-loop violation(s)")
123
+ logger.debug(f"Found {len(violations)} regex-in-loop violation(s)")
129
124
 
130
125
  format_violations(violations, params.format)
131
126
  sys.exit(1 if violations else 0)
@@ -207,8 +202,7 @@ def _execute_perf_lint(params: ExecuteParams, rule: str | None) -> NoReturn:
207
202
  orchestrator, params.path_objs, params.recursive, rule, params.parallel
208
203
  )
209
204
 
210
- if params.verbose:
211
- logger.info(f"Found {len(violations)} performance violation(s)")
205
+ logger.debug(f"Found {len(violations)} performance violation(s)")
212
206
 
213
207
  format_violations(violations, params.format)
214
208
  sys.exit(1 if violations else 0)
@@ -0,0 +1,177 @@
1
+ """
2
+ Purpose: CLI commands for Rust-specific linters (unwrap-abuse, clone-abuse, blocking-async)
3
+
4
+ Scope: Commands that detect Rust-specific anti-patterns and code smells
5
+
6
+ Overview: Provides CLI commands for Rust code linting: unwrap-abuse detects .unwrap() and
7
+ .expect() calls that may panic at runtime and suggests safer alternatives; clone-abuse
8
+ detects .clone() abuse patterns including clone in loops, chained clones, and unnecessary
9
+ clones; blocking-async detects blocking operations (std::fs, std::thread::sleep, std::net)
10
+ inside async functions and suggests tokio equivalents. Each command supports standard
11
+ options (config, format, recursive) and integrates with the orchestrator for execution.
12
+
13
+ Dependencies: click for CLI framework, src.cli.main for CLI group, src.cli.utils for shared utilities
14
+
15
+ Exports: unwrap_abuse command, clone_abuse command, blocking_async command
16
+
17
+ Interfaces: Click CLI commands registered to main CLI group
18
+
19
+ Implementation: Click decorators for command definition, orchestrator-based linting execution
20
+ """
21
+
22
+ import sys
23
+ from pathlib import Path
24
+ from typing import TYPE_CHECKING, NoReturn
25
+
26
+ from loguru import logger
27
+
28
+ from src.cli.linters.shared import ExecuteParams, create_linter_command
29
+ from src.cli.utils import execute_linting_on_paths, setup_base_orchestrator, validate_paths_exist
30
+ from src.core.cli_utils import format_violations
31
+ from src.core.types import Violation
32
+
33
+ if TYPE_CHECKING:
34
+ from src.orchestrator.core import Orchestrator
35
+
36
+
37
+ # =============================================================================
38
+ # Unwrap Abuse Command
39
+ # =============================================================================
40
+
41
+
42
+ def _setup_unwrap_abuse_orchestrator(
43
+ path_objs: list[Path], config_file: str | None, verbose: bool, project_root: Path | None = None
44
+ ) -> "Orchestrator":
45
+ """Set up orchestrator for unwrap-abuse command."""
46
+ return setup_base_orchestrator(path_objs, config_file, verbose, project_root)
47
+
48
+
49
+ def _run_unwrap_abuse_lint(
50
+ orchestrator: "Orchestrator", path_objs: list[Path], recursive: bool, parallel: bool = False
51
+ ) -> list[Violation]:
52
+ """Execute unwrap-abuse lint on files or directories."""
53
+ all_violations = execute_linting_on_paths(orchestrator, path_objs, recursive, parallel)
54
+ return [v for v in all_violations if v.rule_id.startswith("unwrap-abuse")]
55
+
56
+
57
+ def _execute_unwrap_abuse_lint(params: ExecuteParams) -> NoReturn:
58
+ """Execute unwrap-abuse lint."""
59
+ validate_paths_exist(params.path_objs)
60
+ orchestrator = _setup_unwrap_abuse_orchestrator(
61
+ params.path_objs, params.config_file, params.verbose, params.project_root
62
+ )
63
+ unwrap_abuse_violations = _run_unwrap_abuse_lint(
64
+ orchestrator, params.path_objs, params.recursive, params.parallel
65
+ )
66
+
67
+ logger.debug(f"Found {len(unwrap_abuse_violations)} unwrap abuse violation(s)")
68
+
69
+ format_violations(unwrap_abuse_violations, params.format)
70
+ sys.exit(1 if unwrap_abuse_violations else 0)
71
+
72
+
73
+ unwrap_abuse = create_linter_command(
74
+ "unwrap-abuse",
75
+ _execute_unwrap_abuse_lint,
76
+ "Check for .unwrap() and .expect() abuse in Rust code.",
77
+ "Detects .unwrap() and .expect() calls in Rust code that may panic at runtime.\n"
78
+ " Suggests safer alternatives like the ? operator, unwrap_or(),\n"
79
+ " unwrap_or_default(), or match/if-let expressions.\n"
80
+ " Ignores calls in #[test] functions and #[cfg(test)] modules by default.",
81
+ )
82
+
83
+
84
+ # =============================================================================
85
+ # Clone Abuse Command
86
+ # =============================================================================
87
+
88
+
89
+ def _setup_clone_abuse_orchestrator(
90
+ path_objs: list[Path], config_file: str | None, verbose: bool, project_root: Path | None = None
91
+ ) -> "Orchestrator":
92
+ """Set up orchestrator for clone-abuse command."""
93
+ return setup_base_orchestrator(path_objs, config_file, verbose, project_root)
94
+
95
+
96
+ def _run_clone_abuse_lint(
97
+ orchestrator: "Orchestrator", path_objs: list[Path], recursive: bool, parallel: bool = False
98
+ ) -> list[Violation]:
99
+ """Execute clone-abuse lint on files or directories."""
100
+ all_violations = execute_linting_on_paths(orchestrator, path_objs, recursive, parallel)
101
+ return [v for v in all_violations if v.rule_id.startswith("clone-abuse")]
102
+
103
+
104
+ def _execute_clone_abuse_lint(params: ExecuteParams) -> NoReturn:
105
+ """Execute clone-abuse lint."""
106
+ validate_paths_exist(params.path_objs)
107
+ orchestrator = _setup_clone_abuse_orchestrator(
108
+ params.path_objs, params.config_file, params.verbose, params.project_root
109
+ )
110
+ clone_abuse_violations = _run_clone_abuse_lint(
111
+ orchestrator, params.path_objs, params.recursive, params.parallel
112
+ )
113
+
114
+ logger.debug(f"Found {len(clone_abuse_violations)} clone abuse violation(s)")
115
+
116
+ format_violations(clone_abuse_violations, params.format)
117
+ sys.exit(1 if clone_abuse_violations else 0)
118
+
119
+
120
+ clone_abuse = create_linter_command(
121
+ "clone-abuse",
122
+ _execute_clone_abuse_lint,
123
+ "Check for .clone() abuse patterns in Rust code.",
124
+ "Detects .clone() abuse patterns in Rust code: clone in loops,\n"
125
+ " chained .clone().clone() calls, and unnecessary clones where\n"
126
+ " the original is not used after cloning.\n"
127
+ " Suggests borrowing, Rc/Arc, or Cow patterns as alternatives.\n"
128
+ " Ignores calls in #[test] functions and #[cfg(test)] modules by default.",
129
+ )
130
+
131
+
132
+ # =============================================================================
133
+ # Blocking Async Command
134
+ # =============================================================================
135
+
136
+
137
+ def _setup_blocking_async_orchestrator(
138
+ path_objs: list[Path], config_file: str | None, verbose: bool, project_root: Path | None = None
139
+ ) -> "Orchestrator":
140
+ """Set up orchestrator for blocking-async command."""
141
+ return setup_base_orchestrator(path_objs, config_file, verbose, project_root)
142
+
143
+
144
+ def _run_blocking_async_lint(
145
+ orchestrator: "Orchestrator", path_objs: list[Path], recursive: bool, parallel: bool = False
146
+ ) -> list[Violation]:
147
+ """Execute blocking-async lint on files or directories."""
148
+ all_violations = execute_linting_on_paths(orchestrator, path_objs, recursive, parallel)
149
+ return [v for v in all_violations if v.rule_id.startswith("blocking-async")]
150
+
151
+
152
+ def _execute_blocking_async_lint(params: ExecuteParams) -> NoReturn:
153
+ """Execute blocking-async lint."""
154
+ validate_paths_exist(params.path_objs)
155
+ orchestrator = _setup_blocking_async_orchestrator(
156
+ params.path_objs, params.config_file, params.verbose, params.project_root
157
+ )
158
+ blocking_async_violations = _run_blocking_async_lint(
159
+ orchestrator, params.path_objs, params.recursive, params.parallel
160
+ )
161
+
162
+ logger.debug(f"Found {len(blocking_async_violations)} blocking-async violation(s)")
163
+
164
+ format_violations(blocking_async_violations, params.format)
165
+ sys.exit(1 if blocking_async_violations else 0)
166
+
167
+
168
+ blocking_async = create_linter_command(
169
+ "blocking-async",
170
+ _execute_blocking_async_lint,
171
+ "Check for blocking operations in async Rust functions.",
172
+ "Detects blocking operations inside async functions in Rust code:\n"
173
+ " std::fs I/O, std::thread::sleep, and blocking std::net calls.\n"
174
+ " Suggests async-compatible alternatives like tokio::fs,\n"
175
+ " tokio::time::sleep, and tokio::net equivalents.\n"
176
+ " Ignores calls in #[test] functions and #[cfg(test)] modules by default.",
177
+ )
src/cli/linters/shared.py CHANGED
@@ -27,12 +27,12 @@ Suppressions:
27
27
  by Click framework design (ctx, paths, config_file, format, recursive, parallel = 6 params)
28
28
  """
29
29
 
30
- import logging
31
30
  from dataclasses import dataclass
32
31
  from pathlib import Path
33
32
  from typing import TYPE_CHECKING, Any
34
33
 
35
34
  import click
35
+ from loguru import logger
36
36
 
37
37
  from src.cli.utils import (
38
38
  format_option,
@@ -185,10 +185,6 @@ def run_linter_command(
185
185
  handle_linting_error(e, params.verbose)
186
186
 
187
187
 
188
- # Configure module logger
189
- logger = logging.getLogger(__name__)
190
-
191
-
192
188
  def ensure_config_section(orchestrator: "Orchestrator", section: str) -> dict[str, Any]:
193
189
  """Ensure a config section exists and return it.
194
190
 
@@ -219,8 +215,7 @@ def set_config_value(config: dict[str, Any], key: str, value: Any, verbose: bool
219
215
  if value is None:
220
216
  return
221
217
  config[key] = value
222
- if verbose:
223
- logger.debug(f"Overriding {key} to {value}")
218
+ logger.debug(f"Overriding {key} to {value}")
224
219
 
225
220
 
226
221
  def filter_violations_by_prefix(violations: list[Violation], prefix: str) -> list[Violation]: