thailint 0.1.6__py3-none-any.whl → 0.2.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 (68) hide show
  1. src/__init__.py +7 -2
  2. src/analyzers/__init__.py +23 -0
  3. src/analyzers/typescript_base.py +148 -0
  4. src/api.py +1 -1
  5. src/cli.py +498 -141
  6. src/config.py +6 -31
  7. src/core/base.py +12 -0
  8. src/core/cli_utils.py +206 -0
  9. src/core/config_parser.py +99 -0
  10. src/core/linter_utils.py +168 -0
  11. src/core/registry.py +17 -92
  12. src/core/rule_discovery.py +132 -0
  13. src/core/violation_builder.py +122 -0
  14. src/linter_config/ignore.py +112 -40
  15. src/linter_config/loader.py +3 -13
  16. src/linters/dry/__init__.py +23 -0
  17. src/linters/dry/base_token_analyzer.py +76 -0
  18. src/linters/dry/block_filter.py +262 -0
  19. src/linters/dry/block_grouper.py +59 -0
  20. src/linters/dry/cache.py +218 -0
  21. src/linters/dry/cache_query.py +61 -0
  22. src/linters/dry/config.py +130 -0
  23. src/linters/dry/config_loader.py +44 -0
  24. src/linters/dry/deduplicator.py +120 -0
  25. src/linters/dry/duplicate_storage.py +126 -0
  26. src/linters/dry/file_analyzer.py +127 -0
  27. src/linters/dry/inline_ignore.py +140 -0
  28. src/linters/dry/linter.py +170 -0
  29. src/linters/dry/python_analyzer.py +517 -0
  30. src/linters/dry/storage_initializer.py +51 -0
  31. src/linters/dry/token_hasher.py +115 -0
  32. src/linters/dry/typescript_analyzer.py +590 -0
  33. src/linters/dry/violation_builder.py +74 -0
  34. src/linters/dry/violation_filter.py +91 -0
  35. src/linters/dry/violation_generator.py +174 -0
  36. src/linters/file_placement/config_loader.py +86 -0
  37. src/linters/file_placement/directory_matcher.py +80 -0
  38. src/linters/file_placement/linter.py +252 -472
  39. src/linters/file_placement/path_resolver.py +61 -0
  40. src/linters/file_placement/pattern_matcher.py +55 -0
  41. src/linters/file_placement/pattern_validator.py +106 -0
  42. src/linters/file_placement/rule_checker.py +229 -0
  43. src/linters/file_placement/violation_factory.py +177 -0
  44. src/linters/nesting/config.py +13 -3
  45. src/linters/nesting/linter.py +76 -152
  46. src/linters/nesting/typescript_analyzer.py +38 -102
  47. src/linters/nesting/typescript_function_extractor.py +130 -0
  48. src/linters/nesting/violation_builder.py +139 -0
  49. src/linters/srp/__init__.py +99 -0
  50. src/linters/srp/class_analyzer.py +113 -0
  51. src/linters/srp/config.py +76 -0
  52. src/linters/srp/heuristics.py +89 -0
  53. src/linters/srp/linter.py +225 -0
  54. src/linters/srp/metrics_evaluator.py +47 -0
  55. src/linters/srp/python_analyzer.py +72 -0
  56. src/linters/srp/typescript_analyzer.py +75 -0
  57. src/linters/srp/typescript_metrics_calculator.py +90 -0
  58. src/linters/srp/violation_builder.py +117 -0
  59. src/orchestrator/core.py +42 -7
  60. src/utils/__init__.py +4 -0
  61. src/utils/project_root.py +84 -0
  62. {thailint-0.1.6.dist-info → thailint-0.2.0.dist-info}/METADATA +414 -63
  63. thailint-0.2.0.dist-info/RECORD +75 -0
  64. src/.ai/layout.yaml +0 -48
  65. thailint-0.1.6.dist-info/RECORD +0 -28
  66. {thailint-0.1.6.dist-info → thailint-0.2.0.dist-info}/LICENSE +0 -0
  67. {thailint-0.1.6.dist-info → thailint-0.2.0.dist-info}/WHEEL +0 -0
  68. {thailint-0.1.6.dist-info → thailint-0.2.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,225 @@
1
+ """
2
+ Purpose: Main SRP linter rule implementation
3
+
4
+ Scope: SRPRule class implementing BaseLintRule interface
5
+
6
+ Overview: Implements Single Responsibility Principle linter rule following BaseLintRule interface.
7
+ Orchestrates configuration loading, class analysis, metrics evaluation, and violation building
8
+ through focused helper classes. Detects classes with too many methods, excessive lines of code,
9
+ or generic naming patterns. Supports configurable thresholds and ignore directives. Handles both
10
+ Python and TypeScript code analysis. Main rule class acts as coordinator for SRP checking workflow.
11
+
12
+ Dependencies: BaseLintRule, BaseLintContext, Violation, ClassAnalyzer, MetricsEvaluator, ViolationBuilder
13
+
14
+ Exports: SRPRule class
15
+
16
+ Interfaces: SRPRule.check(context) -> list[Violation], properties for rule metadata
17
+
18
+ Implementation: Composition pattern with helper classes, heuristic-based SRP analysis
19
+ """
20
+
21
+ from src.core.base import BaseLintContext, BaseLintRule
22
+ from src.core.linter_utils import has_file_content, load_linter_config
23
+ from src.core.types import Violation
24
+ from src.linter_config.ignore import IgnoreDirectiveParser
25
+
26
+ from .class_analyzer import ClassAnalyzer
27
+ from .config import SRPConfig
28
+ from .metrics_evaluator import evaluate_metrics
29
+ from .violation_builder import ViolationBuilder
30
+
31
+
32
+ class SRPRule(BaseLintRule):
33
+ """Detects Single Responsibility Principle violations in classes."""
34
+
35
+ def __init__(self) -> None:
36
+ """Initialize the SRP rule."""
37
+ self._ignore_parser = IgnoreDirectiveParser()
38
+ self._class_analyzer = ClassAnalyzer()
39
+ self._violation_builder = ViolationBuilder()
40
+
41
+ @property
42
+ def rule_id(self) -> str:
43
+ """Unique identifier for this rule."""
44
+ return "srp.violation"
45
+
46
+ @property
47
+ def rule_name(self) -> str:
48
+ """Human-readable name for this rule."""
49
+ return "Single Responsibility Principle"
50
+
51
+ @property
52
+ def description(self) -> str:
53
+ """Description of what this rule checks."""
54
+ return "Classes should have a single, well-defined responsibility"
55
+
56
+ def check(self, context: BaseLintContext) -> list[Violation]:
57
+ """Check for SRP violations.
58
+
59
+ Args:
60
+ context: Lint context with file information
61
+
62
+ Returns:
63
+ List of violations found
64
+ """
65
+ if not self._should_check_file(context):
66
+ return []
67
+
68
+ config = load_linter_config(context, "srp", SRPConfig)
69
+ if not self._is_linter_enabled(context, config):
70
+ return []
71
+
72
+ return self._check_by_language(context, config)
73
+
74
+ def _should_check_file(self, context: BaseLintContext) -> bool:
75
+ """Check if file has content to analyze.
76
+
77
+ Args:
78
+ context: Lint context
79
+
80
+ Returns:
81
+ True if file should be checked
82
+ """
83
+ return has_file_content(context)
84
+
85
+ def _is_linter_enabled(self, context: BaseLintContext, config: SRPConfig) -> bool:
86
+ """Check if linter is enabled and file is not ignored.
87
+
88
+ Args:
89
+ context: Lint context
90
+ config: SRP configuration
91
+
92
+ Returns:
93
+ True if linter should run on this file
94
+ """
95
+ return config.enabled and not self._is_file_ignored(context, config)
96
+
97
+ def _check_by_language(self, context: BaseLintContext, config: SRPConfig) -> list[Violation]:
98
+ """Dispatch to language-specific checker.
99
+
100
+ Args:
101
+ context: Lint context
102
+ config: SRP configuration
103
+
104
+ Returns:
105
+ List of violations found
106
+ """
107
+ if context.language == "python":
108
+ return self._check_python(context, config)
109
+ if context.language in ("typescript", "javascript"):
110
+ return self._check_typescript(context, config)
111
+ return []
112
+
113
+ def _is_file_ignored(self, context: BaseLintContext, config: SRPConfig) -> bool:
114
+ """Check if file matches ignore patterns.
115
+
116
+ Args:
117
+ context: Lint context
118
+ config: SRP configuration
119
+
120
+ Returns:
121
+ True if file should be ignored
122
+ """
123
+ if not config.ignore:
124
+ return False
125
+
126
+ file_path = str(context.file_path)
127
+ for pattern in config.ignore:
128
+ if pattern in file_path:
129
+ return True
130
+ return False
131
+
132
+ def _check_python(self, context: BaseLintContext, config: SRPConfig) -> list[Violation]:
133
+ """Check Python code for SRP violations.
134
+
135
+ Args:
136
+ context: Lint context with file information
137
+ config: SRP configuration
138
+
139
+ Returns:
140
+ List of violations found
141
+ """
142
+ results = self._class_analyzer.analyze_python(context, config)
143
+ if results and isinstance(results[0], Violation): # Syntax errors
144
+ return results # type: ignore[return-value]
145
+
146
+ return self._build_violations_from_metrics(results, config, context)
147
+
148
+ def _build_violations_from_metrics(
149
+ self,
150
+ metrics_list: list,
151
+ config: SRPConfig,
152
+ context: BaseLintContext,
153
+ ) -> list[Violation]:
154
+ """Build violations from class metrics.
155
+
156
+ Args:
157
+ metrics_list: List of class metrics
158
+ config: SRP configuration
159
+ context: Lint context
160
+
161
+ Returns:
162
+ List of violations
163
+ """
164
+ violations = []
165
+ for metrics in metrics_list:
166
+ if not isinstance(metrics, dict):
167
+ continue
168
+ violation = self._create_violation_if_needed(metrics, config, context)
169
+ if violation:
170
+ violations.append(violation)
171
+ return violations
172
+
173
+ def _create_violation_if_needed(
174
+ self,
175
+ metrics: dict,
176
+ config: SRPConfig,
177
+ context: BaseLintContext,
178
+ ) -> Violation | None:
179
+ """Create violation if metrics exceed thresholds.
180
+
181
+ Args:
182
+ metrics: Class metrics dictionary
183
+ config: SRP configuration
184
+ context: Lint context
185
+
186
+ Returns:
187
+ Violation or None if no issues or should be ignored
188
+ """
189
+ issues = evaluate_metrics(metrics, config)
190
+ if not issues:
191
+ return None
192
+
193
+ violation = self._violation_builder.build_violation(metrics, issues, self.rule_id, context)
194
+ if self._should_ignore(violation, context):
195
+ return None
196
+
197
+ return violation
198
+
199
+ def _check_typescript(self, context: BaseLintContext, config: SRPConfig) -> list[Violation]:
200
+ """Check TypeScript code for SRP violations.
201
+
202
+ Args:
203
+ context: Lint context with file information
204
+ config: SRP configuration
205
+
206
+ Returns:
207
+ List of violations found
208
+ """
209
+ metrics_list = self._class_analyzer.analyze_typescript(context, config)
210
+ return self._build_violations_from_metrics(metrics_list, config, context)
211
+
212
+ def _should_ignore(self, violation: Violation, context: BaseLintContext) -> bool:
213
+ """Check if violation should be ignored based on inline directives.
214
+
215
+ Args:
216
+ violation: Violation to check
217
+ context: Lint context with file content
218
+
219
+ Returns:
220
+ True if violation should be ignored
221
+ """
222
+ if context.file_content is None:
223
+ return False
224
+
225
+ return self._ignore_parser.should_ignore_violation(violation, context.file_content)
@@ -0,0 +1,47 @@
1
+ """
2
+ Purpose: SRP metrics evaluation against configured thresholds
3
+
4
+ Scope: Evaluates class metrics to determine SRP violations
5
+
6
+ Overview: Provides metrics evaluation functionality for the SRP linter. Checks class metrics
7
+ (method count, lines of code, naming keywords) against configured thresholds. Collects
8
+ issues when metrics exceed limits. Isolates threshold evaluation logic from class analysis
9
+ and violation building to maintain single responsibility.
10
+
11
+ Dependencies: SRPConfig, typing
12
+
13
+ Exports: evaluate_metrics
14
+
15
+ Interfaces: evaluate_metrics(metrics, config) -> list[str] (returns list of issue descriptions)
16
+
17
+ Implementation: Compares numeric thresholds and keyword patterns, returns descriptive issue strings
18
+ """
19
+
20
+ from typing import Any
21
+
22
+ from .config import SRPConfig
23
+
24
+
25
+ def evaluate_metrics(metrics: dict[str, Any], config: SRPConfig) -> list[str]:
26
+ """Evaluate class metrics and collect SRP issues.
27
+
28
+ Args:
29
+ metrics: Class metrics dictionary with method_count, loc, has_keyword
30
+ config: SRP configuration with thresholds
31
+
32
+ Returns:
33
+ List of issue descriptions (empty if no violations)
34
+ """
35
+ issues = []
36
+
37
+ # Check numeric thresholds
38
+ if metrics["method_count"] > config.max_methods:
39
+ issues.append(f"{metrics['method_count']} methods (max: {config.max_methods})")
40
+ if metrics["loc"] > config.max_loc:
41
+ issues.append(f"{metrics['loc']} lines (max: {config.max_loc})")
42
+
43
+ # Check keyword heuristic
44
+ if config.check_keywords and metrics["has_keyword"]:
45
+ issues.append("responsibility keyword in name")
46
+
47
+ return issues
@@ -0,0 +1,72 @@
1
+ """
2
+ Purpose: Python AST analyzer for detecting SRP violations in Python classes
3
+
4
+ Scope: PythonSRPAnalyzer class for analyzing Python classes using AST
5
+
6
+ Overview: Implements Python-specific SRP analysis using the ast module to parse and analyze
7
+ class definitions. Walks the AST to find all class definitions, then analyzes each class
8
+ for SRP violation indicators: method count, lines of code, and responsibility keywords.
9
+ Collects comprehensive metrics including class name, method count, LOC, keyword presence,
10
+ and location information (line, column). Integrates with heuristics module for metric
11
+ calculation. Returns structured metric dictionaries that the main linter uses to create
12
+ violations. Handles nested classes by analyzing all classes in the tree.
13
+
14
+ Dependencies: ast module for Python AST parsing, typing for type hints, heuristics module
15
+
16
+ Exports: PythonSRPAnalyzer class
17
+
18
+ Interfaces: find_all_classes(tree), analyze_class(class_node, source, config)
19
+
20
+ Implementation: AST walking pattern, metric collection, integration with heuristics module
21
+ """
22
+
23
+ import ast
24
+ from typing import Any
25
+
26
+ from .config import SRPConfig
27
+ from .heuristics import count_loc, count_methods, has_responsibility_keyword
28
+
29
+
30
+ class PythonSRPAnalyzer:
31
+ """Analyzes Python classes for SRP violations."""
32
+
33
+ def find_all_classes(self, tree: ast.AST) -> list[ast.ClassDef]:
34
+ """Find all class definitions in AST.
35
+
36
+ Args:
37
+ tree: Root AST node to search
38
+
39
+ Returns:
40
+ List of all class definition nodes
41
+ """
42
+ classes = []
43
+ for node in ast.walk(tree):
44
+ if isinstance(node, ast.ClassDef):
45
+ classes.append(node)
46
+ return classes
47
+
48
+ def analyze_class(
49
+ self, class_node: ast.ClassDef, source: str, config: SRPConfig
50
+ ) -> dict[str, Any]:
51
+ """Analyze a class for SRP metrics.
52
+
53
+ Args:
54
+ class_node: AST node representing a class definition
55
+ source: Full source code of the file
56
+ config: SRP configuration with thresholds and keywords
57
+
58
+ Returns:
59
+ Dictionary with class metrics (name, method_count, loc, etc.)
60
+ """
61
+ method_count = count_methods(class_node)
62
+ loc = count_loc(class_node, source)
63
+ has_keyword = has_responsibility_keyword(class_node.name, config.keywords)
64
+
65
+ return {
66
+ "class_name": class_node.name,
67
+ "method_count": method_count,
68
+ "loc": loc,
69
+ "has_keyword": has_keyword,
70
+ "line": class_node.lineno,
71
+ "column": class_node.col_offset,
72
+ }
@@ -0,0 +1,75 @@
1
+ """
2
+ Purpose: TypeScript AST analyzer for detecting SRP violations in TypeScript classes
3
+
4
+ Scope: TypeScriptSRPAnalyzer class for analyzing TypeScript classes using tree-sitter
5
+
6
+ Overview: Implements TypeScript-specific SRP analysis using tree-sitter parser. Extends
7
+ TypeScriptBaseAnalyzer to reuse common tree-sitter initialization and traversal patterns.
8
+ Walks the AST to find all class declarations, analyzes each class for SRP violation
9
+ indicators using metrics calculator helper. Collects comprehensive metrics including
10
+ class name, method count, LOC, keyword presence, and location information. Delegates
11
+ metrics calculation to TypeScriptMetricsCalculator.
12
+
13
+ Dependencies: TypeScriptBaseAnalyzer, TypeScriptMetricsCalculator, SRPConfig
14
+
15
+ Exports: TypeScriptSRPAnalyzer class
16
+
17
+ Interfaces: find_all_classes(tree), analyze_class(class_node, source, config)
18
+
19
+ Implementation: Inherits tree-sitter parsing from base, composition with metrics calculator
20
+ """
21
+
22
+ from typing import Any
23
+
24
+ from src.analyzers.typescript_base import TypeScriptBaseAnalyzer
25
+
26
+ from .config import SRPConfig
27
+ from .typescript_metrics_calculator import TypeScriptMetricsCalculator
28
+
29
+
30
+ class TypeScriptSRPAnalyzer(TypeScriptBaseAnalyzer):
31
+ """Analyzes TypeScript classes for SRP violations."""
32
+
33
+ def __init__(self) -> None:
34
+ """Initialize analyzer with metrics calculator."""
35
+ super().__init__()
36
+ self.metrics_calculator = TypeScriptMetricsCalculator()
37
+
38
+ def find_all_classes(self, root_node: Any) -> list[Any]:
39
+ """Find all class declarations in TypeScript AST.
40
+
41
+ Args:
42
+ root_node: Root tree-sitter node to search
43
+
44
+ Returns:
45
+ List of all class declaration nodes
46
+ """
47
+ return self.walk_tree(root_node, "class_declaration")
48
+
49
+ def analyze_class(self, class_node: Any, source: str, config: SRPConfig) -> dict[str, Any]:
50
+ """Analyze a TypeScript class for SRP metrics.
51
+
52
+ Args:
53
+ class_node: Tree-sitter node representing a class declaration
54
+ source: Full source code of the file
55
+ config: SRP configuration with thresholds and keywords
56
+
57
+ Returns:
58
+ Dictionary with class metrics (name, method_count, loc, etc.)
59
+ """
60
+ class_name = self.extract_identifier_name(class_node)
61
+ if class_name == "anonymous":
62
+ class_name = "UnnamedClass"
63
+
64
+ method_count = self.metrics_calculator.count_methods(class_node)
65
+ loc = self.metrics_calculator.count_loc(class_node, source)
66
+ has_keyword = any(keyword in class_name for keyword in config.keywords)
67
+
68
+ return {
69
+ "class_name": class_name,
70
+ "method_count": method_count,
71
+ "loc": loc,
72
+ "has_keyword": has_keyword,
73
+ "line": class_node.start_point[0] + 1,
74
+ "column": class_node.start_point[1],
75
+ }
@@ -0,0 +1,90 @@
1
+ """
2
+ Purpose: TypeScript class metrics calculation for SRP analysis
3
+
4
+ Scope: Calculates method count and lines of code for TypeScript classes
5
+
6
+ Overview: Provides metrics calculation functionality for TypeScript classes in SRP analysis. Counts
7
+ public methods in class bodies (excludes constructors), calculates lines of code from AST node
8
+ positions, and identifies class body nodes. Uses tree-sitter AST node types. Isolates metrics
9
+ calculation from class analysis and tree traversal logic.
10
+
11
+ Dependencies: typing
12
+
13
+ Exports: TypeScriptMetricsCalculator
14
+
15
+ Interfaces: count_methods(class_node), count_loc(class_node, source)
16
+
17
+ Implementation: Tree-sitter node type matching, AST position arithmetic
18
+ """
19
+
20
+ from typing import Any
21
+
22
+
23
+ class TypeScriptMetricsCalculator:
24
+ """Calculates metrics for TypeScript classes."""
25
+
26
+ def count_methods(self, class_node: Any) -> int:
27
+ """Count number of methods in a TypeScript class.
28
+
29
+ Args:
30
+ class_node: Class declaration tree-sitter node
31
+
32
+ Returns:
33
+ Number of public methods (excludes constructor)
34
+ """
35
+ class_body = self._get_class_body(class_node)
36
+ if not class_body:
37
+ return 0
38
+
39
+ method_count = 0
40
+ for child in class_body.children:
41
+ if self._is_countable_method(child):
42
+ method_count += 1
43
+
44
+ return method_count
45
+
46
+ def count_loc(self, class_node: Any, source: str) -> int:
47
+ """Count lines of code in a TypeScript class.
48
+
49
+ Args:
50
+ class_node: Class declaration tree-sitter node
51
+ source: Full source code string
52
+
53
+ Returns:
54
+ Number of lines in class definition
55
+ """
56
+ start_line = class_node.start_point[0]
57
+ end_line = class_node.end_point[0]
58
+ return end_line - start_line + 1
59
+
60
+ def _get_class_body(self, class_node: Any) -> Any:
61
+ """Get the class_body node from a class declaration.
62
+
63
+ Args:
64
+ class_node: Class declaration node
65
+
66
+ Returns:
67
+ Class body node or None
68
+ """
69
+ for child in class_node.children:
70
+ if child.type == "class_body":
71
+ return child
72
+ return None
73
+
74
+ def _is_countable_method(self, node: Any) -> bool:
75
+ """Check if node is a method that should be counted.
76
+
77
+ Args:
78
+ node: Tree-sitter node to check
79
+
80
+ Returns:
81
+ True if node is a countable method
82
+ """
83
+ if node.type != "method_definition":
84
+ return False
85
+
86
+ # Check if it's a constructor
87
+ return all(
88
+ not (child.type == "property_identifier" and child.text.decode() == "constructor")
89
+ for child in node.children
90
+ )
@@ -0,0 +1,117 @@
1
+ """
2
+ Purpose: Violation creation with suggestions for SRP linter
3
+
4
+ Scope: Builds Violation objects with contextual messages and refactoring suggestions
5
+
6
+ Overview: Provides violation building functionality for the SRP linter. Creates violations
7
+ from class metrics and issue descriptions, generates contextual error messages, and
8
+ provides actionable refactoring suggestions based on issue types (methods, lines, keywords).
9
+ Isolates violation construction and suggestion generation from metrics evaluation and
10
+ class analysis to maintain single responsibility.
11
+
12
+ Dependencies: BaseLintContext, Violation, Severity, typing, src.core.violation_builder
13
+
14
+ Exports: ViolationBuilder
15
+
16
+ Interfaces: build_violation(metrics, issues, rule_id, context) -> Violation
17
+
18
+ Implementation: Formats messages from metrics, generates targeted suggestions per issue type,
19
+ extends BaseViolationBuilder for consistent violation construction
20
+ """
21
+
22
+ from typing import Any
23
+
24
+ from src.core.base import BaseLintContext
25
+ from src.core.types import Severity, Violation
26
+ from src.core.violation_builder import BaseViolationBuilder, ViolationInfo
27
+
28
+
29
+ class ViolationBuilder(BaseViolationBuilder):
30
+ """Builds SRP violations with messages and suggestions."""
31
+
32
+ def build_violation(
33
+ self,
34
+ metrics: dict[str, Any],
35
+ issues: list[str],
36
+ rule_id: str,
37
+ context: BaseLintContext,
38
+ ) -> Violation:
39
+ """Build violation from metrics and issues.
40
+
41
+ Args:
42
+ metrics: Class metrics dictionary
43
+ issues: List of issue descriptions
44
+ rule_id: Rule identifier
45
+ context: Lint context
46
+
47
+ Returns:
48
+ Violation with message and suggestion
49
+ """
50
+ message = f"Class '{metrics['class_name']}' may violate SRP: {', '.join(issues)}"
51
+ suggestion = self._generate_suggestion(issues)
52
+
53
+ info = ViolationInfo(
54
+ rule_id=rule_id,
55
+ file_path=str(context.file_path or ""),
56
+ line=metrics["line"],
57
+ column=metrics["column"],
58
+ message=message,
59
+ severity=Severity.ERROR,
60
+ suggestion=suggestion,
61
+ )
62
+ return self.build(info)
63
+
64
+ def _generate_suggestion(self, issues: list[str]) -> str:
65
+ """Generate refactoring suggestion based on issues.
66
+
67
+ Args:
68
+ issues: List of issue descriptions
69
+
70
+ Returns:
71
+ Suggestion string with refactoring advice
72
+ """
73
+ suggestions = [
74
+ self._suggest_for_methods(issues),
75
+ self._suggest_for_lines(issues),
76
+ self._suggest_for_keywords(issues),
77
+ ]
78
+ return ". ".join(filter(None, suggestions))
79
+
80
+ def _suggest_for_methods(self, issues: list[str]) -> str:
81
+ """Suggest fix for too many methods.
82
+
83
+ Args:
84
+ issues: List of issue descriptions
85
+
86
+ Returns:
87
+ Suggestion string or empty string
88
+ """
89
+ if any("methods" in issue for issue in issues):
90
+ return "Consider extracting related methods into separate classes"
91
+ return ""
92
+
93
+ def _suggest_for_lines(self, issues: list[str]) -> str:
94
+ """Suggest fix for too many lines.
95
+
96
+ Args:
97
+ issues: List of issue descriptions
98
+
99
+ Returns:
100
+ Suggestion string or empty string
101
+ """
102
+ if any("lines" in issue for issue in issues):
103
+ return "Consider breaking the class into smaller, focused classes"
104
+ return ""
105
+
106
+ def _suggest_for_keywords(self, issues: list[str]) -> str:
107
+ """Suggest fix for responsibility keywords.
108
+
109
+ Args:
110
+ issues: List of issue descriptions
111
+
112
+ Returns:
113
+ Suggestion string or empty string
114
+ """
115
+ if any("keyword" in issue for issue in issues):
116
+ return "Avoid generic names like Manager, Handler, Processor"
117
+ return ""