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
@@ -0,0 +1,200 @@
1
+ """
2
+ Purpose: Python AST analysis for finding conditional verbose logging patterns
3
+
4
+ Scope: Detection of if verbose: logger.*() anti-patterns in Python code
5
+
6
+ Overview: Provides ConditionalVerboseAnalyzer class that traverses Python AST to find logging calls
7
+ that are conditionally guarded by verbose flags. Detects patterns like 'if verbose: logger.debug()'
8
+ or 'if self.verbose: logger.info()' which are anti-patterns because logging levels should be
9
+ configured at the logger level rather than through code conditionals. Supports detection of
10
+ various verbose condition patterns including simple names, attribute access, dict access, and
11
+ method calls on context objects.
12
+
13
+ Dependencies: ast module for AST parsing and node types
14
+
15
+ Exports: ConditionalVerboseAnalyzer class, is_verbose_condition function, is_logger_call function
16
+
17
+ Interfaces: find_conditional_verbose_calls(tree) -> list[tuple[If, Call, int]]
18
+
19
+ Implementation: AST walk pattern with condition matching for verbose patterns and logger call detection
20
+ """
21
+
22
+ import ast
23
+
24
+ # Logger methods that indicate a logging call
25
+ LOGGER_METHODS = frozenset({"debug", "info", "warning", "error", "critical", "log", "exception"})
26
+
27
+ # Verbose-related names that typically guard logging
28
+ VERBOSE_NAMES = frozenset({"verbose", "debug", "is_verbose", "is_debug"})
29
+
30
+
31
+ def is_verbose_condition(test: ast.expr) -> bool:
32
+ """Check if an expression is a verbose-like condition.
33
+
34
+ Matches patterns like:
35
+ - verbose
36
+ - self.verbose
37
+ - config.verbose
38
+ - params.verbose
39
+ - ctx.obj.get("verbose")
40
+ - ctx.obj["verbose"]
41
+
42
+ Args:
43
+ test: The condition expression to check
44
+
45
+ Returns:
46
+ True if the condition appears to be a verbose check
47
+ """
48
+ return (
49
+ _is_simple_verbose_name(test)
50
+ or _is_verbose_attribute(test)
51
+ or _is_verbose_subscript(test)
52
+ or _is_verbose_dict_get(test)
53
+ )
54
+
55
+
56
+ def _is_simple_verbose_name(test: ast.expr) -> bool:
57
+ """Check for simple name like 'verbose' or 'debug'."""
58
+ return isinstance(test, ast.Name) and test.id.lower() in VERBOSE_NAMES
59
+
60
+
61
+ def _is_verbose_attribute(test: ast.expr) -> bool:
62
+ """Check for attribute access like 'self.verbose' or 'config.verbose'."""
63
+ if not isinstance(test, ast.Attribute):
64
+ return False
65
+ return test.attr.lower() in VERBOSE_NAMES
66
+
67
+
68
+ def _is_verbose_subscript(test: ast.expr) -> bool:
69
+ """Check for subscript access like 'ctx.obj["verbose"]'."""
70
+ if not isinstance(test, ast.Subscript):
71
+ return False
72
+ if not isinstance(test.slice, ast.Constant):
73
+ return False
74
+ value = test.slice.value
75
+ return isinstance(value, str) and value.lower() in VERBOSE_NAMES
76
+
77
+
78
+ def _is_verbose_dict_get(test: ast.expr) -> bool:
79
+ """Check for dict.get call like 'ctx.obj.get("verbose")'."""
80
+ if not isinstance(test, ast.Call):
81
+ return False
82
+ if not _is_dict_get_call_with_args(test):
83
+ return False
84
+ return _first_arg_is_verbose_string(test.args[0])
85
+
86
+
87
+ def _is_dict_get_call_with_args(call: ast.Call) -> bool:
88
+ """Check if call is a .get() method call with arguments."""
89
+ if not isinstance(call.func, ast.Attribute):
90
+ return False
91
+ if call.func.attr != "get":
92
+ return False
93
+ return bool(call.args)
94
+
95
+
96
+ def _first_arg_is_verbose_string(arg: ast.expr) -> bool:
97
+ """Check if argument is a verbose-related string constant."""
98
+ if not isinstance(arg, ast.Constant):
99
+ return False
100
+ value = arg.value
101
+ return isinstance(value, str) and value.lower() in VERBOSE_NAMES
102
+
103
+
104
+ def is_logger_call(node: ast.Call) -> bool:
105
+ """Check if a Call node is a logger method call.
106
+
107
+ Matches patterns like:
108
+ - logger.debug()
109
+ - logging.info()
110
+ - self.logger.warning()
111
+ - log.error()
112
+
113
+ Args:
114
+ node: The Call node to check
115
+
116
+ Returns:
117
+ True if this appears to be a logging call
118
+ """
119
+ if not isinstance(node.func, ast.Attribute):
120
+ return False
121
+ return node.func.attr in LOGGER_METHODS
122
+
123
+
124
+ def _extract_logger_method(node: ast.Call) -> str:
125
+ """Extract the logger method name from a call node.
126
+
127
+ Args:
128
+ node: The Call node (must be a logger call)
129
+
130
+ Returns:
131
+ The logger method name (e.g., 'debug', 'info')
132
+ """
133
+ if isinstance(node.func, ast.Attribute):
134
+ return node.func.attr
135
+ return ""
136
+
137
+
138
+ class ConditionalVerboseAnalyzer:
139
+ """Analyzes Python AST to find conditional verbose logging patterns."""
140
+
141
+ def __init__(self) -> None:
142
+ """Initialize the analyzer."""
143
+ self.violations: list[tuple[ast.If, ast.Call, int]] = []
144
+
145
+ def find_conditional_verbose_calls(
146
+ self, tree: ast.AST
147
+ ) -> list[tuple[ast.If, ast.Call, str, int]]:
148
+ """Find all conditional verbose logging patterns in the AST.
149
+
150
+ Looks for if statements with verbose-like conditions that contain
151
+ logger method calls in their body.
152
+
153
+ Args:
154
+ tree: The AST to analyze
155
+
156
+ Returns:
157
+ List of tuples (if_node, call_node, logger_method, line_number)
158
+ """
159
+ verbose_if_nodes = (
160
+ node
161
+ for node in ast.walk(tree)
162
+ if isinstance(node, ast.If) and is_verbose_condition(node.test)
163
+ )
164
+
165
+ results: list[tuple[ast.If, ast.Call, str, int]] = []
166
+ for if_node in verbose_if_nodes:
167
+ results.extend(self._extract_logger_call_results(if_node))
168
+
169
+ return results
170
+
171
+ def _extract_logger_call_results(
172
+ self, if_node: ast.If
173
+ ) -> list[tuple[ast.If, ast.Call, str, int]]:
174
+ """Extract logger call results from a verbose if node."""
175
+ logger_calls = self._find_logger_calls_in_body(if_node.body)
176
+ return [
177
+ (
178
+ if_node,
179
+ call_node,
180
+ _extract_logger_method(call_node),
181
+ call_node.lineno if hasattr(call_node, "lineno") else if_node.lineno,
182
+ )
183
+ for call_node in logger_calls
184
+ ]
185
+
186
+ def _find_logger_calls_in_body(self, body: list[ast.stmt]) -> list[ast.Call]:
187
+ """Find all logger calls in a list of statements.
188
+
189
+ Args:
190
+ body: List of AST statements
191
+
192
+ Returns:
193
+ List of Call nodes that are logger calls
194
+ """
195
+ logger_calls: list[ast.Call] = []
196
+ for stmt in body:
197
+ for node in ast.walk(stmt):
198
+ if isinstance(node, ast.Call) and is_logger_call(node):
199
+ logger_calls.append(node)
200
+ return logger_calls
@@ -0,0 +1,254 @@
1
+ """
2
+ Purpose: Lint rule for detecting conditional verbose logging anti-patterns
3
+
4
+ Scope: Detection of if verbose: logger.*() patterns in Python code
5
+
6
+ Overview: Implements the ConditionalVerboseRule that detects logging calls conditionally guarded
7
+ by verbose flags. This is an anti-pattern because logging levels should be configured through
8
+ the logging framework (e.g., logger.setLevel(logging.DEBUG)) rather than through code
9
+ conditionals. The rule reports violations with suggestions to remove the conditional and
10
+ configure logging levels properly. Only applies to Python files as this pattern is specific
11
+ to Python logging practices.
12
+
13
+ Dependencies: BaseLintContext and BaseLintRule from core, ast module, conditional_verbose_analyzer
14
+
15
+ Exports: ConditionalVerboseRule class implementing BaseLintRule interface
16
+
17
+ Interfaces: check(context) -> list[Violation] for rule validation, standard rule properties
18
+ (rule_id, rule_name, description)
19
+
20
+ Implementation: AST-based analysis using ConditionalVerboseAnalyzer for pattern detection
21
+ """
22
+
23
+ import ast
24
+ from pathlib import Path
25
+
26
+ from src.core.base import BaseLintContext, BaseLintRule
27
+ from src.core.constants import Language
28
+ from src.core.linter_utils import has_file_content, load_linter_config
29
+ from src.core.types import Violation
30
+ from src.core.violation_utils import get_violation_line, has_python_noqa
31
+ from src.linter_config.ignore import get_ignore_parser
32
+
33
+ from .conditional_verbose_analyzer import ConditionalVerboseAnalyzer
34
+ from .config import PrintStatementConfig
35
+
36
+
37
+ class ConditionalVerboseRule(BaseLintRule):
38
+ """Detects conditional verbose logging patterns that should use log level configuration."""
39
+
40
+ def __init__(self) -> None:
41
+ """Initialize the conditional verbose rule."""
42
+ self._ignore_parser = get_ignore_parser()
43
+
44
+ @property
45
+ def rule_id(self) -> str:
46
+ """Unique identifier for this rule."""
47
+ return "improper-logging.conditional-verbose"
48
+
49
+ @property
50
+ def rule_name(self) -> str:
51
+ """Human-readable name for this rule."""
52
+ return "Improper Logging - Conditional Verbose"
53
+
54
+ @property
55
+ def description(self) -> str:
56
+ """Description of what this rule checks."""
57
+ return "Conditional verbose logging should use log level configuration instead"
58
+
59
+ def check(self, context: BaseLintContext) -> list[Violation]:
60
+ """Check for conditional verbose logging violations.
61
+
62
+ Only applies to Python files, as this pattern is Python-specific.
63
+
64
+ Args:
65
+ context: Lint context with file information
66
+
67
+ Returns:
68
+ List of violations found
69
+ """
70
+ if not self._should_analyze(context):
71
+ return []
72
+
73
+ tree = self._parse_python_code(context.file_content)
74
+ if tree is None:
75
+ return []
76
+
77
+ analyzer = ConditionalVerboseAnalyzer()
78
+ conditional_calls = analyzer.find_conditional_verbose_calls(tree)
79
+
80
+ return self._collect_violations(conditional_calls, context)
81
+
82
+ def _should_analyze(self, context: BaseLintContext) -> bool:
83
+ """Check if this file should be analyzed."""
84
+ if not has_file_content(context):
85
+ return False
86
+ if context.language != Language.PYTHON:
87
+ return False
88
+ config = self._load_config(context)
89
+ if not config.enabled:
90
+ return False
91
+ return not self._is_file_ignored(context, config)
92
+
93
+ def _load_config(self, context: BaseLintContext) -> PrintStatementConfig:
94
+ """Load configuration from context.
95
+
96
+ Uses the same config as print-statements linter for consistency.
97
+
98
+ Args:
99
+ context: Lint context
100
+
101
+ Returns:
102
+ PrintStatementConfig instance
103
+ """
104
+ test_config = self._try_load_test_config(context)
105
+ if test_config is not None:
106
+ return test_config
107
+
108
+ prod_config = self._try_load_production_config(context)
109
+ if prod_config is not None:
110
+ return prod_config
111
+
112
+ return PrintStatementConfig()
113
+
114
+ def _try_load_test_config(self, context: BaseLintContext) -> PrintStatementConfig | None:
115
+ """Try to load test-style configuration."""
116
+ if not hasattr(context, "config"):
117
+ return None
118
+ config_attr = context.config
119
+ if config_attr is None or not isinstance(config_attr, dict):
120
+ return None
121
+ return PrintStatementConfig.from_dict(config_attr, context.language)
122
+
123
+ def _try_load_production_config(self, context: BaseLintContext) -> PrintStatementConfig | None:
124
+ """Try to load production configuration."""
125
+ if not hasattr(context, "metadata") or not isinstance(context.metadata, dict):
126
+ return None
127
+
128
+ metadata = context.metadata
129
+ config_keys = ("print_statements", "print-statements", "improper-logging")
130
+
131
+ for key in config_keys:
132
+ if key in metadata:
133
+ return load_linter_config(context, key, PrintStatementConfig)
134
+
135
+ return None
136
+
137
+ def _is_file_ignored(self, context: BaseLintContext, config: PrintStatementConfig) -> bool:
138
+ """Check if file matches ignore patterns."""
139
+ if not config.ignore:
140
+ return False
141
+ if not context.file_path:
142
+ return False
143
+
144
+ file_path = Path(context.file_path)
145
+ return any(self._matches_pattern(file_path, pattern) for pattern in config.ignore)
146
+
147
+ def _matches_pattern(self, file_path: Path, pattern: str) -> bool:
148
+ """Check if file path matches a glob pattern."""
149
+ if file_path.match(pattern):
150
+ return True
151
+ if pattern in str(file_path):
152
+ return True
153
+ return False
154
+
155
+ def _parse_python_code(self, code: str | None) -> ast.AST | None:
156
+ """Parse Python code into AST."""
157
+ try:
158
+ return ast.parse(code or "")
159
+ except SyntaxError:
160
+ return None
161
+
162
+ def _collect_violations(
163
+ self,
164
+ conditional_calls: list[tuple[ast.If, ast.Call, str, int]],
165
+ context: BaseLintContext,
166
+ ) -> list[Violation]:
167
+ """Collect violations from conditional verbose logging patterns.
168
+
169
+ Args:
170
+ conditional_calls: List of (if_node, call_node, method_name, line_number) tuples
171
+ context: Lint context
172
+
173
+ Returns:
174
+ List of violations
175
+ """
176
+ violations = []
177
+ for _if_node, _call_node, method_name, line_number in conditional_calls:
178
+ violation = self._create_violation(method_name, line_number, context)
179
+ if not self._should_ignore(violation, context):
180
+ violations.append(violation)
181
+ return violations
182
+
183
+ def _create_violation(
184
+ self,
185
+ method_name: str,
186
+ line: int,
187
+ context: BaseLintContext,
188
+ ) -> Violation:
189
+ """Create a violation for a conditional verbose logging pattern.
190
+
191
+ Args:
192
+ method_name: The logger method name (debug, info, etc.)
193
+ line: Line number where the violation occurs
194
+ context: Lint context
195
+
196
+ Returns:
197
+ Violation object with details about the pattern
198
+ """
199
+ message = f"Conditional verbose check around logger.{method_name}() should be removed"
200
+ suggestion = (
201
+ "Remove the 'if verbose:' condition and configure logging level instead. "
202
+ "Use logger.setLevel(logging.DEBUG) to control verbosity."
203
+ )
204
+
205
+ return Violation(
206
+ rule_id=self.rule_id,
207
+ file_path=str(context.file_path) if context.file_path else "",
208
+ line=line,
209
+ column=0,
210
+ message=message,
211
+ suggestion=suggestion,
212
+ )
213
+
214
+ def _should_ignore(self, violation: Violation, context: BaseLintContext) -> bool:
215
+ """Check if violation should be ignored based on inline directives.
216
+
217
+ Args:
218
+ violation: Violation to check
219
+ context: Lint context with file content
220
+
221
+ Returns:
222
+ True if violation should be ignored
223
+ """
224
+ if self._ignore_parser.should_ignore_violation(violation, context.file_content or ""):
225
+ return True
226
+ return self._check_generic_ignore(violation, context)
227
+
228
+ def _check_generic_ignore(self, violation: Violation, context: BaseLintContext) -> bool:
229
+ """Check for generic ignore directives.
230
+
231
+ Args:
232
+ violation: Violation to check
233
+ context: Lint context
234
+
235
+ Returns:
236
+ True if line has generic ignore directive
237
+ """
238
+ line_text = get_violation_line(violation, context)
239
+ if line_text is None:
240
+ return False
241
+ return self._has_generic_ignore_directive(line_text)
242
+
243
+ def _has_generic_ignore_directive(self, line_text: str) -> bool:
244
+ """Check if line has generic ignore directive."""
245
+ if self._has_generic_thailint_ignore(line_text):
246
+ return True
247
+ return has_python_noqa(line_text)
248
+
249
+ def _has_generic_thailint_ignore(self, line_text: str) -> bool:
250
+ """Check for generic thailint: ignore (no brackets)."""
251
+ if "# thailint: ignore" not in line_text:
252
+ return False
253
+ after_ignore = line_text.split("# thailint: ignore")[1].split("#")[0]
254
+ return "[" not in after_ignore
@@ -54,12 +54,12 @@ class PrintStatementRule(MultiLanguageLintRule): # thailint: ignore[srp]
54
54
  @property
