crackerjack 0.31.10__py3-none-any.whl → 0.31.13__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.
Potentially problematic release.
This version of crackerjack might be problematic. Click here for more details.
- crackerjack/CLAUDE.md +288 -705
- crackerjack/__main__.py +22 -8
- crackerjack/agents/__init__.py +0 -3
- crackerjack/agents/architect_agent.py +0 -43
- crackerjack/agents/base.py +1 -9
- crackerjack/agents/coordinator.py +2 -148
- crackerjack/agents/documentation_agent.py +109 -81
- crackerjack/agents/dry_agent.py +122 -97
- crackerjack/agents/formatting_agent.py +3 -16
- crackerjack/agents/import_optimization_agent.py +1174 -130
- crackerjack/agents/performance_agent.py +956 -188
- crackerjack/agents/performance_helpers.py +229 -0
- crackerjack/agents/proactive_agent.py +1 -48
- crackerjack/agents/refactoring_agent.py +516 -246
- crackerjack/agents/refactoring_helpers.py +282 -0
- crackerjack/agents/security_agent.py +393 -90
- crackerjack/agents/test_creation_agent.py +1776 -120
- crackerjack/agents/test_specialist_agent.py +59 -15
- crackerjack/agents/tracker.py +0 -102
- crackerjack/api.py +145 -37
- crackerjack/cli/handlers.py +48 -30
- crackerjack/cli/interactive.py +11 -11
- crackerjack/cli/options.py +66 -4
- crackerjack/code_cleaner.py +808 -148
- crackerjack/config/global_lock_config.py +110 -0
- crackerjack/config/hooks.py +43 -64
- crackerjack/core/async_workflow_orchestrator.py +247 -97
- crackerjack/core/autofix_coordinator.py +192 -109
- crackerjack/core/enhanced_container.py +46 -63
- crackerjack/core/file_lifecycle.py +549 -0
- crackerjack/core/performance.py +9 -8
- crackerjack/core/performance_monitor.py +395 -0
- crackerjack/core/phase_coordinator.py +281 -94
- crackerjack/core/proactive_workflow.py +9 -58
- crackerjack/core/resource_manager.py +501 -0
- crackerjack/core/service_watchdog.py +490 -0
- crackerjack/core/session_coordinator.py +4 -8
- crackerjack/core/timeout_manager.py +504 -0
- crackerjack/core/websocket_lifecycle.py +475 -0
- crackerjack/core/workflow_orchestrator.py +343 -209
- crackerjack/dynamic_config.py +50 -9
- crackerjack/errors.py +3 -4
- crackerjack/executors/async_hook_executor.py +63 -13
- crackerjack/executors/cached_hook_executor.py +14 -14
- crackerjack/executors/hook_executor.py +100 -37
- crackerjack/executors/hook_lock_manager.py +856 -0
- crackerjack/executors/individual_hook_executor.py +120 -86
- crackerjack/intelligence/__init__.py +0 -7
- crackerjack/intelligence/adaptive_learning.py +13 -86
- crackerjack/intelligence/agent_orchestrator.py +15 -78
- crackerjack/intelligence/agent_registry.py +12 -59
- crackerjack/intelligence/agent_selector.py +31 -92
- crackerjack/intelligence/integration.py +1 -41
- crackerjack/interactive.py +9 -9
- crackerjack/managers/async_hook_manager.py +25 -8
- crackerjack/managers/hook_manager.py +9 -9
- crackerjack/managers/publish_manager.py +57 -59
- crackerjack/managers/test_command_builder.py +6 -36
- crackerjack/managers/test_executor.py +9 -61
- crackerjack/managers/test_manager.py +17 -63
- crackerjack/managers/test_manager_backup.py +77 -127
- crackerjack/managers/test_progress.py +4 -23
- crackerjack/mcp/cache.py +5 -12
- crackerjack/mcp/client_runner.py +10 -10
- crackerjack/mcp/context.py +64 -6
- crackerjack/mcp/dashboard.py +14 -11
- crackerjack/mcp/enhanced_progress_monitor.py +55 -55
- crackerjack/mcp/file_monitor.py +72 -42
- crackerjack/mcp/progress_components.py +103 -84
- crackerjack/mcp/progress_monitor.py +122 -49
- crackerjack/mcp/rate_limiter.py +12 -12
- crackerjack/mcp/server_core.py +16 -22
- crackerjack/mcp/service_watchdog.py +26 -26
- crackerjack/mcp/state.py +15 -0
- crackerjack/mcp/tools/core_tools.py +95 -39
- crackerjack/mcp/tools/error_analyzer.py +6 -32
- crackerjack/mcp/tools/execution_tools.py +1 -56
- crackerjack/mcp/tools/execution_tools_backup.py +35 -131
- crackerjack/mcp/tools/intelligence_tool_registry.py +0 -36
- crackerjack/mcp/tools/intelligence_tools.py +2 -55
- crackerjack/mcp/tools/monitoring_tools.py +308 -145
- crackerjack/mcp/tools/proactive_tools.py +12 -42
- crackerjack/mcp/tools/progress_tools.py +23 -15
- crackerjack/mcp/tools/utility_tools.py +3 -40
- crackerjack/mcp/tools/workflow_executor.py +40 -60
- crackerjack/mcp/websocket/app.py +0 -3
- crackerjack/mcp/websocket/endpoints.py +206 -268
- crackerjack/mcp/websocket/jobs.py +213 -66
- crackerjack/mcp/websocket/server.py +84 -6
- crackerjack/mcp/websocket/websocket_handler.py +137 -29
- crackerjack/models/config_adapter.py +3 -16
- crackerjack/models/protocols.py +162 -3
- crackerjack/models/resource_protocols.py +454 -0
- crackerjack/models/task.py +3 -3
- crackerjack/monitoring/__init__.py +0 -0
- crackerjack/monitoring/ai_agent_watchdog.py +25 -71
- crackerjack/monitoring/regression_prevention.py +28 -87
- crackerjack/orchestration/advanced_orchestrator.py +44 -78
- crackerjack/orchestration/coverage_improvement.py +10 -60
- crackerjack/orchestration/execution_strategies.py +16 -16
- crackerjack/orchestration/test_progress_streamer.py +61 -53
- crackerjack/plugins/base.py +1 -1
- crackerjack/plugins/managers.py +22 -20
- crackerjack/py313.py +65 -21
- crackerjack/services/backup_service.py +467 -0
- crackerjack/services/bounded_status_operations.py +627 -0
- crackerjack/services/cache.py +7 -9
- crackerjack/services/config.py +35 -52
- crackerjack/services/config_integrity.py +5 -16
- crackerjack/services/config_merge.py +542 -0
- crackerjack/services/contextual_ai_assistant.py +17 -19
- crackerjack/services/coverage_ratchet.py +44 -73
- crackerjack/services/debug.py +25 -39
- crackerjack/services/dependency_monitor.py +52 -50
- crackerjack/services/enhanced_filesystem.py +14 -11
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/filesystem.py +1 -12
- crackerjack/services/git.py +71 -47
- crackerjack/services/health_metrics.py +31 -27
- crackerjack/services/initialization.py +276 -428
- crackerjack/services/input_validator.py +760 -0
- crackerjack/services/log_manager.py +16 -16
- crackerjack/services/logging.py +7 -6
- crackerjack/services/metrics.py +43 -43
- crackerjack/services/pattern_cache.py +2 -31
- crackerjack/services/pattern_detector.py +26 -63
- crackerjack/services/performance_benchmarks.py +20 -45
- crackerjack/services/regex_patterns.py +2887 -0
- crackerjack/services/regex_utils.py +537 -0
- crackerjack/services/secure_path_utils.py +683 -0
- crackerjack/services/secure_status_formatter.py +534 -0
- crackerjack/services/secure_subprocess.py +605 -0
- crackerjack/services/security.py +47 -10
- crackerjack/services/security_logger.py +492 -0
- crackerjack/services/server_manager.py +109 -50
- crackerjack/services/smart_scheduling.py +8 -25
- crackerjack/services/status_authentication.py +603 -0
- crackerjack/services/status_security_manager.py +442 -0
- crackerjack/services/thread_safe_status_collector.py +546 -0
- crackerjack/services/tool_version_service.py +1 -23
- crackerjack/services/unified_config.py +36 -58
- crackerjack/services/validation_rate_limiter.py +269 -0
- crackerjack/services/version_checker.py +9 -40
- crackerjack/services/websocket_resource_limiter.py +572 -0
- crackerjack/slash_commands/__init__.py +52 -2
- crackerjack/tools/__init__.py +0 -0
- crackerjack/tools/validate_input_validator_patterns.py +262 -0
- crackerjack/tools/validate_regex_patterns.py +198 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/METADATA +197 -12
- crackerjack-0.31.13.dist-info/RECORD +178 -0
- crackerjack/cli/facade.py +0 -104
- crackerjack-0.31.10.dist-info/RECORD +0 -149
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/WHEEL +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,31 +1,69 @@
|
|
|
1
1
|
import ast
|
|
2
2
|
import operator
|
|
3
|
+
import time
|
|
3
4
|
import typing as t
|
|
4
5
|
from contextlib import suppress
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
|
|
8
|
+
from ..services.regex_patterns import SAFE_PATTERNS
|
|
9
|
+
from . import performance_helpers
|
|
7
10
|
from .base import (
|
|
11
|
+
AgentContext,
|
|
8
12
|
FixResult,
|
|
9
13
|
Issue,
|
|
10
14
|
IssueType,
|
|
11
15
|
SubAgent,
|
|
12
16
|
agent_registry,
|
|
13
17
|
)
|
|
18
|
+
from .performance_helpers import OptimizationResult
|
|
14
19
|
|
|
15
20
|
|
|
16
21
|
class PerformanceAgent(SubAgent):
|
|
17
|
-
"""
|
|
22
|
+
"""Enhanced PerformanceAgent with automated O(n²) detection and measurable optimizations."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, context: AgentContext) -> None:
|
|
25
|
+
super().__init__(context)
|
|
26
|
+
self.performance_metrics: dict[str, t.Any] = {}
|
|
27
|
+
self.optimization_stats: dict[str, int] = {
|
|
28
|
+
"nested_loops_optimized": 0,
|
|
29
|
+
"list_ops_optimized": 0,
|
|
30
|
+
"string_concat_optimized": 0,
|
|
31
|
+
"repeated_ops_cached": 0,
|
|
32
|
+
"comprehensions_applied": 0,
|
|
33
|
+
}
|
|
18
34
|
|
|
19
35
|
def get_supported_types(self) -> set[IssueType]:
|
|
20
36
|
return {IssueType.PERFORMANCE}
|
|
21
37
|
|
|
22
38
|
async def can_handle(self, issue: Issue) -> float:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
39
|
+
"""Enhanced confidence scoring based on issue complexity."""
|
|
40
|
+
if issue.type != IssueType.PERFORMANCE:
|
|
41
|
+
return 0.0
|
|
42
|
+
|
|
43
|
+
# Higher confidence for specific performance patterns
|
|
44
|
+
confidence = 0.85
|
|
45
|
+
message_lower = issue.message.lower()
|
|
46
|
+
|
|
47
|
+
# Boost confidence for known optimization patterns
|
|
48
|
+
if any(
|
|
49
|
+
pattern in message_lower
|
|
50
|
+
for pattern in (
|
|
51
|
+
"nested loop",
|
|
52
|
+
"o(n²)",
|
|
53
|
+
"string concatenation",
|
|
54
|
+
"list concatenation",
|
|
55
|
+
"inefficient",
|
|
56
|
+
"complexity",
|
|
57
|
+
)
|
|
58
|
+
):
|
|
59
|
+
confidence = 0.9
|
|
60
|
+
|
|
61
|
+
return confidence
|
|
26
62
|
|
|
27
63
|
async def analyze_and_fix(self, issue: Issue) -> FixResult:
|
|
64
|
+
"""Enhanced analysis with performance measurement and optimization tracking."""
|
|
28
65
|
self.log(f"Analyzing performance issue: {issue.message}")
|
|
66
|
+
start_time = time.time()
|
|
29
67
|
|
|
30
68
|
validation_result = self._validate_performance_issue(issue)
|
|
31
69
|
if validation_result:
|
|
@@ -41,12 +79,27 @@ class PerformanceAgent(SubAgent):
|
|
|
41
79
|
file_path = Path(issue.file_path)
|
|
42
80
|
|
|
43
81
|
try:
|
|
44
|
-
|
|
82
|
+
result = await self._process_performance_optimization(file_path)
|
|
83
|
+
|
|
84
|
+
# Track performance metrics
|
|
85
|
+
analysis_time = time.time() - start_time
|
|
86
|
+
self.performance_metrics[str(file_path)] = {
|
|
87
|
+
"analysis_duration": analysis_time,
|
|
88
|
+
"optimizations_applied": result.fixes_applied,
|
|
89
|
+
"timestamp": time.time(),
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# Add performance statistics to result
|
|
93
|
+
if result.success and result.fixes_applied:
|
|
94
|
+
stats_summary = self._generate_optimization_summary()
|
|
95
|
+
result.recommendations = result.recommendations + [stats_summary]
|
|
96
|
+
|
|
97
|
+
return result
|
|
45
98
|
except Exception as e:
|
|
46
99
|
return self._create_performance_error_result(e)
|
|
47
100
|
|
|
48
|
-
|
|
49
|
-
|
|
101
|
+
@staticmethod
|
|
102
|
+
def _validate_performance_issue(issue: Issue) -> FixResult | None:
|
|
50
103
|
if not issue.file_path:
|
|
51
104
|
return FixResult(
|
|
52
105
|
success=False,
|
|
@@ -65,7 +118,6 @@ class PerformanceAgent(SubAgent):
|
|
|
65
118
|
return None
|
|
66
119
|
|
|
67
120
|
async def _process_performance_optimization(self, file_path: Path) -> FixResult:
|
|
68
|
-
"""Process performance issue detection and optimization for a file."""
|
|
69
121
|
content = self.context.get_file_content(file_path)
|
|
70
122
|
if not content:
|
|
71
123
|
return FixResult(
|
|
@@ -95,7 +147,6 @@ class PerformanceAgent(SubAgent):
|
|
|
95
147
|
content: str,
|
|
96
148
|
issues: list[dict[str, t.Any]],
|
|
97
149
|
) -> FixResult:
|
|
98
|
-
"""Apply performance optimizations and save changes."""
|
|
99
150
|
optimized_content = self._apply_performance_optimizations(content, issues)
|
|
100
151
|
|
|
101
152
|
if optimized_content == content:
|
|
@@ -120,8 +171,8 @@ class PerformanceAgent(SubAgent):
|
|
|
120
171
|
recommendations=["Test performance improvements with benchmarks"],
|
|
121
172
|
)
|
|
122
173
|
|
|
123
|
-
|
|
124
|
-
|
|
174
|
+
@staticmethod
|
|
175
|
+
def _create_no_optimization_result() -> FixResult:
|
|
125
176
|
return FixResult(
|
|
126
177
|
success=False,
|
|
127
178
|
confidence=0.6,
|
|
@@ -134,8 +185,8 @@ class PerformanceAgent(SubAgent):
|
|
|
134
185
|
],
|
|
135
186
|
)
|
|
136
187
|
|
|
137
|
-
|
|
138
|
-
|
|
188
|
+
@staticmethod
|
|
189
|
+
def _create_performance_error_result(error: Exception) -> FixResult:
|
|
139
190
|
return FixResult(
|
|
140
191
|
success=False,
|
|
141
192
|
confidence=0.0,
|
|
@@ -147,137 +198,176 @@ class PerformanceAgent(SubAgent):
|
|
|
147
198
|
content: str,
|
|
148
199
|
file_path: Path,
|
|
149
200
|
) -> list[dict[str, t.Any]]:
|
|
150
|
-
"""
|
|
201
|
+
"""Enhanced performance issue detection with comprehensive O(n²) analysis."""
|
|
151
202
|
issues: list[dict[str, t.Any]] = []
|
|
152
203
|
|
|
153
204
|
with suppress(SyntaxError):
|
|
154
205
|
tree = ast.parse(content)
|
|
155
206
|
|
|
156
|
-
#
|
|
157
|
-
|
|
207
|
+
# Enhanced nested loop detection with complexity analysis
|
|
208
|
+
nested_issues = self._detect_nested_loops_enhanced(tree)
|
|
209
|
+
issues.extend(nested_issues)
|
|
210
|
+
|
|
211
|
+
# Improved list operations detection
|
|
212
|
+
list_issues = self._detect_inefficient_list_ops_enhanced(content, tree)
|
|
213
|
+
issues.extend(list_issues)
|
|
214
|
+
|
|
215
|
+
# Enhanced repeated operations detection
|
|
216
|
+
repeated_issues = self._detect_repeated_operations_enhanced(content, tree)
|
|
217
|
+
issues.extend(repeated_issues)
|
|
158
218
|
|
|
159
|
-
#
|
|
160
|
-
|
|
219
|
+
# Comprehensive string inefficiency detection
|
|
220
|
+
string_issues = self._detect_string_inefficiencies_enhanced(content)
|
|
221
|
+
issues.extend(string_issues)
|
|
161
222
|
|
|
162
|
-
# Detect
|
|
163
|
-
|
|
223
|
+
# New: Detect list comprehension opportunities
|
|
224
|
+
comprehension_issues = self._detect_list_comprehension_opportunities(tree)
|
|
225
|
+
issues.extend(comprehension_issues)
|
|
164
226
|
|
|
165
|
-
# Detect inefficient
|
|
166
|
-
|
|
227
|
+
# New: Detect inefficient built-in usage
|
|
228
|
+
builtin_issues = self._detect_inefficient_builtin_usage(tree, content)
|
|
229
|
+
issues.extend(builtin_issues)
|
|
167
230
|
|
|
168
231
|
return issues
|
|
169
232
|
|
|
170
|
-
def
|
|
171
|
-
"""
|
|
172
|
-
|
|
233
|
+
def _detect_nested_loops_enhanced(self, tree: ast.AST) -> list[dict[str, t.Any]]:
|
|
234
|
+
"""Enhanced nested loop detection with complexity analysis and optimization suggestions."""
|
|
235
|
+
analyzer = self._create_nested_loop_analyzer()
|
|
236
|
+
analyzer.visit(tree)
|
|
237
|
+
return self._build_nested_loop_issues(analyzer)
|
|
173
238
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
239
|
+
@staticmethod
|
|
240
|
+
def _create_nested_loop_analyzer() -> (
|
|
241
|
+
performance_helpers.EnhancedNestedLoopAnalyzer
|
|
242
|
+
):
|
|
243
|
+
"""Create and configure the nested loop analyzer."""
|
|
244
|
+
return performance_helpers.EnhancedNestedLoopAnalyzer()
|
|
178
245
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
246
|
+
def _build_nested_loop_issues(
|
|
247
|
+
self, analyzer: performance_helpers.EnhancedNestedLoopAnalyzer
|
|
248
|
+
) -> list[dict[str, t.Any]]:
|
|
249
|
+
"""Build the final nested loop issues from analyzer results."""
|
|
250
|
+
if not analyzer.nested_loops:
|
|
251
|
+
return []
|
|
252
|
+
|
|
253
|
+
return [
|
|
254
|
+
{
|
|
255
|
+
"type": "nested_loops_enhanced",
|
|
256
|
+
"instances": analyzer.nested_loops,
|
|
257
|
+
"hotspots": analyzer.complexity_hotspots,
|
|
258
|
+
"total_count": len(analyzer.nested_loops),
|
|
259
|
+
"high_priority_count": self._count_high_priority_loops(
|
|
260
|
+
analyzer.nested_loops
|
|
261
|
+
),
|
|
262
|
+
"suggestion": self._generate_nested_loop_suggestions(
|
|
263
|
+
analyzer.nested_loops
|
|
264
|
+
),
|
|
265
|
+
}
|
|
266
|
+
]
|
|
192
267
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
{
|
|
198
|
-
"line_number": node.lineno,
|
|
199
|
-
"type": "nested_while_loop",
|
|
200
|
-
"depth": len(self.loop_stack),
|
|
201
|
-
"node": node,
|
|
202
|
-
},
|
|
203
|
-
)
|
|
204
|
-
self.generic_visit(node)
|
|
205
|
-
self.loop_stack.pop()
|
|
268
|
+
@staticmethod
|
|
269
|
+
def _count_high_priority_loops(nested_loops: list[dict[str, t.Any]]) -> int:
|
|
270
|
+
"""Count loops with high or critical priority."""
|
|
271
|
+
return len([n for n in nested_loops if n["priority"] in ("high", "critical")])
|
|
206
272
|
|
|
207
|
-
|
|
208
|
-
|
|
273
|
+
@staticmethod
|
|
274
|
+
def _generate_nested_loop_suggestions(nested_loops: list[dict[str, t.Any]]) -> str:
|
|
275
|
+
"""Generate specific optimization suggestions based on nested loop analysis."""
|
|
276
|
+
suggestions = []
|
|
209
277
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
278
|
+
critical_count = len(
|
|
279
|
+
[n for n in nested_loops if n.get("priority") == "critical"]
|
|
280
|
+
)
|
|
281
|
+
high_count = len([n for n in nested_loops if n.get("priority") == "high"])
|
|
282
|
+
|
|
283
|
+
if critical_count > 0:
|
|
284
|
+
suggestions.append(
|
|
285
|
+
f"CRITICAL: {critical_count} O(n⁴+) loops need immediate algorithmic redesign"
|
|
286
|
+
)
|
|
287
|
+
if high_count > 0:
|
|
288
|
+
suggestions.append(
|
|
289
|
+
f"HIGH: {high_count} O(n³) loops should use memoization/caching"
|
|
217
290
|
)
|
|
218
291
|
|
|
219
|
-
|
|
292
|
+
suggestions.extend(
|
|
293
|
+
[
|
|
294
|
+
"Consider: 1) Hash tables for lookups 2) List comprehensions 3) NumPy for numerical operations",
|
|
295
|
+
"Profile: Use timeit or cProfile to measure actual performance impact",
|
|
296
|
+
]
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
return "; ".join(suggestions)
|
|
220
300
|
|
|
221
|
-
def
|
|
301
|
+
def _detect_inefficient_list_ops_enhanced(
|
|
222
302
|
self,
|
|
223
303
|
content: str,
|
|
224
304
|
tree: ast.AST,
|
|
225
305
|
) -> list[dict[str, t.Any]]:
|
|
226
|
-
"""
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
# Pattern: list += [item] or list = list + [item] in loops
|
|
306
|
+
"""Enhanced list operations detection with performance impact assessment."""
|
|
307
|
+
analyzer = self._create_enhanced_list_op_analyzer()
|
|
308
|
+
analyzer.visit(tree)
|
|
231
309
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
310
|
+
if not analyzer.list_ops:
|
|
311
|
+
return []
|
|
312
|
+
|
|
313
|
+
return self._build_list_ops_issues(analyzer)
|
|
314
|
+
|
|
315
|
+
@staticmethod
|
|
316
|
+
def _create_enhanced_list_op_analyzer() -> t.Any:
|
|
317
|
+
"""Create the enhanced list operations analyzer."""
|
|
318
|
+
return performance_helpers.EnhancedListOpAnalyzer()
|
|
319
|
+
|
|
320
|
+
def _build_list_ops_issues(self, analyzer: t.Any) -> list[dict[str, t.Any]]:
|
|
321
|
+
"""Build the final list operations issues from analyzer results."""
|
|
322
|
+
total_impact = sum(int(op["impact_factor"]) for op in analyzer.list_ops)
|
|
323
|
+
high_impact_ops = [
|
|
324
|
+
op for op in analyzer.list_ops if int(op["impact_factor"]) >= 10
|
|
325
|
+
]
|
|
326
|
+
|
|
327
|
+
return [
|
|
328
|
+
{
|
|
329
|
+
"type": "inefficient_list_operations_enhanced",
|
|
330
|
+
"instances": analyzer.list_ops,
|
|
331
|
+
"total_impact": total_impact,
|
|
332
|
+
"high_impact_count": len(high_impact_ops),
|
|
333
|
+
"suggestion": self._generate_list_op_suggestions(analyzer.list_ops),
|
|
334
|
+
}
|
|
335
|
+
]
|
|
236
336
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
self.in_loop = old_in_loop
|
|
337
|
+
@staticmethod
|
|
338
|
+
def _generate_list_op_suggestions(list_ops: list[dict[str, t.Any]]) -> str:
|
|
339
|
+
"""Generate specific optimization suggestions for list operations."""
|
|
340
|
+
suggestions = []
|
|
242
341
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
342
|
+
high_impact_count = len(
|
|
343
|
+
[op for op in list_ops if int(op["impact_factor"]) >= 10]
|
|
344
|
+
)
|
|
345
|
+
if high_impact_count > 0:
|
|
346
|
+
suggestions.append(
|
|
347
|
+
f"HIGH IMPACT: {high_impact_count} list operations in hot loops"
|
|
348
|
+
)
|
|
248
349
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
if isinstance(node.value, ast.List):
|
|
252
|
-
self.list_ops.append(
|
|
253
|
-
{
|
|
254
|
-
"line_number": node.lineno,
|
|
255
|
-
"type": "list_concat_in_loop",
|
|
256
|
-
"pattern": "list += [item]",
|
|
257
|
-
},
|
|
258
|
-
)
|
|
259
|
-
self.generic_visit(node)
|
|
350
|
+
append_count = len([op for op in list_ops if op["optimization"] == "append"])
|
|
351
|
+
extend_count = len([op for op in list_ops if op["optimization"] == "extend"])
|
|
260
352
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
{
|
|
267
|
-
"type": "inefficient_list_operations",
|
|
268
|
-
"instances": analyzer.list_ops,
|
|
269
|
-
"suggestion": "Use list.append() or collect items first, then extend",
|
|
270
|
-
},
|
|
353
|
+
if append_count > 0:
|
|
354
|
+
suggestions.append(f"Replace {append_count} += [item] with .append(item)")
|
|
355
|
+
if extend_count > 0:
|
|
356
|
+
suggestions.append(
|
|
357
|
+
f"Replace {extend_count} += multiple_items with .extend()"
|
|
271
358
|
)
|
|
272
359
|
|
|
273
|
-
|
|
360
|
+
suggestions.append(
|
|
361
|
+
"Expected performance gains: 2-50x depending on loop context"
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
return "; ".join(suggestions)
|
|
274
365
|
|
|
275
|
-
def
|
|
366
|
+
def _detect_repeated_operations_enhanced(
|
|
276
367
|
self,
|
|
277
368
|
content: str,
|
|
278
369
|
tree: ast.AST,
|
|
279
370
|
) -> list[dict[str, t.Any]]:
|
|
280
|
-
"""Detect repeated expensive operations that could be cached."""
|
|
281
371
|
lines = content.split("\n")
|
|
282
372
|
repeated_calls = self._find_expensive_operations_in_loops(lines)
|
|
283
373
|
|
|
@@ -287,7 +377,6 @@ class PerformanceAgent(SubAgent):
|
|
|
287
377
|
self,
|
|
288
378
|
lines: list[str],
|
|
289
379
|
) -> list[dict[str, t.Any]]:
|
|
290
|
-
"""Find expensive operations that occur within loop contexts."""
|
|
291
380
|
repeated_calls: list[dict[str, t.Any]] = []
|
|
292
381
|
expensive_patterns = self._get_expensive_operation_patterns()
|
|
293
382
|
|
|
@@ -299,8 +388,8 @@ class PerformanceAgent(SubAgent):
|
|
|
299
388
|
|
|
300
389
|
return repeated_calls
|
|
301
390
|
|
|
302
|
-
|
|
303
|
-
|
|
391
|
+
@staticmethod
|
|
392
|
+
def _get_expensive_operation_patterns() -> tuple[str, ...]:
|
|
304
393
|
return (
|
|
305
394
|
".exists()",
|
|
306
395
|
".read_text()",
|
|
@@ -311,42 +400,39 @@ class PerformanceAgent(SubAgent):
|
|
|
311
400
|
".get(",
|
|
312
401
|
)
|
|
313
402
|
|
|
403
|
+
@staticmethod
|
|
314
404
|
def _contains_expensive_operation(
|
|
315
|
-
self,
|
|
316
405
|
line: str,
|
|
317
406
|
patterns: tuple[str, ...],
|
|
318
407
|
) -> bool:
|
|
319
|
-
"""Check if line contains any expensive operation patterns."""
|
|
320
408
|
return any(pattern in line for pattern in patterns)
|
|
321
409
|
|
|
322
|
-
|
|
323
|
-
|
|
410
|
+
@staticmethod
|
|
411
|
+
def _is_in_loop_context(lines: list[str], line_index: int) -> bool:
|
|
324
412
|
context_start = max(0, line_index - 5)
|
|
325
413
|
context_lines = lines[context_start : line_index + 1]
|
|
326
|
-
|
|
414
|
+
|
|
327
415
|
loop_keywords = ("for ", "while ")
|
|
328
416
|
return any(
|
|
329
417
|
any(keyword in ctx_line for keyword in loop_keywords)
|
|
330
418
|
for ctx_line in context_lines
|
|
331
419
|
)
|
|
332
420
|
|
|
421
|
+
@staticmethod
|
|
333
422
|
def _create_operation_record(
|
|
334
|
-
self,
|
|
335
423
|
line_index: int,
|
|
336
424
|
content: str,
|
|
337
425
|
) -> dict[str, t.Any]:
|
|
338
|
-
"""Create a record for an expensive operation found in a loop."""
|
|
339
426
|
return {
|
|
340
427
|
"line_number": line_index + 1,
|
|
341
428
|
"content": content,
|
|
342
429
|
"type": "expensive_operation_in_loop",
|
|
343
430
|
}
|
|
344
431
|
|
|
432
|
+
@staticmethod
|
|
345
433
|
def _create_repeated_operations_issues(
|
|
346
|
-
self,
|
|
347
434
|
repeated_calls: list[dict[str, t.Any]],
|
|
348
435
|
) -> list[dict[str, t.Any]]:
|
|
349
|
-
"""Create issues list for repeated operations if threshold is met."""
|
|
350
436
|
if len(repeated_calls) >= 2:
|
|
351
437
|
return [
|
|
352
438
|
{
|
|
@@ -357,21 +443,19 @@ class PerformanceAgent(SubAgent):
|
|
|
357
443
|
]
|
|
358
444
|
return []
|
|
359
445
|
|
|
360
|
-
|
|
361
|
-
|
|
446
|
+
@staticmethod
|
|
447
|
+
def _detect_string_inefficiencies(content: str) -> list[dict[str, t.Any]]:
|
|
362
448
|
issues: list[dict[str, t.Any]] = []
|
|
363
449
|
lines = content.split("\n")
|
|
364
450
|
|
|
365
|
-
# Pattern: String concatenation in loops
|
|
366
451
|
string_concat_in_loop: list[dict[str, t.Any]] = []
|
|
367
452
|
|
|
368
453
|
for i, line in enumerate(lines):
|
|
369
454
|
stripped = line.strip()
|
|
370
455
|
if "+=" in stripped and any(quote in stripped for quote in ('"', "'")):
|
|
371
|
-
# Check if in loop context
|
|
372
456
|
context_start = max(0, i - 5)
|
|
373
457
|
context_lines = lines[context_start : i + 1]
|
|
374
|
-
|
|
458
|
+
|
|
375
459
|
loop_keywords = ("for ", "while ")
|
|
376
460
|
if any(
|
|
377
461
|
any(keyword in ctx_line for keyword in loop_keywords)
|
|
@@ -395,37 +479,505 @@ class PerformanceAgent(SubAgent):
|
|
|
395
479
|
|
|
396
480
|
return issues
|
|
397
481
|
|
|
482
|
+
def _detect_string_inefficiencies_enhanced(
|
|
483
|
+
self, content: str
|
|
484
|
+
) -> list[dict[str, t.Any]]:
|
|
485
|
+
"""Enhanced string inefficiency detection with comprehensive analysis."""
|
|
486
|
+
issues: list[dict[str, t.Any]] = []
|
|
487
|
+
lines = content.split("\n")
|
|
488
|
+
|
|
489
|
+
string_concat_patterns = []
|
|
490
|
+
inefficient_joins = []
|
|
491
|
+
repeated_format_calls = []
|
|
492
|
+
|
|
493
|
+
for i, line in enumerate(lines):
|
|
494
|
+
stripped = line.strip()
|
|
495
|
+
|
|
496
|
+
# Detect string concatenation in loops (enhanced)
|
|
497
|
+
if "+=" in stripped and any(quote in stripped for quote in ('"', "'")):
|
|
498
|
+
if self._is_in_loop_context_enhanced(lines, i):
|
|
499
|
+
context_info = self._analyze_string_context(lines, i)
|
|
500
|
+
string_concat_patterns.append(
|
|
501
|
+
{
|
|
502
|
+
"line_number": i + 1,
|
|
503
|
+
"content": stripped,
|
|
504
|
+
"context": context_info,
|
|
505
|
+
"estimated_impact": int(
|
|
506
|
+
context_info.get("impact_factor", "1")
|
|
507
|
+
),
|
|
508
|
+
}
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
# Detect inefficient string joins
|
|
512
|
+
if ".join([])" in stripped:
|
|
513
|
+
inefficient_joins.append(
|
|
514
|
+
{
|
|
515
|
+
"line_number": i + 1,
|
|
516
|
+
"content": stripped,
|
|
517
|
+
"optimization": "Use empty string literal instead",
|
|
518
|
+
"performance_gain": "2x",
|
|
519
|
+
}
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
# Detect repeated string formatting in loops
|
|
523
|
+
if any(pattern in stripped for pattern in ('f"', ".format(", "% ")):
|
|
524
|
+
if self._is_in_loop_context_enhanced(lines, i):
|
|
525
|
+
repeated_format_calls.append(
|
|
526
|
+
{
|
|
527
|
+
"line_number": i + 1,
|
|
528
|
+
"content": stripped,
|
|
529
|
+
"optimization": "Move formatting outside loop if static",
|
|
530
|
+
}
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
total_issues = (
|
|
534
|
+
len(string_concat_patterns)
|
|
535
|
+
+ len(inefficient_joins)
|
|
536
|
+
+ len(repeated_format_calls)
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
if total_issues > 0:
|
|
540
|
+
issues.append(
|
|
541
|
+
{
|
|
542
|
+
"type": "string_inefficiencies_enhanced",
|
|
543
|
+
"string_concat_patterns": string_concat_patterns,
|
|
544
|
+
"inefficient_joins": inefficient_joins,
|
|
545
|
+
"repeated_formatting": repeated_format_calls,
|
|
546
|
+
"total_count": total_issues,
|
|
547
|
+
"suggestion": self._generate_string_suggestions(
|
|
548
|
+
string_concat_patterns, inefficient_joins, repeated_format_calls
|
|
549
|
+
),
|
|
550
|
+
}
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
return issues
|
|
554
|
+
|
|
555
|
+
def _analyze_string_context(
|
|
556
|
+
self, lines: list[str], line_idx: int
|
|
557
|
+
) -> dict[str, t.Any]:
|
|
558
|
+
"""Analyze the context around a string operation for impact assessment."""
|
|
559
|
+
context = self._create_default_string_context()
|
|
560
|
+
loop_context = self._find_loop_context_in_lines(lines, line_idx)
|
|
561
|
+
|
|
562
|
+
if loop_context:
|
|
563
|
+
context.update(loop_context)
|
|
564
|
+
|
|
565
|
+
return context
|
|
566
|
+
|
|
567
|
+
@staticmethod
|
|
568
|
+
def _create_default_string_context() -> dict[str, t.Any]:
|
|
569
|
+
"""Create default context for string operations."""
|
|
570
|
+
return {
|
|
571
|
+
"loop_type": "unknown",
|
|
572
|
+
"loop_depth": 1,
|
|
573
|
+
"impact_factor": "1",
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
def _find_loop_context_in_lines(
|
|
577
|
+
self, lines: list[str], line_idx: int
|
|
578
|
+
) -> dict[str, t.Any] | None:
|
|
579
|
+
"""Find loop context by looking back through lines."""
|
|
580
|
+
for i in range(max(0, line_idx - 10), line_idx):
|
|
581
|
+
line = lines[i].strip()
|
|
582
|
+
loop_context = self._analyze_single_line_for_loop_context(line)
|
|
583
|
+
if loop_context:
|
|
584
|
+
return loop_context
|
|
585
|
+
return None
|
|
586
|
+
|
|
587
|
+
def _analyze_single_line_for_loop_context(
|
|
588
|
+
self, line: str
|
|
589
|
+
) -> dict[str, t.Any] | None:
|
|
590
|
+
"""Analyze a single line for loop context information."""
|
|
591
|
+
if "for " in line and " in " in line:
|
|
592
|
+
return self._analyze_for_loop_context(line)
|
|
593
|
+
elif "while " in line:
|
|
594
|
+
return self._analyze_while_loop_context()
|
|
595
|
+
return None
|
|
596
|
+
|
|
597
|
+
def _analyze_for_loop_context(self, line: str) -> dict[str, t.Any]:
|
|
598
|
+
"""Analyze for loop context and estimate impact."""
|
|
599
|
+
context = {"loop_type": "for"}
|
|
600
|
+
|
|
601
|
+
if "range(" in line:
|
|
602
|
+
impact_factor = self._estimate_range_impact_factor(line)
|
|
603
|
+
context["impact_factor"] = str(impact_factor)
|
|
604
|
+
else:
|
|
605
|
+
context["impact_factor"] = "2" # Default for for loops
|
|
606
|
+
|
|
607
|
+
return context
|
|
608
|
+
|
|
609
|
+
@staticmethod
|
|
610
|
+
def _analyze_while_loop_context() -> dict[str, t.Any]:
|
|
611
|
+
"""Analyze while loop context."""
|
|
612
|
+
return {
|
|
613
|
+
"loop_type": "while",
|
|
614
|
+
"impact_factor": "3", # Generally higher impact for while loops
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
def _estimate_range_impact_factor(self, line: str) -> int:
|
|
618
|
+
"""Estimate impact factor based on range size."""
|
|
619
|
+
try:
|
|
620
|
+
pattern_obj = SAFE_PATTERNS["extract_range_size"]
|
|
621
|
+
if not pattern_obj.test(line):
|
|
622
|
+
return 2
|
|
623
|
+
|
|
624
|
+
range_str = pattern_obj.apply(line)
|
|
625
|
+
range_size = self._extract_range_size_from_string(range_str)
|
|
626
|
+
|
|
627
|
+
return self._calculate_impact_from_range_size(range_size)
|
|
628
|
+
except (ValueError, AttributeError):
|
|
629
|
+
return 2
|
|
630
|
+
|
|
631
|
+
@staticmethod
|
|
632
|
+
def _extract_range_size_from_string(range_str: str) -> int:
|
|
633
|
+
"""Extract numeric range size from string using safe regex."""
|
|
634
|
+
import re # REGEX OK: temporary for extracting number from safe pattern
|
|
635
|
+
|
|
636
|
+
number_match = re.search(
|
|
637
|
+
r"\d+", range_str
|
|
638
|
+
) # REGEX OK: extracting digits from validated pattern
|
|
639
|
+
if number_match:
|
|
640
|
+
return int(number_match.group())
|
|
641
|
+
return 0
|
|
642
|
+
|
|
643
|
+
@staticmethod
|
|
644
|
+
def _calculate_impact_from_range_size(range_size: int) -> int:
|
|
645
|
+
"""Calculate impact factor based on range size."""
|
|
646
|
+
if range_size > 1000:
|
|
647
|
+
return 10
|
|
648
|
+
elif range_size > 100:
|
|
649
|
+
return 5
|
|
650
|
+
return 2
|
|
651
|
+
|
|
652
|
+
@staticmethod
|
|
653
|
+
def _is_in_loop_context_enhanced(lines: list[str], line_index: int) -> bool:
|
|
654
|
+
"""Enhanced loop context detection with better accuracy."""
|
|
655
|
+
context_start = max(0, line_index - 8)
|
|
656
|
+
context_lines = lines[context_start : line_index + 1]
|
|
657
|
+
|
|
658
|
+
# Check for various loop patterns
|
|
659
|
+
|
|
660
|
+
for ctx_line in context_lines:
|
|
661
|
+
# Use safe pattern matching for loop detection
|
|
662
|
+
pattern_obj = SAFE_PATTERNS["match_loop_patterns"]
|
|
663
|
+
if pattern_obj.test(ctx_line):
|
|
664
|
+
return True
|
|
665
|
+
|
|
666
|
+
return False
|
|
667
|
+
|
|
668
|
+
@staticmethod
|
|
669
|
+
def _generate_string_suggestions(
|
|
670
|
+
concat_patterns: list[dict[str, t.Any]],
|
|
671
|
+
inefficient_joins: list[dict[str, t.Any]],
|
|
672
|
+
repeated_formatting: list[dict[str, t.Any]],
|
|
673
|
+
) -> str:
|
|
674
|
+
"""Generate comprehensive string optimization suggestions."""
|
|
675
|
+
suggestions = []
|
|
676
|
+
|
|
677
|
+
if concat_patterns:
|
|
678
|
+
high_impact = len(
|
|
679
|
+
[p for p in concat_patterns if p.get("estimated_impact", 1) >= 5]
|
|
680
|
+
)
|
|
681
|
+
suggestions.append(
|
|
682
|
+
f"String concatenation: {len(concat_patterns)} instances "
|
|
683
|
+
f"({high_impact} high-impact) - use list.append + join"
|
|
684
|
+
)
|
|
685
|
+
|
|
686
|
+
if inefficient_joins:
|
|
687
|
+
suggestions.append(
|
|
688
|
+
f"Empty joins: {len(inefficient_joins)} - use empty string literal"
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
if repeated_formatting:
|
|
692
|
+
suggestions.append(
|
|
693
|
+
f"Repeated formatting: {len(repeated_formatting)} - cache format strings"
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
suggestions.append("Expected gains: 3-50x for string building in loops")
|
|
697
|
+
return "; ".join(suggestions)
|
|
698
|
+
|
|
699
|
+
def _detect_list_comprehension_opportunities(
|
|
700
|
+
self, tree: ast.AST
|
|
701
|
+
) -> list[dict[str, t.Any]]:
|
|
702
|
+
"""Detect opportunities to replace append loops with list comprehensions."""
|
|
703
|
+
issues: list[dict[str, t.Any]] = []
|
|
704
|
+
|
|
705
|
+
class ComprehensionAnalyzer(ast.NodeVisitor):
|
|
706
|
+
def __init__(self) -> None:
|
|
707
|
+
self.opportunities: list[dict[str, t.Any]] = []
|
|
708
|
+
|
|
709
|
+
def visit_For(self, node: ast.For) -> None:
|
|
710
|
+
# Look for simple append patterns that can be comprehensions
|
|
711
|
+
if (
|
|
712
|
+
len(node.body) == 1
|
|
713
|
+
and isinstance(node.body[0], ast.Expr)
|
|
714
|
+
and isinstance(
|
|
715
|
+
node.body[0].value,
|
|
716
|
+
ast.Call,
|
|
717
|
+
)
|
|
718
|
+
and isinstance(
|
|
719
|
+
node.body[0].value.func,
|
|
720
|
+
ast.Attribute,
|
|
721
|
+
)
|
|
722
|
+
and node.body[0].value.func.attr == "append"
|
|
723
|
+
):
|
|
724
|
+
self.opportunities.append(
|
|
725
|
+
{
|
|
726
|
+
"line_number": node.lineno,
|
|
727
|
+
"type": "append_loop_to_comprehension",
|
|
728
|
+
"optimization": "list_comprehension",
|
|
729
|
+
"performance_gain": "20-30% faster",
|
|
730
|
+
"readability": "improved",
|
|
731
|
+
}
|
|
732
|
+
)
|
|
733
|
+
|
|
734
|
+
self.generic_visit(node)
|
|
735
|
+
|
|
736
|
+
analyzer = ComprehensionAnalyzer()
|
|
737
|
+
analyzer.visit(tree)
|
|
738
|
+
|
|
739
|
+
if analyzer.opportunities:
|
|
740
|
+
issues.append(
|
|
741
|
+
{
|
|
742
|
+
"type": "list_comprehension_opportunities",
|
|
743
|
+
"instances": analyzer.opportunities,
|
|
744
|
+
"total_count": len(analyzer.opportunities),
|
|
745
|
+
"suggestion": f"Convert {len(analyzer.opportunities)} append loops"
|
|
746
|
+
f" to list comprehensions for better performance "
|
|
747
|
+
f"and readability",
|
|
748
|
+
}
|
|
749
|
+
)
|
|
750
|
+
|
|
751
|
+
return issues
|
|
752
|
+
|
|
753
|
+
def _detect_inefficient_builtin_usage(
|
|
754
|
+
self, tree: ast.AST, content: str
|
|
755
|
+
) -> list[dict[str, t.Any]]:
|
|
756
|
+
"""Detect inefficient usage of built-in functions."""
|
|
757
|
+
issues: list[dict[str, t.Any]] = []
|
|
758
|
+
|
|
759
|
+
class BuiltinAnalyzer(ast.NodeVisitor):
|
|
760
|
+
def __init__(self) -> None:
|
|
761
|
+
self.inefficient_calls: list[dict[str, t.Any]] = []
|
|
762
|
+
self.in_loop = False
|
|
763
|
+
|
|
764
|
+
def visit_For(self, node: ast.For) -> None:
|
|
765
|
+
old_in_loop = self.in_loop
|
|
766
|
+
self.in_loop = True
|
|
767
|
+
self.generic_visit(node)
|
|
768
|
+
self.in_loop = old_in_loop
|
|
769
|
+
|
|
770
|
+
def visit_While(self, node: ast.While) -> None:
|
|
771
|
+
old_in_loop = self.in_loop
|
|
772
|
+
self.in_loop = True
|
|
773
|
+
self.generic_visit(node)
|
|
774
|
+
self.in_loop = old_in_loop
|
|
775
|
+
|
|
776
|
+
def visit_Call(self, node: ast.Call) -> None:
|
|
777
|
+
if self.in_loop and isinstance(node.func, ast.Name):
|
|
778
|
+
func_name = node.func.id
|
|
779
|
+
|
|
780
|
+
# Detect expensive operations in loops
|
|
781
|
+
if func_name in ("len", "sum", "max", "min", "sorted"):
|
|
782
|
+
# Check if called on the same variable repeatedly
|
|
783
|
+
if node.args and isinstance(node.args[0], ast.Name):
|
|
784
|
+
self.inefficient_calls.append(
|
|
785
|
+
{
|
|
786
|
+
"line_number": node.lineno,
|
|
787
|
+
"function": func_name,
|
|
788
|
+
"type": "repeated_builtin_in_loop",
|
|
789
|
+
"optimization": f"Cache {func_name}() "
|
|
790
|
+
f"result outside loop",
|
|
791
|
+
"performance_gain": "2-10x depending on data size",
|
|
792
|
+
}
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
self.generic_visit(node)
|
|
796
|
+
|
|
797
|
+
analyzer = BuiltinAnalyzer()
|
|
798
|
+
analyzer.visit(tree)
|
|
799
|
+
|
|
800
|
+
if analyzer.inefficient_calls:
|
|
801
|
+
issues.append(
|
|
802
|
+
{
|
|
803
|
+
"type": "inefficient_builtin_usage",
|
|
804
|
+
"instances": analyzer.inefficient_calls,
|
|
805
|
+
"total_count": len(analyzer.inefficient_calls),
|
|
806
|
+
"suggestion": f"Cache {len(analyzer.inefficient_calls)} "
|
|
807
|
+
f"repeated builtin calls outside loops",
|
|
808
|
+
}
|
|
809
|
+
)
|
|
810
|
+
|
|
811
|
+
return issues
|
|
812
|
+
|
|
813
|
+
def _generate_optimization_summary(self) -> str:
|
|
814
|
+
"""Generate a summary of all optimizations applied."""
|
|
815
|
+
total_optimizations = sum(self.optimization_stats.values())
|
|
816
|
+
if total_optimizations == 0:
|
|
817
|
+
return "No optimizations applied in this session"
|
|
818
|
+
|
|
819
|
+
summary_parts = [
|
|
820
|
+
f"{opt_type}: {count}"
|
|
821
|
+
for opt_type, count in self.optimization_stats.items()
|
|
822
|
+
if count > 0
|
|
823
|
+
]
|
|
824
|
+
|
|
825
|
+
return (
|
|
826
|
+
f"Optimization Summary - {', '.join(summary_parts)} "
|
|
827
|
+
f"(Total: {total_optimizations})"
|
|
828
|
+
)
|
|
829
|
+
|
|
398
830
|
def _apply_performance_optimizations(
|
|
399
831
|
self,
|
|
400
832
|
content: str,
|
|
401
833
|
issues: list[dict[str, t.Any]],
|
|
402
834
|
) -> str:
|
|
403
|
-
"""
|
|
835
|
+
"""Enhanced optimization application with support for new issue types."""
|
|
404
836
|
lines = content.split("\n")
|
|
405
837
|
modified = False
|
|
838
|
+
optimizations_applied = []
|
|
406
839
|
|
|
407
840
|
for issue in issues:
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
841
|
+
result = self._process_single_issue(lines, issue)
|
|
842
|
+
if result.modified:
|
|
843
|
+
lines = result.lines
|
|
844
|
+
modified = True
|
|
845
|
+
if result.optimization_description:
|
|
846
|
+
optimizations_applied.append(result.optimization_description)
|
|
847
|
+
|
|
848
|
+
if optimizations_applied:
|
|
849
|
+
self.log(f"Applied optimizations: {', '.join(optimizations_applied)}")
|
|
417
850
|
|
|
418
851
|
return "\n".join(lines) if modified else content
|
|
419
852
|
|
|
420
|
-
def
|
|
421
|
-
self,
|
|
853
|
+
def _process_single_issue(
|
|
854
|
+
self, lines: list[str], issue: dict[str, t.Any]
|
|
855
|
+
) -> OptimizationResult:
|
|
856
|
+
"""Process a single optimization issue and return the result."""
|
|
857
|
+
issue_type = issue["type"]
|
|
858
|
+
|
|
859
|
+
if issue_type in (
|
|
860
|
+
"inefficient_list_operations",
|
|
861
|
+
"inefficient_list_operations_enhanced",
|
|
862
|
+
):
|
|
863
|
+
return self._handle_list_operations_issue(lines, issue)
|
|
864
|
+
elif issue_type in (
|
|
865
|
+
"string_concatenation_in_loop",
|
|
866
|
+
"string_inefficiencies_enhanced",
|
|
867
|
+
):
|
|
868
|
+
return self._handle_string_operations_issue(lines, issue)
|
|
869
|
+
elif issue_type == "repeated_expensive_operations":
|
|
870
|
+
return self._handle_repeated_operations_issue(lines, issue)
|
|
871
|
+
elif issue_type in ("nested_loops", "nested_loops_enhanced"):
|
|
872
|
+
return self._handle_nested_loops_issue(lines, issue)
|
|
873
|
+
elif issue_type == "list_comprehension_opportunities":
|
|
874
|
+
return self._handle_comprehension_opportunities_issue(lines, issue)
|
|
875
|
+
elif issue_type == "inefficient_builtin_usage":
|
|
876
|
+
return self._handle_builtin_usage_issue(lines, issue)
|
|
877
|
+
return self._create_no_change_result(lines)
|
|
878
|
+
|
|
879
|
+
def _handle_list_operations_issue(
|
|
880
|
+
self, lines: list[str], issue: dict[str, t.Any]
|
|
881
|
+
) -> OptimizationResult:
|
|
882
|
+
"""Handle list operations optimization issue."""
|
|
883
|
+
new_lines, changed = self._fix_list_operations_enhanced(lines, issue)
|
|
884
|
+
description = None
|
|
885
|
+
|
|
886
|
+
if changed:
|
|
887
|
+
instance_count = len(issue.get("instances", []))
|
|
888
|
+
self.optimization_stats["list_ops_optimized"] += instance_count
|
|
889
|
+
description = f"List operations: {instance_count}"
|
|
890
|
+
|
|
891
|
+
return self._create_optimization_result(new_lines, changed, description)
|
|
892
|
+
|
|
893
|
+
def _handle_string_operations_issue(
|
|
894
|
+
self, lines: list[str], issue: dict[str, t.Any]
|
|
895
|
+
) -> OptimizationResult:
|
|
896
|
+
"""Handle string operations optimization issue."""
|
|
897
|
+
new_lines, changed = self._fix_string_operations_enhanced(lines, issue)
|
|
898
|
+
description = None
|
|
899
|
+
|
|
900
|
+
if changed:
|
|
901
|
+
total_string_fixes = (
|
|
902
|
+
len(issue.get("string_concat_patterns", []))
|
|
903
|
+
+ len(issue.get("inefficient_joins", []))
|
|
904
|
+
+ len(issue.get("repeated_formatting", []))
|
|
905
|
+
)
|
|
906
|
+
self.optimization_stats["string_concat_optimized"] += total_string_fixes
|
|
907
|
+
description = f"String operations: {total_string_fixes}"
|
|
908
|
+
|
|
909
|
+
return self._create_optimization_result(new_lines, changed, description)
|
|
910
|
+
|
|
911
|
+
def _handle_repeated_operations_issue(
|
|
912
|
+
self, lines: list[str], issue: dict[str, t.Any]
|
|
913
|
+
) -> OptimizationResult:
|
|
914
|
+
"""Handle repeated operations optimization issue."""
|
|
915
|
+
new_lines, changed = self._fix_repeated_operations(lines, issue)
|
|
916
|
+
|
|
917
|
+
if changed:
|
|
918
|
+
self.optimization_stats["repeated_ops_cached"] += len(
|
|
919
|
+
issue.get("instances", [])
|
|
920
|
+
)
|
|
921
|
+
|
|
922
|
+
return self._create_optimization_result(new_lines, changed)
|
|
923
|
+
|
|
924
|
+
def _handle_nested_loops_issue(
|
|
925
|
+
self, lines: list[str], issue: dict[str, t.Any]
|
|
926
|
+
) -> OptimizationResult:
|
|
927
|
+
"""Handle nested loops optimization issue."""
|
|
928
|
+
new_lines, changed = self._add_nested_loop_comments(lines, issue)
|
|
929
|
+
|
|
930
|
+
if changed:
|
|
931
|
+
self.optimization_stats["nested_loops_optimized"] += len(
|
|
932
|
+
issue.get("instances", [])
|
|
933
|
+
)
|
|
934
|
+
|
|
935
|
+
return self._create_optimization_result(new_lines, changed)
|
|
936
|
+
|
|
937
|
+
def _handle_comprehension_opportunities_issue(
|
|
938
|
+
self, lines: list[str], issue: dict[str, t.Any]
|
|
939
|
+
) -> OptimizationResult:
|
|
940
|
+
"""Handle list comprehension opportunities issue."""
|
|
941
|
+
new_lines, changed = self._apply_list_comprehension_optimizations(lines, issue)
|
|
942
|
+
|
|
943
|
+
if changed:
|
|
944
|
+
self.optimization_stats["comprehensions_applied"] += len(
|
|
945
|
+
issue.get("instances", [])
|
|
946
|
+
)
|
|
947
|
+
|
|
948
|
+
return self._create_optimization_result(new_lines, changed)
|
|
949
|
+
|
|
950
|
+
def _handle_builtin_usage_issue(
|
|
951
|
+
self, lines: list[str], issue: dict[str, t.Any]
|
|
952
|
+
) -> OptimizationResult:
|
|
953
|
+
"""Handle inefficient builtin usage issue."""
|
|
954
|
+
new_lines, changed = self._add_builtin_caching_comments(lines, issue)
|
|
955
|
+
return self._create_optimization_result(new_lines, changed)
|
|
956
|
+
|
|
957
|
+
@staticmethod
|
|
958
|
+
def _create_optimization_result(
|
|
959
|
+
lines: list[str], modified: bool, description: str | None = None
|
|
960
|
+
) -> OptimizationResult:
|
|
961
|
+
"""Create an optimization result object."""
|
|
962
|
+
return OptimizationResult(
|
|
963
|
+
lines=lines, modified=modified, optimization_description=description
|
|
964
|
+
)
|
|
965
|
+
|
|
966
|
+
@staticmethod
|
|
967
|
+
def _create_no_change_result(lines: list[str]) -> OptimizationResult:
|
|
968
|
+
"""Create a result indicating no changes were made."""
|
|
969
|
+
return OptimizationResult(
|
|
970
|
+
lines=lines, modified=False, optimization_description=None
|
|
971
|
+
)
|
|
972
|
+
|
|
973
|
+
@staticmethod
|
|
974
|
+
def _fix_list_operations_enhanced(
|
|
422
975
|
lines: list[str],
|
|
423
976
|
issue: dict[str, t.Any],
|
|
424
977
|
) -> tuple[list[str], bool]:
|
|
425
|
-
"""
|
|
978
|
+
"""Enhanced list operations fixing with comprehensive optimization."""
|
|
426
979
|
modified = False
|
|
427
980
|
|
|
428
|
-
# Process instances in reverse order (highest line numbers first) to avoid line number shifts
|
|
429
981
|
instances = sorted(
|
|
430
982
|
issue["instances"],
|
|
431
983
|
key=operator.itemgetter("line_number"),
|
|
@@ -437,20 +989,240 @@ class PerformanceAgent(SubAgent):
|
|
|
437
989
|
if line_idx < len(lines):
|
|
438
990
|
original_line = t.cast(str, lines[line_idx])
|
|
439
991
|
|
|
440
|
-
#
|
|
441
|
-
|
|
442
|
-
|
|
992
|
+
# Apply optimization based on instance type
|
|
993
|
+
optimization_type = instance.get(
|
|
994
|
+
"optimization",
|
|
995
|
+
"append",
|
|
996
|
+
)
|
|
997
|
+
|
|
998
|
+
if optimization_type == "append":
|
|
999
|
+
# Use existing safe pattern for single item append
|
|
1000
|
+
list_pattern = SAFE_PATTERNS["list_append_inefficiency_pattern"]
|
|
1001
|
+
if list_pattern.test(original_line):
|
|
1002
|
+
optimized_line = list_pattern.apply(original_line)
|
|
1003
|
+
lines[line_idx] = optimized_line
|
|
1004
|
+
modified = True
|
|
1005
|
+
|
|
1006
|
+
# Add performance comment
|
|
1007
|
+
indent = original_line[
|
|
1008
|
+
: len(original_line) - len(original_line.lstrip())
|
|
1009
|
+
]
|
|
1010
|
+
performance_gain = instance.get("performance_gain", "2x")
|
|
1011
|
+
comment = (
|
|
1012
|
+
f"{indent}# Performance: {performance_gain}"
|
|
1013
|
+
f" improvement (append vs +=)"
|
|
1014
|
+
)
|
|
1015
|
+
lines.insert(line_idx, comment)
|
|
1016
|
+
|
|
1017
|
+
elif optimization_type == "extend":
|
|
1018
|
+
# Use new extend pattern for multiple items
|
|
1019
|
+
extend_pattern = SAFE_PATTERNS["list_extend_optimization_pattern"]
|
|
1020
|
+
if extend_pattern.test(original_line):
|
|
1021
|
+
optimized_line = extend_pattern.apply(original_line)
|
|
1022
|
+
lines[line_idx] = optimized_line
|
|
1023
|
+
modified = True
|
|
1024
|
+
|
|
1025
|
+
# Add performance comment
|
|
1026
|
+
indent = original_line[
|
|
1027
|
+
: len(original_line) - len(original_line.lstrip())
|
|
1028
|
+
]
|
|
1029
|
+
performance_gain = instance.get("performance_gain", "x")
|
|
1030
|
+
impact_factor = int(instance.get("impact_factor", "1"))
|
|
1031
|
+
comment = (
|
|
1032
|
+
f"{indent}# Performance: {performance_gain} "
|
|
1033
|
+
f"improvement, impact factor: {impact_factor}"
|
|
1034
|
+
)
|
|
1035
|
+
lines.insert(line_idx, comment)
|
|
1036
|
+
|
|
1037
|
+
return lines, modified
|
|
1038
|
+
|
|
1039
|
+
def _fix_string_operations_enhanced(
|
|
1040
|
+
self,
|
|
1041
|
+
lines: list[str],
|
|
1042
|
+
issue: dict[str, t.Any],
|
|
1043
|
+
) -> tuple[list[str], bool]:
|
|
1044
|
+
"""Enhanced string operations fixing with comprehensive patterns."""
|
|
1045
|
+
modified = False
|
|
443
1046
|
|
|
444
|
-
|
|
445
|
-
|
|
1047
|
+
# Handle string concatenation patterns
|
|
1048
|
+
concat_patterns = issue.get("string_concat_patterns", [])
|
|
1049
|
+
if concat_patterns:
|
|
1050
|
+
lines, concat_modified = self._fix_string_concatenation(
|
|
1051
|
+
lines, {"instances": concat_patterns}
|
|
1052
|
+
)
|
|
1053
|
+
modified = modified or concat_modified
|
|
446
1054
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
1055
|
+
# Handle inefficient joins
|
|
1056
|
+
inefficient_joins = issue.get("inefficient_joins", [])
|
|
1057
|
+
for join_issue in inefficient_joins:
|
|
1058
|
+
line_idx = join_issue["line_number"] - 1
|
|
1059
|
+
if line_idx < len(lines):
|
|
1060
|
+
original_line = lines[line_idx]
|
|
1061
|
+
join_pattern = SAFE_PATTERNS["inefficient_string_join_pattern"]
|
|
1062
|
+
if join_pattern.test(original_line):
|
|
1063
|
+
lines[line_idx] = join_pattern.apply(original_line)
|
|
1064
|
+
modified = True
|
|
1065
|
+
|
|
1066
|
+
# Handle repeated formatting - just add comments for now
|
|
1067
|
+
repeated_formatting = issue.get("repeated_formatting", [])
|
|
1068
|
+
for format_issue in repeated_formatting:
|
|
1069
|
+
line_idx = format_issue["line_number"] - 1
|
|
1070
|
+
if line_idx < len(lines):
|
|
1071
|
+
original_line = lines[line_idx]
|
|
1072
|
+
indent = original_line[
|
|
1073
|
+
: len(original_line) - len(original_line.lstrip())
|
|
1074
|
+
]
|
|
1075
|
+
comment = (
|
|
1076
|
+
f"{indent}# Performance: Consider caching format string "
|
|
1077
|
+
f"outside loop"
|
|
1078
|
+
)
|
|
1079
|
+
lines.insert(line_idx, comment)
|
|
1080
|
+
modified = True
|
|
1081
|
+
|
|
1082
|
+
return lines, modified
|
|
1083
|
+
|
|
1084
|
+
@staticmethod
|
|
1085
|
+
def _add_nested_loop_comments(
|
|
1086
|
+
lines: list[str],
|
|
1087
|
+
issue: dict[str, t.Any],
|
|
1088
|
+
) -> tuple[list[str], bool]:
|
|
1089
|
+
"""Add informative comments about nested loop complexity."""
|
|
1090
|
+
modified = False
|
|
1091
|
+
|
|
1092
|
+
instances = issue.get("instances", [])
|
|
1093
|
+
for instance in sorted(
|
|
1094
|
+
instances, key=operator.itemgetter("line_number"), reverse=True
|
|
1095
|
+
):
|
|
1096
|
+
line_idx = instance["line_number"] - 1
|
|
1097
|
+
if line_idx < len(lines):
|
|
1098
|
+
original_line = lines[line_idx]
|
|
1099
|
+
indent = original_line[
|
|
1100
|
+
: len(original_line) - len(original_line.lstrip())
|
|
1101
|
+
]
|
|
1102
|
+
|
|
1103
|
+
complexity = instance.get("complexity", "O(n²)")
|
|
1104
|
+
priority = instance.get("priority", "medium")
|
|
1105
|
+
|
|
1106
|
+
comment_lines = [
|
|
1107
|
+
f"{indent}# Performance: {complexity} nested loop detected -"
|
|
1108
|
+
f" {priority} priority",
|
|
1109
|
+
]
|
|
1110
|
+
|
|
1111
|
+
# Add specific suggestions for high priority loops
|
|
1112
|
+
if priority in ("high", "critical"):
|
|
1113
|
+
if priority == "critical":
|
|
1114
|
+
comment_lines.append(
|
|
1115
|
+
f"{indent}# CRITICAL: Consider algorithmic redesign or"
|
|
1116
|
+
f" data structure changes"
|
|
1117
|
+
)
|
|
1118
|
+
else:
|
|
1119
|
+
comment_lines.append(
|
|
1120
|
+
f"{indent}# Suggestion: Consider memoization, caching,"
|
|
1121
|
+
f" or hash tables"
|
|
1122
|
+
)
|
|
1123
|
+
|
|
1124
|
+
# Insert comments before the loop
|
|
1125
|
+
for i, comment in enumerate(comment_lines):
|
|
1126
|
+
lines.insert(line_idx + i, comment)
|
|
1127
|
+
|
|
1128
|
+
modified = True
|
|
1129
|
+
|
|
1130
|
+
return lines, modified
|
|
1131
|
+
|
|
1132
|
+
@staticmethod
|
|
1133
|
+
def _apply_list_comprehension_optimizations(
|
|
1134
|
+
lines: list[str],
|
|
1135
|
+
issue: dict[str, t.Any],
|
|
1136
|
+
) -> tuple[list[str], bool]:
|
|
1137
|
+
"""Apply list comprehension optimizations where detected."""
|
|
1138
|
+
modified = False
|
|
1139
|
+
|
|
1140
|
+
instances = issue.get("instances", [])
|
|
1141
|
+
for instance in sorted(
|
|
1142
|
+
instances, key=operator.itemgetter("line_number"), reverse=True
|
|
1143
|
+
):
|
|
1144
|
+
line_idx = instance["line_number"] - 1
|
|
1145
|
+
if line_idx < len(lines):
|
|
1146
|
+
original_line = lines[line_idx]
|
|
1147
|
+
indent = original_line[
|
|
1148
|
+
: len(original_line) - len(original_line.lstrip())
|
|
1149
|
+
]
|
|
1150
|
+
|
|
1151
|
+
# Add suggestion comment for now - actual transformation would need more AST analysis
|
|
1152
|
+
comment = (
|
|
1153
|
+
f"{indent}# Performance: Consider list comprehension for "
|
|
1154
|
+
f"20-30% improvement"
|
|
1155
|
+
)
|
|
1156
|
+
lines.insert(line_idx, comment)
|
|
1157
|
+
modified = True
|
|
1158
|
+
|
|
1159
|
+
return lines, modified
|
|
1160
|
+
|
|
1161
|
+
@staticmethod
|
|
1162
|
+
def _add_builtin_caching_comments(
|
|
1163
|
+
lines: list[str],
|
|
1164
|
+
issue: dict[str, t.Any],
|
|
1165
|
+
) -> tuple[list[str], bool]:
|
|
1166
|
+
"""Add comments about caching builtin function calls."""
|
|
1167
|
+
modified = False
|
|
1168
|
+
|
|
1169
|
+
instances = issue.get("instances", [])
|
|
1170
|
+
for instance in sorted(
|
|
1171
|
+
instances, key=operator.itemgetter("line_number"), reverse=True
|
|
1172
|
+
):
|
|
1173
|
+
line_idx = instance["line_number"] - 1
|
|
1174
|
+
if line_idx < len(lines):
|
|
1175
|
+
original_line = lines[line_idx]
|
|
1176
|
+
indent = original_line[
|
|
1177
|
+
: len(original_line) - len(original_line.lstrip())
|
|
1178
|
+
]
|
|
1179
|
+
|
|
1180
|
+
func_name = instance.get("function", "builtin")
|
|
1181
|
+
performance_gain = instance.get(
|
|
1182
|
+
"performance_gain",
|
|
1183
|
+
"2-10x",
|
|
1184
|
+
)
|
|
1185
|
+
|
|
1186
|
+
comment = (
|
|
1187
|
+
f"{indent}# Performance: Cache {func_name}() result outside"
|
|
1188
|
+
f" loop for {performance_gain} improvement"
|
|
1189
|
+
)
|
|
1190
|
+
lines.insert(line_idx, comment)
|
|
1191
|
+
modified = True
|
|
1192
|
+
|
|
1193
|
+
return lines, modified
|
|
1194
|
+
|
|
1195
|
+
@staticmethod
|
|
1196
|
+
def _fix_list_operations(
|
|
1197
|
+
lines: list[str],
|
|
1198
|
+
issue: dict[str, t.Any],
|
|
1199
|
+
) -> tuple[list[str], bool]:
|
|
1200
|
+
modified = False
|
|
1201
|
+
|
|
1202
|
+
instances = sorted(
|
|
1203
|
+
issue["instances"],
|
|
1204
|
+
key=operator.itemgetter("line_number"),
|
|
1205
|
+
reverse=True,
|
|
1206
|
+
)
|
|
1207
|
+
|
|
1208
|
+
list_pattern = SAFE_PATTERNS["list_append_inefficiency_pattern"]
|
|
1209
|
+
|
|
1210
|
+
for instance in instances:
|
|
1211
|
+
line_idx = instance["line_number"] - 1
|
|
1212
|
+
if line_idx < len(lines):
|
|
1213
|
+
original_line = t.cast(str, lines[line_idx])
|
|
1214
|
+
|
|
1215
|
+
# Use safe pattern to test and transform
|
|
1216
|
+
if list_pattern.test(original_line):
|
|
1217
|
+
# Apply the performance optimization using safe pattern
|
|
1218
|
+
optimized_line = list_pattern.apply(original_line)
|
|
450
1219
|
lines[line_idx] = optimized_line
|
|
451
1220
|
modified = True
|
|
452
1221
|
|
|
453
|
-
#
|
|
1222
|
+
# Extract indent from the original line for comment
|
|
1223
|
+
indent = original_line[
|
|
1224
|
+
: len(original_line) - len(original_line.lstrip())
|
|
1225
|
+
]
|
|
454
1226
|
comment = (
|
|
455
1227
|
f"{indent}# Performance: Changed += [item] to .append(item)"
|
|
456
1228
|
)
|
|
@@ -463,7 +1235,6 @@ class PerformanceAgent(SubAgent):
|
|
|
463
1235
|
lines: list[str],
|
|
464
1236
|
issue: dict[str, t.Any],
|
|
465
1237
|
) -> tuple[list[str], bool]:
|
|
466
|
-
"""Fix inefficient string concatenation in loops by transforming to list.append + join pattern."""
|
|
467
1238
|
var_groups = self._group_concatenation_instances(lines, issue["instances"])
|
|
468
1239
|
return self._apply_concatenation_optimizations(lines, var_groups)
|
|
469
1240
|
|
|
@@ -472,7 +1243,6 @@ class PerformanceAgent(SubAgent):
|
|
|
472
1243
|
lines: list[str],
|
|
473
1244
|
instances: list[dict[str, t.Any]],
|
|
474
1245
|
) -> dict[str, list[dict[str, t.Any]]]:
|
|
475
|
-
"""Group string concatenation instances by variable name."""
|
|
476
1246
|
var_groups: dict[str, list[dict[str, t.Any]]] = {}
|
|
477
1247
|
|
|
478
1248
|
for instance in instances:
|
|
@@ -485,32 +1255,32 @@ class PerformanceAgent(SubAgent):
|
|
|
485
1255
|
|
|
486
1256
|
return var_groups
|
|
487
1257
|
|
|
1258
|
+
@staticmethod
|
|
488
1259
|
def _parse_concatenation_line(
|
|
489
|
-
self,
|
|
490
1260
|
lines: list[str],
|
|
491
1261
|
instance: dict[str, t.Any],
|
|
492
1262
|
) -> dict[str, t.Any] | None:
|
|
493
|
-
"""Parse a string concatenation line to extract variable info."""
|
|
494
1263
|
line_idx = instance["line_number"] - 1
|
|
495
1264
|
if line_idx >= len(lines):
|
|
496
1265
|
return None
|
|
497
1266
|
|
|
498
1267
|
original_line = t.cast(str, lines[line_idx])
|
|
499
1268
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
1269
|
+
# Use safe pattern for string concatenation parsing
|
|
1270
|
+
concat_pattern = SAFE_PATTERNS["string_concatenation_pattern"]
|
|
1271
|
+
if concat_pattern.test(original_line):
|
|
1272
|
+
# Extract parts using the safe pattern's compiled pattern
|
|
1273
|
+
compiled = concat_pattern._get_compiled_pattern()
|
|
1274
|
+
match = compiled.match(original_line)
|
|
1275
|
+
if match:
|
|
1276
|
+
indent, var_name, expr = match.groups()
|
|
1277
|
+
return {
|
|
1278
|
+
"line_idx": line_idx,
|
|
1279
|
+
"indent": indent,
|
|
1280
|
+
"var_name": var_name,
|
|
1281
|
+
"expr": expr.strip(),
|
|
1282
|
+
"original_line": original_line,
|
|
1283
|
+
}
|
|
514
1284
|
return None
|
|
515
1285
|
|
|
516
1286
|
def _apply_concatenation_optimizations(
|
|
@@ -518,7 +1288,6 @@ class PerformanceAgent(SubAgent):
|
|
|
518
1288
|
lines: list[str],
|
|
519
1289
|
var_groups: dict[str, list[dict[str, t.Any]]],
|
|
520
1290
|
) -> tuple[list[str], bool]:
|
|
521
|
-
"""Apply string building optimizations for each variable group."""
|
|
522
1291
|
modified = False
|
|
523
1292
|
|
|
524
1293
|
for var_name, instances in var_groups.items():
|
|
@@ -534,13 +1303,13 @@ class PerformanceAgent(SubAgent):
|
|
|
534
1303
|
|
|
535
1304
|
return lines, modified
|
|
536
1305
|
|
|
537
|
-
|
|
538
|
-
|
|
1306
|
+
@staticmethod
|
|
1307
|
+
def _find_loop_start(lines: list[str], start_idx: int) -> int | None:
|
|
539
1308
|
for i in range(start_idx, -1, -1):
|
|
540
1309
|
line = lines[i].strip()
|
|
541
1310
|
if line.startswith(("for ", "while ")):
|
|
542
1311
|
return i
|
|
543
|
-
|
|
1312
|
+
|
|
544
1313
|
if line.startswith(("def ", "class ")):
|
|
545
1314
|
break
|
|
546
1315
|
return None
|
|
@@ -552,7 +1321,6 @@ class PerformanceAgent(SubAgent):
|
|
|
552
1321
|
instances: list[dict[str, t.Any]],
|
|
553
1322
|
loop_start: int,
|
|
554
1323
|
) -> bool:
|
|
555
|
-
"""Apply string building optimization using list.append + join pattern."""
|
|
556
1324
|
if not instances:
|
|
557
1325
|
return False
|
|
558
1326
|
|
|
@@ -572,13 +1340,12 @@ class PerformanceAgent(SubAgent):
|
|
|
572
1340
|
|
|
573
1341
|
return False
|
|
574
1342
|
|
|
1343
|
+
@staticmethod
|
|
575
1344
|
def _find_variable_initialization(
|
|
576
|
-
self,
|
|
577
1345
|
lines: list[str],
|
|
578
1346
|
var_name: str,
|
|
579
1347
|
loop_start: int,
|
|
580
1348
|
) -> int | None:
|
|
581
|
-
"""Find the line where the string variable is initialized."""
|
|
582
1349
|
search_start = max(0, loop_start - 10)
|
|
583
1350
|
|
|
584
1351
|
for i in range(loop_start - 1, search_start - 1, -1):
|
|
@@ -587,26 +1354,24 @@ class PerformanceAgent(SubAgent):
|
|
|
587
1354
|
return i
|
|
588
1355
|
return None
|
|
589
1356
|
|
|
1357
|
+
@staticmethod
|
|
590
1358
|
def _transform_string_initialization(
|
|
591
|
-
self,
|
|
592
1359
|
lines: list[str],
|
|
593
1360
|
init_line_idx: int,
|
|
594
1361
|
var_name: str,
|
|
595
1362
|
indent: str,
|
|
596
1363
|
) -> None:
|
|
597
|
-
"""Transform string initialization to list initialization."""
|
|
598
1364
|
lines[init_line_idx] = (
|
|
599
|
-
f"{indent}{var_name}_parts = []
|
|
1365
|
+
f"{indent}{var_name}_parts = [] # Performance: Use list for string building"
|
|
600
1366
|
)
|
|
601
1367
|
|
|
1368
|
+
@staticmethod
|
|
602
1369
|
def _replace_concatenations_with_appends(
|
|
603
|
-
self,
|
|
604
1370
|
lines: list[str],
|
|
605
1371
|
instances: list[dict[str, t.Any]],
|
|
606
1372
|
var_name: str,
|
|
607
1373
|
indent: str,
|
|
608
1374
|
) -> None:
|
|
609
|
-
"""Replace string concatenations with list appends."""
|
|
610
1375
|
for instance in instances:
|
|
611
1376
|
line_idx = instance["line_idx"]
|
|
612
1377
|
expr = instance["expr"]
|
|
@@ -619,14 +1384,16 @@ class PerformanceAgent(SubAgent):
|
|
|
619
1384
|
indent: str,
|
|
620
1385
|
loop_start: int,
|
|
621
1386
|
) -> None:
|
|
622
|
-
"""Add join operation after the loop ends."""
|
|
623
1387
|
loop_end = self._find_loop_end(lines, loop_start)
|
|
624
1388
|
if loop_end is not None:
|
|
625
|
-
join_line =
|
|
1389
|
+
join_line = (
|
|
1390
|
+
f"{indent}{var_name} = ''.join({var_name}_parts) # Performance:"
|
|
1391
|
+
f" Join string parts"
|
|
1392
|
+
)
|
|
626
1393
|
lines.insert(loop_end + 1, join_line)
|
|
627
1394
|
|
|
628
|
-
|
|
629
|
-
|
|
1395
|
+
@staticmethod
|
|
1396
|
+
def _find_loop_end(lines: list[str], loop_start: int) -> int | None:
|
|
630
1397
|
if loop_start >= len(lines):
|
|
631
1398
|
return None
|
|
632
1399
|
|
|
@@ -634,7 +1401,7 @@ class PerformanceAgent(SubAgent):
|
|
|
634
1401
|
|
|
635
1402
|
for i in range(loop_start + 1, len(lines)):
|
|
636
1403
|
line = lines[i]
|
|
637
|
-
if line.strip() == "":
|
|
1404
|
+
if line.strip() == "":
|
|
638
1405
|
continue
|
|
639
1406
|
|
|
640
1407
|
current_indent = len(line) - len(line.lstrip())
|
|
@@ -643,12 +1410,11 @@ class PerformanceAgent(SubAgent):
|
|
|
643
1410
|
|
|
644
1411
|
return len(lines) - 1
|
|
645
1412
|
|
|
1413
|
+
@staticmethod
|
|
646
1414
|
def _fix_repeated_operations(
|
|
647
|
-
self,
|
|
648
1415
|
lines: list[str],
|
|
649
1416
|
issue: dict[str, t.Any],
|
|
650
1417
|
) -> tuple[list[str], bool]:
|
|
651
|
-
"""Add comments suggesting caching for repeated expensive operations."""
|
|
652
1418
|
modified = False
|
|
653
1419
|
|
|
654
1420
|
for instance in issue["instances"]:
|
|
@@ -658,8 +1424,10 @@ class PerformanceAgent(SubAgent):
|
|
|
658
1424
|
indent_level = len(original_line) - len(original_line.lstrip())
|
|
659
1425
|
indent_str = " " * indent_level
|
|
660
1426
|
|
|
661
|
-
|
|
662
|
-
|
|
1427
|
+
comment = (
|
|
1428
|
+
f"{indent_str}# Performance: Consider caching this expensive"
|
|
1429
|
+
f" operation outside the loop"
|
|
1430
|
+
)
|
|
663
1431
|
lines.insert(line_idx, comment)
|
|
664
1432
|
modified = True
|
|
665
1433
|
|