55
55
  def rule_id(self) -> str:
56
56
  """Unique identifier for this rule."""
57
- return "print-statements.detected"
57
+ return "improper-logging.print-statement"
58
58
 
59
59
  @property
60
60
  def rule_name(self) -> str:
61
61
  """Human-readable name for this rule."""
62
- return "Print Statements"
62
+ return "Improper Logging - Print Statement"
63
63
 
64
64
  @property
65
65
  def description(self) -> str:
@@ -25,6 +25,7 @@ from src.core.types import Severity, Violation
25
25
 
26
26
  from .config import SRPConfig
27
27
  from .python_analyzer import PythonSRPAnalyzer
28
+ from .rust_analyzer import RustSRPAnalyzer
28
29
  from .typescript_analyzer import TypeScriptSRPAnalyzer
29
30
 
30
31
 
@@ -36,6 +37,7 @@ class ClassAnalyzer:
36
37
  # Singleton analyzers for performance (avoid recreating per-file)
37
38
  self._python_analyzer = PythonSRPAnalyzer()
38
39
  self._typescript_analyzer = TypeScriptSRPAnalyzer()
40
+ self._rust_analyzer = RustSRPAnalyzer()
39
41
 
40
42
  def analyze_python(
41
43
  self, context: BaseLintContext, config: SRPConfig
@@ -81,6 +83,53 @@ class ClassAnalyzer:
81
83
  for class_node in classes
82
84
  ]
83
85
 
86
+ def analyze_rust(self, context: BaseLintContext, config: SRPConfig) -> list[dict[str, Any]]:
87
+ """Analyze Rust structs and return metrics.
88
+
89
+ Finds all struct declarations and impl blocks, matches impl blocks to
90
+ their target structs, and returns metrics for each struct.
91
+
92
+ Args:
93
+ context: Lint context with file information
94
+ config: SRP configuration
95
+
96
+ Returns:
97
+ List of struct metrics dicts
98
+ """
99
+ root_node = self._rust_analyzer.parse_rust(context.file_content or "")
100
+ if not root_node:
101
+ return []
102
+
103
+ structs = self._rust_analyzer.find_all_structs(root_node)
104
+ impl_blocks = self._rust_analyzer.find_all_impl_blocks(root_node)
105
+ impl_map = self._build_impl_map(impl_blocks)
106
+
107
+ return [
108
+ self._rust_analyzer.analyze_struct(
109
+ struct_node,
110
+ impl_map.get(self._rust_analyzer.get_impl_target_name(struct_node), []),
111
+ context.file_content or "",
112
+ config,
113
+ )
114
+ for struct_node in structs
115
+ ]
116
+
117
+ def _build_impl_map(self, impl_blocks: list) -> dict[str, list]:
118
+ """Build mapping from struct name to its impl blocks.
119
+
120
+ Args:
121
+ impl_blocks: List of impl_item nodes
122
+
123
+ Returns:
124
+ Dictionary mapping struct names to lists of impl blocks
125
+ """
126
+ impl_map: dict[str, list] = {}
127
+ for impl_node in impl_blocks:
128
+ target_name = self._rust_analyzer.get_impl_target_name(impl_node)
129
+ if target_name:
130
+ impl_map.setdefault(target_name, []).append(impl_node)
131
+ return impl_map
132
+
84
133
  def _parse_python_safely(self, context: BaseLintContext) -> ast.AST | list[Violation]:
85
134
  """Parse Python code and return AST or syntax error violations.
86
135
 
src/linters/srp/linter.py CHANGED
@@ -21,6 +21,7 @@ Suppressions:
21
21
  - type:ignore[return-value]: Generic TypeScript analyzer return type variance
22
22
  """
23
23
 
24
+ from src.analyzers.rust_base import TREE_SITTER_RUST_AVAILABLE
24
25
  from src.core.base import BaseLintContext, MultiLanguageLintRule
25
26
  from src.core.constants import Language
26
27
  from src.core.linter_utils import load_linter_config
@@ -110,6 +111,9 @@ class SRPRule(MultiLanguageLintRule):
110
111
  if context.language in (Language.TYPESCRIPT, Language.JAVASCRIPT):
111
112
  return self._check_typescript(context, config)
112
113
 
114
+ if context.language == Language.RUST:
115
+ return self._check_rust(context, config)
116
+
113
117
  return []
114
118
 
115
119
  def _load_config(self, context: BaseLintContext) -> SRPConfig:
@@ -204,6 +208,24 @@ class SRPRule(MultiLanguageLintRule):
204
208
 
205
209
  return violation
206
210
 
211
+ def _check_rust(self, context: BaseLintContext, config: SRPConfig) -> list[Violation]:
212
+ """Check Rust code for SRP violations.
213
+
214
+ Analyzes struct declarations and their impl blocks to detect structs
215
+ with too many methods, excessive lines of code, or generic naming.
216
+
217
+ Args:
218
+ context: Lint context with file information
219
+ config: SRP configuration
220
+
221
+ Returns:
222
+ List of violations found
223
+ """
224
+ if not TREE_SITTER_RUST_AVAILABLE:
225
+ return []
226
+ metrics_list = self._class_analyzer.analyze_rust(context, config)
227
+ return self._build_violations_from_metrics(metrics_list, config, context)
228
+
207
229
  def _check_typescript(self, context: BaseLintContext, config: SRPConfig) -> list[Violation]:
208
230
  """Check TypeScript code for SRP violations.
209
231