crackerjack 0.18.2__py3-none-any.whl → 0.45.2__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.
- crackerjack/README.md +19 -0
- crackerjack/__init__.py +96 -2
- crackerjack/__main__.py +637 -138
- crackerjack/adapters/README.md +18 -0
- crackerjack/adapters/__init__.py +39 -0
- crackerjack/adapters/_output_paths.py +167 -0
- crackerjack/adapters/_qa_adapter_base.py +309 -0
- crackerjack/adapters/_tool_adapter_base.py +706 -0
- crackerjack/adapters/ai/README.md +65 -0
- crackerjack/adapters/ai/__init__.py +5 -0
- crackerjack/adapters/ai/claude.py +853 -0
- crackerjack/adapters/complexity/README.md +53 -0
- crackerjack/adapters/complexity/__init__.py +10 -0
- crackerjack/adapters/complexity/complexipy.py +641 -0
- crackerjack/adapters/dependency/__init__.py +22 -0
- crackerjack/adapters/dependency/pip_audit.py +418 -0
- crackerjack/adapters/format/README.md +72 -0
- crackerjack/adapters/format/__init__.py +11 -0
- crackerjack/adapters/format/mdformat.py +313 -0
- crackerjack/adapters/format/ruff.py +516 -0
- crackerjack/adapters/lint/README.md +47 -0
- crackerjack/adapters/lint/__init__.py +11 -0
- crackerjack/adapters/lint/codespell.py +273 -0
- crackerjack/adapters/lsp/README.md +49 -0
- crackerjack/adapters/lsp/__init__.py +27 -0
- crackerjack/adapters/lsp/_base.py +194 -0
- crackerjack/adapters/lsp/_client.py +358 -0
- crackerjack/adapters/lsp/_manager.py +193 -0
- crackerjack/adapters/lsp/skylos.py +283 -0
- crackerjack/adapters/lsp/zuban.py +557 -0
- crackerjack/adapters/refactor/README.md +59 -0
- crackerjack/adapters/refactor/__init__.py +12 -0
- crackerjack/adapters/refactor/creosote.py +318 -0
- crackerjack/adapters/refactor/refurb.py +406 -0
- crackerjack/adapters/refactor/skylos.py +494 -0
- crackerjack/adapters/sast/README.md +132 -0
- crackerjack/adapters/sast/__init__.py +32 -0
- crackerjack/adapters/sast/_base.py +201 -0
- crackerjack/adapters/sast/bandit.py +423 -0
- crackerjack/adapters/sast/pyscn.py +405 -0
- crackerjack/adapters/sast/semgrep.py +241 -0
- crackerjack/adapters/security/README.md +111 -0
- crackerjack/adapters/security/__init__.py +17 -0
- crackerjack/adapters/security/gitleaks.py +339 -0
- crackerjack/adapters/type/README.md +52 -0
- crackerjack/adapters/type/__init__.py +12 -0
- crackerjack/adapters/type/pyrefly.py +402 -0
- crackerjack/adapters/type/ty.py +402 -0
- crackerjack/adapters/type/zuban.py +522 -0
- crackerjack/adapters/utility/README.md +51 -0
- crackerjack/adapters/utility/__init__.py +10 -0
- crackerjack/adapters/utility/checks.py +884 -0
- crackerjack/agents/README.md +264 -0
- crackerjack/agents/__init__.py +66 -0
- crackerjack/agents/architect_agent.py +238 -0
- crackerjack/agents/base.py +167 -0
- crackerjack/agents/claude_code_bridge.py +641 -0
- crackerjack/agents/coordinator.py +600 -0
- crackerjack/agents/documentation_agent.py +520 -0
- crackerjack/agents/dry_agent.py +585 -0
- crackerjack/agents/enhanced_coordinator.py +279 -0
- crackerjack/agents/enhanced_proactive_agent.py +185 -0
- crackerjack/agents/error_middleware.py +53 -0
- crackerjack/agents/formatting_agent.py +230 -0
- crackerjack/agents/helpers/__init__.py +9 -0
- crackerjack/agents/helpers/performance/__init__.py +22 -0
- crackerjack/agents/helpers/performance/performance_ast_analyzer.py +357 -0
- crackerjack/agents/helpers/performance/performance_pattern_detector.py +909 -0
- crackerjack/agents/helpers/performance/performance_recommender.py +572 -0
- crackerjack/agents/helpers/refactoring/__init__.py +22 -0
- crackerjack/agents/helpers/refactoring/code_transformer.py +536 -0
- crackerjack/agents/helpers/refactoring/complexity_analyzer.py +344 -0
- crackerjack/agents/helpers/refactoring/dead_code_detector.py +437 -0
- crackerjack/agents/helpers/test_creation/__init__.py +19 -0
- crackerjack/agents/helpers/test_creation/test_ast_analyzer.py +216 -0
- crackerjack/agents/helpers/test_creation/test_coverage_analyzer.py +643 -0
- crackerjack/agents/helpers/test_creation/test_template_generator.py +1031 -0
- crackerjack/agents/import_optimization_agent.py +1181 -0
- crackerjack/agents/performance_agent.py +325 -0
- crackerjack/agents/performance_helpers.py +205 -0
- crackerjack/agents/proactive_agent.py +55 -0
- crackerjack/agents/refactoring_agent.py +511 -0
- crackerjack/agents/refactoring_helpers.py +247 -0
- crackerjack/agents/security_agent.py +793 -0
- crackerjack/agents/semantic_agent.py +479 -0
- crackerjack/agents/semantic_helpers.py +356 -0
- crackerjack/agents/test_creation_agent.py +570 -0
- crackerjack/agents/test_specialist_agent.py +526 -0
- crackerjack/agents/tracker.py +110 -0
- crackerjack/api.py +647 -0
- crackerjack/cli/README.md +394 -0
- crackerjack/cli/__init__.py +24 -0
- crackerjack/cli/cache_handlers.py +209 -0
- crackerjack/cli/cache_handlers_enhanced.py +680 -0
- crackerjack/cli/facade.py +162 -0
- crackerjack/cli/formatting.py +13 -0
- crackerjack/cli/handlers/__init__.py +85 -0
- crackerjack/cli/handlers/advanced.py +103 -0
- crackerjack/cli/handlers/ai_features.py +62 -0
- crackerjack/cli/handlers/analytics.py +479 -0
- crackerjack/cli/handlers/changelog.py +271 -0
- crackerjack/cli/handlers/config_handlers.py +16 -0
- crackerjack/cli/handlers/coverage.py +84 -0
- crackerjack/cli/handlers/documentation.py +280 -0
- crackerjack/cli/handlers/main_handlers.py +497 -0
- crackerjack/cli/handlers/monitoring.py +371 -0
- crackerjack/cli/handlers.py +700 -0
- crackerjack/cli/interactive.py +488 -0
- crackerjack/cli/options.py +1216 -0
- crackerjack/cli/semantic_handlers.py +292 -0
- crackerjack/cli/utils.py +19 -0
- crackerjack/cli/version.py +19 -0
- crackerjack/code_cleaner.py +1307 -0
- crackerjack/config/README.md +472 -0
- crackerjack/config/__init__.py +275 -0
- crackerjack/config/global_lock_config.py +207 -0
- crackerjack/config/hooks.py +390 -0
- crackerjack/config/loader.py +239 -0
- crackerjack/config/settings.py +141 -0
- crackerjack/config/tool_commands.py +331 -0
- crackerjack/core/README.md +393 -0
- crackerjack/core/__init__.py +0 -0
- crackerjack/core/async_workflow_orchestrator.py +738 -0
- crackerjack/core/autofix_coordinator.py +282 -0
- crackerjack/core/container.py +105 -0
- crackerjack/core/enhanced_container.py +583 -0
- crackerjack/core/file_lifecycle.py +472 -0
- crackerjack/core/performance.py +244 -0
- crackerjack/core/performance_monitor.py +357 -0
- crackerjack/core/phase_coordinator.py +1227 -0
- crackerjack/core/proactive_workflow.py +267 -0
- crackerjack/core/resource_manager.py +425 -0
- crackerjack/core/retry.py +275 -0
- crackerjack/core/service_watchdog.py +601 -0
- crackerjack/core/session_coordinator.py +239 -0
- crackerjack/core/timeout_manager.py +563 -0
- crackerjack/core/websocket_lifecycle.py +410 -0
- crackerjack/core/workflow/__init__.py +21 -0
- crackerjack/core/workflow/workflow_ai_coordinator.py +863 -0
- crackerjack/core/workflow/workflow_event_orchestrator.py +1107 -0
- crackerjack/core/workflow/workflow_issue_parser.py +714 -0
- crackerjack/core/workflow/workflow_phase_executor.py +1158 -0
- crackerjack/core/workflow/workflow_security_gates.py +400 -0
- crackerjack/core/workflow_orchestrator.py +2243 -0
- crackerjack/data/README.md +11 -0
- crackerjack/data/__init__.py +8 -0
- crackerjack/data/models.py +79 -0
- crackerjack/data/repository.py +210 -0
- crackerjack/decorators/README.md +180 -0
- crackerjack/decorators/__init__.py +35 -0
- crackerjack/decorators/error_handling.py +649 -0
- crackerjack/decorators/error_handling_decorators.py +334 -0
- crackerjack/decorators/helpers.py +58 -0
- crackerjack/decorators/patterns.py +281 -0
- crackerjack/decorators/utils.py +58 -0
- crackerjack/docs/INDEX.md +11 -0
- crackerjack/docs/README.md +11 -0
- crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
- crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
- crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
- crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
- crackerjack/docs/generated/api/SERVICES.md +1252 -0
- crackerjack/documentation/README.md +11 -0
- crackerjack/documentation/__init__.py +31 -0
- crackerjack/documentation/ai_templates.py +756 -0
- crackerjack/documentation/dual_output_generator.py +767 -0
- crackerjack/documentation/mkdocs_integration.py +518 -0
- crackerjack/documentation/reference_generator.py +1065 -0
- crackerjack/dynamic_config.py +678 -0
- crackerjack/errors.py +378 -0
- crackerjack/events/README.md +11 -0
- crackerjack/events/__init__.py +16 -0
- crackerjack/events/telemetry.py +175 -0
- crackerjack/events/workflow_bus.py +346 -0
- crackerjack/exceptions/README.md +301 -0
- crackerjack/exceptions/__init__.py +5 -0
- crackerjack/exceptions/config.py +4 -0
- crackerjack/exceptions/tool_execution_error.py +245 -0
- crackerjack/executors/README.md +591 -0
- crackerjack/executors/__init__.py +13 -0
- crackerjack/executors/async_hook_executor.py +938 -0
- crackerjack/executors/cached_hook_executor.py +316 -0
- crackerjack/executors/hook_executor.py +1295 -0
- crackerjack/executors/hook_lock_manager.py +708 -0
- crackerjack/executors/individual_hook_executor.py +739 -0
- crackerjack/executors/lsp_aware_hook_executor.py +349 -0
- crackerjack/executors/progress_hook_executor.py +282 -0
- crackerjack/executors/tool_proxy.py +433 -0
- crackerjack/hooks/README.md +485 -0
- crackerjack/hooks/lsp_hook.py +93 -0
- crackerjack/intelligence/README.md +557 -0
- crackerjack/intelligence/__init__.py +37 -0
- crackerjack/intelligence/adaptive_learning.py +693 -0
- crackerjack/intelligence/agent_orchestrator.py +485 -0
- crackerjack/intelligence/agent_registry.py +377 -0
- crackerjack/intelligence/agent_selector.py +439 -0
- crackerjack/intelligence/integration.py +250 -0
- crackerjack/interactive.py +719 -0
- crackerjack/managers/README.md +369 -0
- crackerjack/managers/__init__.py +11 -0
- crackerjack/managers/async_hook_manager.py +135 -0
- crackerjack/managers/hook_manager.py +585 -0
- crackerjack/managers/publish_manager.py +631 -0
- crackerjack/managers/test_command_builder.py +391 -0
- crackerjack/managers/test_executor.py +474 -0
- crackerjack/managers/test_manager.py +1357 -0
- crackerjack/managers/test_progress.py +187 -0
- crackerjack/mcp/README.md +374 -0
- crackerjack/mcp/__init__.py +0 -0
- crackerjack/mcp/cache.py +352 -0
- crackerjack/mcp/client_runner.py +121 -0
- crackerjack/mcp/context.py +802 -0
- crackerjack/mcp/dashboard.py +657 -0
- crackerjack/mcp/enhanced_progress_monitor.py +493 -0
- crackerjack/mcp/file_monitor.py +394 -0
- crackerjack/mcp/progress_components.py +607 -0
- crackerjack/mcp/progress_monitor.py +1016 -0
- crackerjack/mcp/rate_limiter.py +336 -0
- crackerjack/mcp/server.py +24 -0
- crackerjack/mcp/server_core.py +526 -0
- crackerjack/mcp/service_watchdog.py +505 -0
- crackerjack/mcp/state.py +407 -0
- crackerjack/mcp/task_manager.py +259 -0
- crackerjack/mcp/tools/README.md +27 -0
- crackerjack/mcp/tools/__init__.py +19 -0
- crackerjack/mcp/tools/core_tools.py +469 -0
- crackerjack/mcp/tools/error_analyzer.py +283 -0
- crackerjack/mcp/tools/execution_tools.py +384 -0
- crackerjack/mcp/tools/intelligence_tool_registry.py +46 -0
- crackerjack/mcp/tools/intelligence_tools.py +264 -0
- crackerjack/mcp/tools/monitoring_tools.py +628 -0
- crackerjack/mcp/tools/proactive_tools.py +367 -0
- crackerjack/mcp/tools/progress_tools.py +222 -0
- crackerjack/mcp/tools/semantic_tools.py +584 -0
- crackerjack/mcp/tools/utility_tools.py +358 -0
- crackerjack/mcp/tools/workflow_executor.py +699 -0
- crackerjack/mcp/websocket/README.md +31 -0
- crackerjack/mcp/websocket/__init__.py +14 -0
- crackerjack/mcp/websocket/app.py +54 -0
- crackerjack/mcp/websocket/endpoints.py +492 -0
- crackerjack/mcp/websocket/event_bridge.py +188 -0
- crackerjack/mcp/websocket/jobs.py +406 -0
- crackerjack/mcp/websocket/monitoring/__init__.py +25 -0
- crackerjack/mcp/websocket/monitoring/api/__init__.py +19 -0
- crackerjack/mcp/websocket/monitoring/api/dependencies.py +141 -0
- crackerjack/mcp/websocket/monitoring/api/heatmap.py +154 -0
- crackerjack/mcp/websocket/monitoring/api/intelligence.py +199 -0
- crackerjack/mcp/websocket/monitoring/api/metrics.py +203 -0
- crackerjack/mcp/websocket/monitoring/api/telemetry.py +101 -0
- crackerjack/mcp/websocket/monitoring/dashboard.py +18 -0
- crackerjack/mcp/websocket/monitoring/factory.py +109 -0
- crackerjack/mcp/websocket/monitoring/filters.py +10 -0
- crackerjack/mcp/websocket/monitoring/metrics.py +64 -0
- crackerjack/mcp/websocket/monitoring/models.py +90 -0
- crackerjack/mcp/websocket/monitoring/utils.py +171 -0
- crackerjack/mcp/websocket/monitoring/websocket_manager.py +78 -0
- crackerjack/mcp/websocket/monitoring/websockets/__init__.py +17 -0
- crackerjack/mcp/websocket/monitoring/websockets/dependencies.py +126 -0
- crackerjack/mcp/websocket/monitoring/websockets/heatmap.py +176 -0
- crackerjack/mcp/websocket/monitoring/websockets/intelligence.py +291 -0
- crackerjack/mcp/websocket/monitoring/websockets/metrics.py +291 -0
- crackerjack/mcp/websocket/monitoring_endpoints.py +21 -0
- crackerjack/mcp/websocket/server.py +174 -0
- crackerjack/mcp/websocket/websocket_handler.py +276 -0
- crackerjack/mcp/websocket_server.py +10 -0
- crackerjack/models/README.md +308 -0
- crackerjack/models/__init__.py +40 -0
- crackerjack/models/config.py +730 -0
- crackerjack/models/config_adapter.py +265 -0
- crackerjack/models/protocols.py +1535 -0
- crackerjack/models/pydantic_models.py +320 -0
- crackerjack/models/qa_config.py +145 -0
- crackerjack/models/qa_results.py +134 -0
- crackerjack/models/resource_protocols.py +299 -0
- crackerjack/models/results.py +35 -0
- crackerjack/models/semantic_models.py +258 -0
- crackerjack/models/task.py +173 -0
- crackerjack/models/test_models.py +60 -0
- crackerjack/monitoring/README.md +11 -0
- crackerjack/monitoring/__init__.py +0 -0
- crackerjack/monitoring/ai_agent_watchdog.py +405 -0
- crackerjack/monitoring/metrics_collector.py +427 -0
- crackerjack/monitoring/regression_prevention.py +580 -0
- crackerjack/monitoring/websocket_server.py +406 -0
- crackerjack/orchestration/README.md +340 -0
- crackerjack/orchestration/__init__.py +43 -0
- crackerjack/orchestration/advanced_orchestrator.py +894 -0
- crackerjack/orchestration/cache/README.md +312 -0
- crackerjack/orchestration/cache/__init__.py +37 -0
- crackerjack/orchestration/cache/memory_cache.py +338 -0
- crackerjack/orchestration/cache/tool_proxy_cache.py +340 -0
- crackerjack/orchestration/config.py +297 -0
- crackerjack/orchestration/coverage_improvement.py +180 -0
- crackerjack/orchestration/execution_strategies.py +361 -0
- crackerjack/orchestration/hook_orchestrator.py +1398 -0
- crackerjack/orchestration/strategies/README.md +401 -0
- crackerjack/orchestration/strategies/__init__.py +39 -0
- crackerjack/orchestration/strategies/adaptive_strategy.py +630 -0
- crackerjack/orchestration/strategies/parallel_strategy.py +237 -0
- crackerjack/orchestration/strategies/sequential_strategy.py +299 -0
- crackerjack/orchestration/test_progress_streamer.py +647 -0
- crackerjack/plugins/README.md +11 -0
- crackerjack/plugins/__init__.py +15 -0
- crackerjack/plugins/base.py +200 -0
- crackerjack/plugins/hooks.py +254 -0
- crackerjack/plugins/loader.py +335 -0
- crackerjack/plugins/managers.py +264 -0
- crackerjack/py313.py +191 -0
- crackerjack/security/README.md +11 -0
- crackerjack/security/__init__.py +0 -0
- crackerjack/security/audit.py +197 -0
- crackerjack/services/README.md +374 -0
- crackerjack/services/__init__.py +9 -0
- crackerjack/services/ai/README.md +295 -0
- crackerjack/services/ai/__init__.py +7 -0
- crackerjack/services/ai/advanced_optimizer.py +878 -0
- crackerjack/services/ai/contextual_ai_assistant.py +542 -0
- crackerjack/services/ai/embeddings.py +444 -0
- crackerjack/services/ai/intelligent_commit.py +328 -0
- crackerjack/services/ai/predictive_analytics.py +510 -0
- crackerjack/services/anomaly_detector.py +392 -0
- crackerjack/services/api_extractor.py +617 -0
- crackerjack/services/backup_service.py +467 -0
- crackerjack/services/bounded_status_operations.py +530 -0
- crackerjack/services/cache.py +369 -0
- crackerjack/services/changelog_automation.py +399 -0
- crackerjack/services/command_execution_service.py +305 -0
- crackerjack/services/config_integrity.py +132 -0
- crackerjack/services/config_merge.py +546 -0
- crackerjack/services/config_service.py +198 -0
- crackerjack/services/config_template.py +493 -0
- crackerjack/services/coverage_badge_service.py +173 -0
- crackerjack/services/coverage_ratchet.py +381 -0
- crackerjack/services/debug.py +733 -0
- crackerjack/services/dependency_analyzer.py +460 -0
- crackerjack/services/dependency_monitor.py +622 -0
- crackerjack/services/documentation_generator.py +493 -0
- crackerjack/services/documentation_service.py +704 -0
- crackerjack/services/enhanced_filesystem.py +497 -0
- crackerjack/services/enterprise_optimizer.py +865 -0
- crackerjack/services/error_pattern_analyzer.py +676 -0
- crackerjack/services/file_filter.py +221 -0
- crackerjack/services/file_hasher.py +149 -0
- crackerjack/services/file_io_service.py +361 -0
- crackerjack/services/file_modifier.py +615 -0
- crackerjack/services/filesystem.py +381 -0
- crackerjack/services/git.py +422 -0
- crackerjack/services/health_metrics.py +615 -0
- crackerjack/services/heatmap_generator.py +744 -0
- crackerjack/services/incremental_executor.py +380 -0
- crackerjack/services/initialization.py +823 -0
- crackerjack/services/input_validator.py +668 -0
- crackerjack/services/intelligent_commit.py +327 -0
- crackerjack/services/log_manager.py +289 -0
- crackerjack/services/logging.py +228 -0
- crackerjack/services/lsp_client.py +628 -0
- crackerjack/services/memory_optimizer.py +414 -0
- crackerjack/services/metrics.py +587 -0
- crackerjack/services/monitoring/README.md +30 -0
- crackerjack/services/monitoring/__init__.py +9 -0
- crackerjack/services/monitoring/dependency_monitor.py +678 -0
- crackerjack/services/monitoring/error_pattern_analyzer.py +676 -0
- crackerjack/services/monitoring/health_metrics.py +716 -0
- crackerjack/services/monitoring/metrics.py +587 -0
- crackerjack/services/monitoring/performance_benchmarks.py +410 -0
- crackerjack/services/monitoring/performance_cache.py +388 -0
- crackerjack/services/monitoring/performance_monitor.py +569 -0
- crackerjack/services/parallel_executor.py +527 -0
- crackerjack/services/pattern_cache.py +333 -0
- crackerjack/services/pattern_detector.py +478 -0
- crackerjack/services/patterns/__init__.py +142 -0
- crackerjack/services/patterns/agents.py +107 -0
- crackerjack/services/patterns/code/__init__.py +15 -0
- crackerjack/services/patterns/code/detection.py +118 -0
- crackerjack/services/patterns/code/imports.py +107 -0
- crackerjack/services/patterns/code/paths.py +159 -0
- crackerjack/services/patterns/code/performance.py +119 -0
- crackerjack/services/patterns/code/replacement.py +36 -0
- crackerjack/services/patterns/core.py +212 -0
- crackerjack/services/patterns/documentation/__init__.py +14 -0
- crackerjack/services/patterns/documentation/badges_markdown.py +96 -0
- crackerjack/services/patterns/documentation/comments_blocks.py +83 -0
- crackerjack/services/patterns/documentation/docstrings.py +89 -0
- crackerjack/services/patterns/formatting.py +226 -0
- crackerjack/services/patterns/operations.py +339 -0
- crackerjack/services/patterns/security/__init__.py +23 -0
- crackerjack/services/patterns/security/code_injection.py +122 -0
- crackerjack/services/patterns/security/credentials.py +190 -0
- crackerjack/services/patterns/security/path_traversal.py +221 -0
- crackerjack/services/patterns/security/unsafe_operations.py +216 -0
- crackerjack/services/patterns/templates.py +62 -0
- crackerjack/services/patterns/testing/__init__.py +18 -0
- crackerjack/services/patterns/testing/error_patterns.py +107 -0
- crackerjack/services/patterns/testing/pytest_output.py +126 -0
- crackerjack/services/patterns/tool_output/__init__.py +16 -0
- crackerjack/services/patterns/tool_output/bandit.py +72 -0
- crackerjack/services/patterns/tool_output/other.py +97 -0
- crackerjack/services/patterns/tool_output/pyright.py +67 -0
- crackerjack/services/patterns/tool_output/ruff.py +44 -0
- crackerjack/services/patterns/url_sanitization.py +114 -0
- crackerjack/services/patterns/utilities.py +42 -0
- crackerjack/services/patterns/utils.py +339 -0
- crackerjack/services/patterns/validation.py +46 -0
- crackerjack/services/patterns/versioning.py +62 -0
- crackerjack/services/predictive_analytics.py +523 -0
- crackerjack/services/profiler.py +280 -0
- crackerjack/services/quality/README.md +415 -0
- crackerjack/services/quality/__init__.py +11 -0
- crackerjack/services/quality/anomaly_detector.py +392 -0
- crackerjack/services/quality/pattern_cache.py +333 -0
- crackerjack/services/quality/pattern_detector.py +479 -0
- crackerjack/services/quality/qa_orchestrator.py +491 -0
- crackerjack/services/quality/quality_baseline.py +395 -0
- crackerjack/services/quality/quality_baseline_enhanced.py +649 -0
- crackerjack/services/quality/quality_intelligence.py +949 -0
- crackerjack/services/regex_patterns.py +58 -0
- crackerjack/services/regex_utils.py +483 -0
- crackerjack/services/secure_path_utils.py +524 -0
- crackerjack/services/secure_status_formatter.py +450 -0
- crackerjack/services/secure_subprocess.py +635 -0
- crackerjack/services/security.py +239 -0
- crackerjack/services/security_logger.py +495 -0
- crackerjack/services/server_manager.py +411 -0
- crackerjack/services/smart_scheduling.py +167 -0
- crackerjack/services/status_authentication.py +460 -0
- crackerjack/services/status_security_manager.py +315 -0
- crackerjack/services/terminal_utils.py +0 -0
- crackerjack/services/thread_safe_status_collector.py +441 -0
- crackerjack/services/tool_filter.py +368 -0
- crackerjack/services/tool_version_service.py +43 -0
- crackerjack/services/unified_config.py +115 -0
- crackerjack/services/validation_rate_limiter.py +220 -0
- crackerjack/services/vector_store.py +689 -0
- crackerjack/services/version_analyzer.py +461 -0
- crackerjack/services/version_checker.py +223 -0
- crackerjack/services/websocket_resource_limiter.py +438 -0
- crackerjack/services/zuban_lsp_service.py +391 -0
- crackerjack/slash_commands/README.md +11 -0
- crackerjack/slash_commands/__init__.py +59 -0
- crackerjack/slash_commands/init.md +112 -0
- crackerjack/slash_commands/run.md +197 -0
- crackerjack/slash_commands/status.md +127 -0
- crackerjack/tools/README.md +11 -0
- crackerjack/tools/__init__.py +30 -0
- crackerjack/tools/_git_utils.py +105 -0
- crackerjack/tools/check_added_large_files.py +139 -0
- crackerjack/tools/check_ast.py +105 -0
- crackerjack/tools/check_json.py +103 -0
- crackerjack/tools/check_jsonschema.py +297 -0
- crackerjack/tools/check_toml.py +103 -0
- crackerjack/tools/check_yaml.py +110 -0
- crackerjack/tools/codespell_wrapper.py +72 -0
- crackerjack/tools/end_of_file_fixer.py +202 -0
- crackerjack/tools/format_json.py +128 -0
- crackerjack/tools/mdformat_wrapper.py +114 -0
- crackerjack/tools/trailing_whitespace.py +198 -0
- crackerjack/tools/validate_input_validator_patterns.py +236 -0
- crackerjack/tools/validate_regex_patterns.py +188 -0
- crackerjack/ui/README.md +11 -0
- crackerjack/ui/__init__.py +1 -0
- crackerjack/ui/dashboard_renderer.py +28 -0
- crackerjack/ui/templates/README.md +11 -0
- crackerjack/utils/console_utils.py +13 -0
- crackerjack/utils/dependency_guard.py +230 -0
- crackerjack/utils/retry_utils.py +275 -0
- crackerjack/workflows/README.md +590 -0
- crackerjack/workflows/__init__.py +46 -0
- crackerjack/workflows/actions.py +811 -0
- crackerjack/workflows/auto_fix.py +444 -0
- crackerjack/workflows/container_builder.py +499 -0
- crackerjack/workflows/definitions.py +443 -0
- crackerjack/workflows/engine.py +177 -0
- crackerjack/workflows/event_bridge.py +242 -0
- crackerjack-0.45.2.dist-info/METADATA +1678 -0
- crackerjack-0.45.2.dist-info/RECORD +478 -0
- {crackerjack-0.18.2.dist-info → crackerjack-0.45.2.dist-info}/WHEEL +1 -1
- crackerjack-0.45.2.dist-info/entry_points.txt +2 -0
- crackerjack/.gitignore +0 -14
- crackerjack/.libcst.codemod.yaml +0 -18
- crackerjack/.pdm.toml +0 -1
- crackerjack/.pre-commit-config.yaml +0 -91
- crackerjack/.pytest_cache/.gitignore +0 -2
- crackerjack/.pytest_cache/CACHEDIR.TAG +0 -4
- crackerjack/.pytest_cache/README.md +0 -8
- crackerjack/.pytest_cache/v/cache/nodeids +0 -1
- crackerjack/.pytest_cache/v/cache/stepwise +0 -1
- crackerjack/.ruff_cache/.gitignore +0 -1
- crackerjack/.ruff_cache/0.1.11/3256171999636029978 +0 -0
- crackerjack/.ruff_cache/0.1.14/602324811142551221 +0 -0
- crackerjack/.ruff_cache/0.1.4/10355199064880463147 +0 -0
- crackerjack/.ruff_cache/0.1.6/15140459877605758699 +0 -0
- crackerjack/.ruff_cache/0.1.7/1790508110482614856 +0 -0
- crackerjack/.ruff_cache/0.1.9/17041001205004563469 +0 -0
- crackerjack/.ruff_cache/0.11.2/4070660268492669020 +0 -0
- crackerjack/.ruff_cache/0.11.3/9818742842212983150 +0 -0
- crackerjack/.ruff_cache/0.11.4/9818742842212983150 +0 -0
- crackerjack/.ruff_cache/0.11.6/3557596832929915217 +0 -0
- crackerjack/.ruff_cache/0.11.7/10386934055395314831 +0 -0
- crackerjack/.ruff_cache/0.11.7/3557596832929915217 +0 -0
- crackerjack/.ruff_cache/0.11.8/530407680854991027 +0 -0
- crackerjack/.ruff_cache/0.2.0/10047773857155985907 +0 -0
- crackerjack/.ruff_cache/0.2.1/8522267973936635051 +0 -0
- crackerjack/.ruff_cache/0.2.2/18053836298936336950 +0 -0
- crackerjack/.ruff_cache/0.3.0/12548816621480535786 +0 -0
- crackerjack/.ruff_cache/0.3.3/11081883392474770722 +0 -0
- crackerjack/.ruff_cache/0.3.4/676973378459347183 +0 -0
- crackerjack/.ruff_cache/0.3.5/16311176246009842383 +0 -0
- crackerjack/.ruff_cache/0.5.7/1493622539551733492 +0 -0
- crackerjack/.ruff_cache/0.5.7/6231957614044513175 +0 -0
- crackerjack/.ruff_cache/0.5.7/9932762556785938009 +0 -0
- crackerjack/.ruff_cache/0.6.0/11982804814124138945 +0 -0
- crackerjack/.ruff_cache/0.6.0/12055761203849489982 +0 -0
- crackerjack/.ruff_cache/0.6.2/1206147804896221174 +0 -0
- crackerjack/.ruff_cache/0.6.4/1206147804896221174 +0 -0
- crackerjack/.ruff_cache/0.6.5/1206147804896221174 +0 -0
- crackerjack/.ruff_cache/0.6.7/3657366982708166874 +0 -0
- crackerjack/.ruff_cache/0.6.9/285614542852677309 +0 -0
- crackerjack/.ruff_cache/0.7.1/1024065805990144819 +0 -0
- crackerjack/.ruff_cache/0.7.1/285614542852677309 +0 -0
- crackerjack/.ruff_cache/0.7.3/16061516852537040135 +0 -0
- crackerjack/.ruff_cache/0.8.4/16354268377385700367 +0 -0
- crackerjack/.ruff_cache/0.9.10/12813592349865671909 +0 -0
- crackerjack/.ruff_cache/0.9.10/923908772239632759 +0 -0
- crackerjack/.ruff_cache/0.9.3/13948373885254993391 +0 -0
- crackerjack/.ruff_cache/0.9.9/12813592349865671909 +0 -0
- crackerjack/.ruff_cache/0.9.9/8843823720003377982 +0 -0
- crackerjack/.ruff_cache/CACHEDIR.TAG +0 -1
- crackerjack/crackerjack.py +0 -855
- crackerjack/pyproject.toml +0 -214
- crackerjack-0.18.2.dist-info/METADATA +0 -420
- crackerjack-0.18.2.dist-info/RECORD +0 -59
- crackerjack-0.18.2.dist-info/entry_points.txt +0 -4
- {crackerjack-0.18.2.dist-info → crackerjack-0.45.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,909 @@
|
|
|
1
|
+
"""Pattern detection for performance anti-patterns in Python code."""
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import operator
|
|
5
|
+
import typing as t
|
|
6
|
+
from contextlib import suppress
|
|
7
|
+
|
|
8
|
+
from ....services.regex_patterns import SAFE_PATTERNS
|
|
9
|
+
from ...base import AgentContext
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PerformancePatternDetector:
|
|
13
|
+
"""Detects performance anti-patterns in Python code using AST and pattern matching."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, context: AgentContext) -> None:
|
|
16
|
+
"""Initialize detector with agent context.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
context: AgentContext for file operations and logging
|
|
20
|
+
"""
|
|
21
|
+
self.context = context
|
|
22
|
+
|
|
23
|
+
def detect_performance_issues(
|
|
24
|
+
self,
|
|
25
|
+
content: str,
|
|
26
|
+
file_path: t.Any,
|
|
27
|
+
) -> list[dict[str, t.Any]]:
|
|
28
|
+
"""Detect all performance issues in content using pattern matching.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
content: File content to analyze
|
|
32
|
+
file_path: Path to the file
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
List of detected performance issues
|
|
36
|
+
"""
|
|
37
|
+
issues: list[dict[str, t.Any]] = []
|
|
38
|
+
|
|
39
|
+
with suppress(SyntaxError):
|
|
40
|
+
tree = ast.parse(content)
|
|
41
|
+
|
|
42
|
+
nested_issues = self._detect_nested_loops_enhanced(tree)
|
|
43
|
+
issues.extend(nested_issues)
|
|
44
|
+
|
|
45
|
+
list_issues = self._detect_inefficient_list_ops_enhanced(content, tree)
|
|
46
|
+
issues.extend(list_issues)
|
|
47
|
+
|
|
48
|
+
repeated_issues = self._detect_repeated_operations_enhanced(content, tree)
|
|
49
|
+
issues.extend(repeated_issues)
|
|
50
|
+
|
|
51
|
+
string_issues = self._detect_string_inefficiencies_enhanced(content)
|
|
52
|
+
issues.extend(string_issues)
|
|
53
|
+
|
|
54
|
+
comprehension_issues = self._detect_list_comprehension_opportunities(tree)
|
|
55
|
+
issues.extend(comprehension_issues)
|
|
56
|
+
|
|
57
|
+
builtin_issues = self._detect_inefficient_builtin_usage(tree, content)
|
|
58
|
+
issues.extend(builtin_issues)
|
|
59
|
+
|
|
60
|
+
return issues
|
|
61
|
+
|
|
62
|
+
def _detect_nested_loops_enhanced(self, tree: ast.AST) -> list[dict[str, t.Any]]:
|
|
63
|
+
"""Detect nested loops with complexity analysis.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
tree: AST tree to analyze
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
List of nested loop issues
|
|
70
|
+
"""
|
|
71
|
+
analyzer = self._create_nested_loop_analyzer()
|
|
72
|
+
analyzer.visit(tree)
|
|
73
|
+
return self._build_nested_loop_issues(analyzer)
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def _create_nested_loop_analyzer() -> "NestedLoopAnalyzer":
|
|
77
|
+
"""Create AST analyzer for nested loops.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
NestedLoopAnalyzer instance
|
|
81
|
+
"""
|
|
82
|
+
return NestedLoopAnalyzer()
|
|
83
|
+
|
|
84
|
+
def _build_nested_loop_issues(
|
|
85
|
+
self, analyzer: "NestedLoopAnalyzer"
|
|
86
|
+
) -> list[dict[str, t.Any]]:
|
|
87
|
+
"""Build issue data from nested loop analysis.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
analyzer: NestedLoopAnalyzer instance
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
List of issues
|
|
94
|
+
"""
|
|
95
|
+
if not analyzer.nested_loops:
|
|
96
|
+
return []
|
|
97
|
+
|
|
98
|
+
return [
|
|
99
|
+
{
|
|
100
|
+
"type": "nested_loops_enhanced",
|
|
101
|
+
"instances": analyzer.nested_loops,
|
|
102
|
+
"hotspots": analyzer.complexity_hotspots,
|
|
103
|
+
"total_count": len(analyzer.nested_loops),
|
|
104
|
+
"high_priority_count": self._count_high_priority_loops(
|
|
105
|
+
analyzer.nested_loops
|
|
106
|
+
),
|
|
107
|
+
"suggestion": self._generate_nested_loop_suggestions(
|
|
108
|
+
analyzer.nested_loops
|
|
109
|
+
),
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def _count_high_priority_loops(nested_loops: list[dict[str, t.Any]]) -> int:
|
|
115
|
+
"""Count high priority nested loops.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
nested_loops: List of nested loop instances
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Count of high priority loops
|
|
122
|
+
"""
|
|
123
|
+
return len([n for n in nested_loops if n["priority"] in ("high", "critical")])
|
|
124
|
+
|
|
125
|
+
@staticmethod
|
|
126
|
+
def _generate_nested_loop_suggestions(nested_loops: list[dict[str, t.Any]]) -> str:
|
|
127
|
+
"""Generate optimization suggestions for nested loops.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
nested_loops: List of nested loop instances
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Suggestion string
|
|
134
|
+
"""
|
|
135
|
+
suggestions = []
|
|
136
|
+
|
|
137
|
+
critical_count = len(
|
|
138
|
+
[n for n in nested_loops if n.get("priority") == "critical"]
|
|
139
|
+
)
|
|
140
|
+
high_count = len([n for n in nested_loops if n.get("priority") == "high"])
|
|
141
|
+
|
|
142
|
+
if critical_count > 0:
|
|
143
|
+
suggestions.append(
|
|
144
|
+
f"CRITICAL: {critical_count} O(n⁴+) loops need immediate algorithmic redesign"
|
|
145
|
+
)
|
|
146
|
+
if high_count > 0:
|
|
147
|
+
suggestions.append(
|
|
148
|
+
f"HIGH: {high_count} O(n³) loops should use memoization/caching"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
suggestions.extend(
|
|
152
|
+
[
|
|
153
|
+
"Consider: 1) Hash tables for lookups 2) List comprehensions 3) NumPy for numerical operations",
|
|
154
|
+
"Profile: Use timeit or cProfile to measure actual performance impact",
|
|
155
|
+
]
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
return "; ".join(suggestions)
|
|
159
|
+
|
|
160
|
+
def _detect_inefficient_list_ops_enhanced(
|
|
161
|
+
self,
|
|
162
|
+
content: str,
|
|
163
|
+
tree: ast.AST,
|
|
164
|
+
) -> list[dict[str, t.Any]]:
|
|
165
|
+
"""Detect inefficient list operations in loops.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
content: File content
|
|
169
|
+
tree: AST tree
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
List of inefficient list operation issues
|
|
173
|
+
"""
|
|
174
|
+
analyzer = self._create_enhanced_list_op_analyzer()
|
|
175
|
+
analyzer.visit(tree)
|
|
176
|
+
|
|
177
|
+
if not analyzer.list_ops:
|
|
178
|
+
return []
|
|
179
|
+
|
|
180
|
+
return self._build_list_ops_issues(analyzer)
|
|
181
|
+
|
|
182
|
+
@staticmethod
|
|
183
|
+
def _create_enhanced_list_op_analyzer() -> "ListOpAnalyzer":
|
|
184
|
+
"""Create AST analyzer for list operations.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
ListOpAnalyzer instance
|
|
188
|
+
"""
|
|
189
|
+
return ListOpAnalyzer()
|
|
190
|
+
|
|
191
|
+
def _build_list_ops_issues(
|
|
192
|
+
self, analyzer: "ListOpAnalyzer"
|
|
193
|
+
) -> list[dict[str, t.Any]]:
|
|
194
|
+
"""Build issue data from list operation analysis.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
analyzer: ListOpAnalyzer instance
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
List of issues
|
|
201
|
+
"""
|
|
202
|
+
total_impact = sum(int(op["impact_factor"]) for op in analyzer.list_ops)
|
|
203
|
+
high_impact_ops = [
|
|
204
|
+
op for op in analyzer.list_ops if int(op["impact_factor"]) >= 10
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
return [
|
|
208
|
+
{
|
|
209
|
+
"type": "inefficient_list_operations_enhanced",
|
|
210
|
+
"instances": analyzer.list_ops,
|
|
211
|
+
"total_impact": total_impact,
|
|
212
|
+
"high_impact_count": len(high_impact_ops),
|
|
213
|
+
"suggestion": self._generate_list_op_suggestions(analyzer.list_ops),
|
|
214
|
+
}
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
@staticmethod
|
|
218
|
+
def _generate_list_op_suggestions(list_ops: list[dict[str, t.Any]]) -> str:
|
|
219
|
+
"""Generate optimization suggestions for list operations.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
list_ops: List of inefficient operations
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
Suggestion string
|
|
226
|
+
"""
|
|
227
|
+
suggestions = []
|
|
228
|
+
|
|
229
|
+
high_impact_count = len(
|
|
230
|
+
[op for op in list_ops if int(op["impact_factor"]) >= 10]
|
|
231
|
+
)
|
|
232
|
+
if high_impact_count > 0:
|
|
233
|
+
suggestions.append(
|
|
234
|
+
f"HIGH IMPACT: {high_impact_count} list[t.Any] operations in hot loops"
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
append_count = len([op for op in list_ops if op["optimization"] == "append"])
|
|
238
|
+
extend_count = len([op for op in list_ops if op["optimization"] == "extend"])
|
|
239
|
+
|
|
240
|
+
if append_count > 0:
|
|
241
|
+
suggestions.append(f"Replace {append_count} += [item] with .append(item)")
|
|
242
|
+
if extend_count > 0:
|
|
243
|
+
suggestions.append(
|
|
244
|
+
f"Replace {extend_count} += multiple_items with .extend()"
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
suggestions.append(
|
|
248
|
+
"Expected performance gains: 2-50x depending on loop context"
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
return "; ".join(suggestions)
|
|
252
|
+
|
|
253
|
+
def _detect_repeated_operations_enhanced(
|
|
254
|
+
self,
|
|
255
|
+
content: str,
|
|
256
|
+
tree: ast.AST,
|
|
257
|
+
) -> list[dict[str, t.Any]]:
|
|
258
|
+
"""Detect repeated expensive operations in loops.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
content: File content
|
|
262
|
+
tree: AST tree
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
List of repeated operation issues
|
|
266
|
+
"""
|
|
267
|
+
lines = content.split("\n")
|
|
268
|
+
repeated_calls = self._find_expensive_operations_in_loops(lines)
|
|
269
|
+
|
|
270
|
+
return self._create_repeated_operations_issues(repeated_calls)
|
|
271
|
+
|
|
272
|
+
def _find_expensive_operations_in_loops(
|
|
273
|
+
self,
|
|
274
|
+
lines: list[str],
|
|
275
|
+
) -> list[dict[str, t.Any]]:
|
|
276
|
+
"""Find expensive operations inside loops.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
lines: File lines
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
List of repeated operation records
|
|
283
|
+
"""
|
|
284
|
+
repeated_calls: list[dict[str, t.Any]] = []
|
|
285
|
+
expensive_patterns = self._get_expensive_operation_patterns()
|
|
286
|
+
|
|
287
|
+
for i, line in enumerate(lines):
|
|
288
|
+
stripped = line.strip()
|
|
289
|
+
if self._contains_expensive_operation(stripped, expensive_patterns):
|
|
290
|
+
if self._is_in_loop_context(lines, i):
|
|
291
|
+
repeated_calls.append(self._create_operation_record(i, stripped))
|
|
292
|
+
|
|
293
|
+
return repeated_calls
|
|
294
|
+
|
|
295
|
+
@staticmethod
|
|
296
|
+
def _get_expensive_operation_patterns() -> tuple[str, ...]:
|
|
297
|
+
"""Get patterns for expensive operations.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
Tuple of pattern strings
|
|
301
|
+
"""
|
|
302
|
+
return (
|
|
303
|
+
".exists()",
|
|
304
|
+
".read_text()",
|
|
305
|
+
".glob(",
|
|
306
|
+
".rglob(",
|
|
307
|
+
"Path(",
|
|
308
|
+
"len(",
|
|
309
|
+
".get(",
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
@staticmethod
|
|
313
|
+
def _contains_expensive_operation(
|
|
314
|
+
line: str,
|
|
315
|
+
patterns: tuple[str, ...],
|
|
316
|
+
) -> bool:
|
|
317
|
+
"""Check if line contains expensive operations.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
line: Code line
|
|
321
|
+
patterns: Patterns to match
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
True if expensive operation found
|
|
325
|
+
"""
|
|
326
|
+
return any(pattern in line for pattern in patterns)
|
|
327
|
+
|
|
328
|
+
@staticmethod
|
|
329
|
+
def _is_in_loop_context(lines: list[str], line_index: int) -> bool:
|
|
330
|
+
"""Check if line is inside a loop.
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
lines: File lines
|
|
334
|
+
line_index: Index of line to check
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
True if inside loop context
|
|
338
|
+
"""
|
|
339
|
+
context_start = max(0, line_index - 5)
|
|
340
|
+
context_lines = lines[context_start : line_index + 1]
|
|
341
|
+
|
|
342
|
+
loop_keywords = ("for ", "while ")
|
|
343
|
+
return any(
|
|
344
|
+
any(keyword in ctx_line for keyword in loop_keywords)
|
|
345
|
+
for ctx_line in context_lines
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
@staticmethod
|
|
349
|
+
def _create_operation_record(
|
|
350
|
+
line_index: int,
|
|
351
|
+
content: str,
|
|
352
|
+
) -> dict[str, t.Any]:
|
|
353
|
+
"""Create record for operation.
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
line_index: Line number
|
|
357
|
+
content: Line content
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
Operation record
|
|
361
|
+
"""
|
|
362
|
+
return {
|
|
363
|
+
"line_number": line_index + 1,
|
|
364
|
+
"content": content,
|
|
365
|
+
"type": "expensive_operation_in_loop",
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
@staticmethod
|
|
369
|
+
def _create_repeated_operations_issues(
|
|
370
|
+
repeated_calls: list[dict[str, t.Any]],
|
|
371
|
+
) -> list[dict[str, t.Any]]:
|
|
372
|
+
"""Create issues from repeated operations.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
repeated_calls: List of repeated operation records
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
List of issues
|
|
379
|
+
"""
|
|
380
|
+
if len(repeated_calls) >= 2:
|
|
381
|
+
return [
|
|
382
|
+
{
|
|
383
|
+
"type": "repeated_expensive_operations",
|
|
384
|
+
"instances": repeated_calls,
|
|
385
|
+
"suggestion": "Cache expensive operations outside loops",
|
|
386
|
+
},
|
|
387
|
+
]
|
|
388
|
+
return []
|
|
389
|
+
|
|
390
|
+
def _detect_string_inefficiencies_enhanced(
|
|
391
|
+
self, content: str
|
|
392
|
+
) -> list[dict[str, t.Any]]:
|
|
393
|
+
"""Detect string building inefficiencies.
|
|
394
|
+
|
|
395
|
+
Args:
|
|
396
|
+
content: File content
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
List of string inefficiency issues
|
|
400
|
+
"""
|
|
401
|
+
issues: list[dict[str, t.Any]] = []
|
|
402
|
+
lines = content.split("\n")
|
|
403
|
+
|
|
404
|
+
string_concat_patterns = []
|
|
405
|
+
inefficient_joins = []
|
|
406
|
+
repeated_format_calls = []
|
|
407
|
+
|
|
408
|
+
for i, line in enumerate(lines):
|
|
409
|
+
stripped = line.strip()
|
|
410
|
+
|
|
411
|
+
if "+=" in stripped and any(quote in stripped for quote in ('"', "'")):
|
|
412
|
+
if self._is_in_loop_context_enhanced(lines, i):
|
|
413
|
+
context_info = self._analyze_string_context(lines, i)
|
|
414
|
+
string_concat_patterns.append(
|
|
415
|
+
{
|
|
416
|
+
"line_number": i + 1,
|
|
417
|
+
"content": stripped,
|
|
418
|
+
"context": context_info,
|
|
419
|
+
"estimated_impact": int(
|
|
420
|
+
context_info.get("impact_factor", "1")
|
|
421
|
+
),
|
|
422
|
+
}
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
if ".join([])" in stripped:
|
|
426
|
+
inefficient_joins.append(
|
|
427
|
+
{
|
|
428
|
+
"line_number": i + 1,
|
|
429
|
+
"content": stripped,
|
|
430
|
+
"optimization": "Use empty string literal instead",
|
|
431
|
+
"performance_gain": "2x",
|
|
432
|
+
}
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
if any(pattern in stripped for pattern in ('f"', ".format(", "% ")):
|
|
436
|
+
if self._is_in_loop_context_enhanced(lines, i):
|
|
437
|
+
repeated_format_calls.append(
|
|
438
|
+
{
|
|
439
|
+
"line_number": i + 1,
|
|
440
|
+
"content": stripped,
|
|
441
|
+
"optimization": "Move formatting outside loop if static",
|
|
442
|
+
}
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
total_issues = (
|
|
446
|
+
len(string_concat_patterns)
|
|
447
|
+
+ len(inefficient_joins)
|
|
448
|
+
+ len(repeated_format_calls)
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
if total_issues > 0:
|
|
452
|
+
issues.append(
|
|
453
|
+
{
|
|
454
|
+
"type": "string_inefficiencies_enhanced",
|
|
455
|
+
"string_concat_patterns": string_concat_patterns,
|
|
456
|
+
"inefficient_joins": inefficient_joins,
|
|
457
|
+
"repeated_formatting": repeated_format_calls,
|
|
458
|
+
"total_count": total_issues,
|
|
459
|
+
"suggestion": self._generate_string_suggestions(
|
|
460
|
+
string_concat_patterns, inefficient_joins, repeated_format_calls
|
|
461
|
+
),
|
|
462
|
+
}
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
return issues
|
|
466
|
+
|
|
467
|
+
def _analyze_string_context(
|
|
468
|
+
self, lines: list[str], line_idx: int
|
|
469
|
+
) -> dict[str, t.Any]:
|
|
470
|
+
"""Analyze string building context.
|
|
471
|
+
|
|
472
|
+
Args:
|
|
473
|
+
lines: File lines
|
|
474
|
+
line_idx: Line index
|
|
475
|
+
|
|
476
|
+
Returns:
|
|
477
|
+
Context information
|
|
478
|
+
"""
|
|
479
|
+
context = self._create_default_string_context()
|
|
480
|
+
loop_context = self._find_loop_context_in_lines(lines, line_idx)
|
|
481
|
+
|
|
482
|
+
if loop_context:
|
|
483
|
+
context.update(loop_context)
|
|
484
|
+
|
|
485
|
+
return context
|
|
486
|
+
|
|
487
|
+
@staticmethod
|
|
488
|
+
def _create_default_string_context() -> dict[str, t.Any]:
|
|
489
|
+
"""Create default string context.
|
|
490
|
+
|
|
491
|
+
Returns:
|
|
492
|
+
Default context dict
|
|
493
|
+
"""
|
|
494
|
+
return {
|
|
495
|
+
"loop_type": "unknown",
|
|
496
|
+
"loop_depth": 1,
|
|
497
|
+
"impact_factor": "1",
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
def _find_loop_context_in_lines(
|
|
501
|
+
self, lines: list[str], line_idx: int
|
|
502
|
+
) -> dict[str, t.Any] | None:
|
|
503
|
+
"""Find loop context for a line.
|
|
504
|
+
|
|
505
|
+
Args:
|
|
506
|
+
lines: File lines
|
|
507
|
+
line_idx: Line index
|
|
508
|
+
|
|
509
|
+
Returns:
|
|
510
|
+
Loop context or None
|
|
511
|
+
"""
|
|
512
|
+
for i in range(max(0, line_idx - 10), line_idx):
|
|
513
|
+
line = lines[i].strip()
|
|
514
|
+
loop_context = self._analyze_single_line_for_loop_context(line)
|
|
515
|
+
if loop_context:
|
|
516
|
+
return loop_context
|
|
517
|
+
return None
|
|
518
|
+
|
|
519
|
+
def _analyze_single_line_for_loop_context(
|
|
520
|
+
self, line: str
|
|
521
|
+
) -> dict[str, t.Any] | None:
|
|
522
|
+
"""Analyze a single line for loop context.
|
|
523
|
+
|
|
524
|
+
Args:
|
|
525
|
+
line: Code line
|
|
526
|
+
|
|
527
|
+
Returns:
|
|
528
|
+
Loop context or None
|
|
529
|
+
"""
|
|
530
|
+
if "for " in line and " in " in line:
|
|
531
|
+
return self._analyze_for_loop_context(line)
|
|
532
|
+
elif "while " in line:
|
|
533
|
+
return self._analyze_while_loop_context()
|
|
534
|
+
return None
|
|
535
|
+
|
|
536
|
+
def _analyze_for_loop_context(self, line: str) -> dict[str, t.Any]:
|
|
537
|
+
"""Analyze for loop for impact factor.
|
|
538
|
+
|
|
539
|
+
Args:
|
|
540
|
+
line: For loop line
|
|
541
|
+
|
|
542
|
+
Returns:
|
|
543
|
+
Context dict
|
|
544
|
+
"""
|
|
545
|
+
context = {"loop_type": "for"}
|
|
546
|
+
|
|
547
|
+
if "range(" in line:
|
|
548
|
+
impact_factor = self._estimate_range_impact_factor(line)
|
|
549
|
+
context["impact_factor"] = str(impact_factor)
|
|
550
|
+
else:
|
|
551
|
+
context["impact_factor"] = "2"
|
|
552
|
+
|
|
553
|
+
return context
|
|
554
|
+
|
|
555
|
+
@staticmethod
|
|
556
|
+
def _analyze_while_loop_context() -> dict[str, t.Any]:
|
|
557
|
+
"""Analyze while loop context.
|
|
558
|
+
|
|
559
|
+
Returns:
|
|
560
|
+
Context dict
|
|
561
|
+
"""
|
|
562
|
+
return {
|
|
563
|
+
"loop_type": "while",
|
|
564
|
+
"impact_factor": "3",
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
def _estimate_range_impact_factor(self, line: str) -> int:
|
|
568
|
+
"""Estimate impact factor for range size.
|
|
569
|
+
|
|
570
|
+
Args:
|
|
571
|
+
line: Code line
|
|
572
|
+
|
|
573
|
+
Returns:
|
|
574
|
+
Impact factor
|
|
575
|
+
"""
|
|
576
|
+
try:
|
|
577
|
+
pattern_obj = SAFE_PATTERNS["extract_range_size"]
|
|
578
|
+
if not pattern_obj.test(line):
|
|
579
|
+
return 2
|
|
580
|
+
|
|
581
|
+
range_str = pattern_obj.apply(line)
|
|
582
|
+
range_size = self._extract_range_size_from_string(range_str)
|
|
583
|
+
|
|
584
|
+
return self._calculate_impact_from_range_size(range_size)
|
|
585
|
+
except (ValueError, AttributeError):
|
|
586
|
+
return 2
|
|
587
|
+
|
|
588
|
+
@staticmethod
|
|
589
|
+
def _extract_range_size_from_string(range_str: str) -> int:
|
|
590
|
+
"""Extract range size from string.
|
|
591
|
+
|
|
592
|
+
Args:
|
|
593
|
+
range_str: Range string
|
|
594
|
+
|
|
595
|
+
Returns:
|
|
596
|
+
Range size
|
|
597
|
+
"""
|
|
598
|
+
import re
|
|
599
|
+
|
|
600
|
+
number_match = re.search(r"\d+", range_str)
|
|
601
|
+
if number_match:
|
|
602
|
+
return int(number_match.group())
|
|
603
|
+
return 0
|
|
604
|
+
|
|
605
|
+
@staticmethod
|
|
606
|
+
def _calculate_impact_from_range_size(range_size: int) -> int:
|
|
607
|
+
"""Calculate impact factor from range size.
|
|
608
|
+
|
|
609
|
+
Args:
|
|
610
|
+
range_size: Size of range
|
|
611
|
+
|
|
612
|
+
Returns:
|
|
613
|
+
Impact factor
|
|
614
|
+
"""
|
|
615
|
+
if range_size > 1000:
|
|
616
|
+
return 10
|
|
617
|
+
elif range_size > 100:
|
|
618
|
+
return 5
|
|
619
|
+
return 2
|
|
620
|
+
|
|
621
|
+
@staticmethod
|
|
622
|
+
def _is_in_loop_context_enhanced(lines: list[str], line_index: int) -> bool:
|
|
623
|
+
"""Check if line is in enhanced loop context.
|
|
624
|
+
|
|
625
|
+
Args:
|
|
626
|
+
lines: File lines
|
|
627
|
+
line_index: Line index
|
|
628
|
+
|
|
629
|
+
Returns:
|
|
630
|
+
True if in loop
|
|
631
|
+
"""
|
|
632
|
+
context_start = max(0, line_index - 8)
|
|
633
|
+
context_lines = lines[context_start : line_index + 1]
|
|
634
|
+
|
|
635
|
+
for ctx_line in context_lines:
|
|
636
|
+
pattern_obj = SAFE_PATTERNS["match_loop_patterns"]
|
|
637
|
+
if pattern_obj.test(ctx_line):
|
|
638
|
+
return True
|
|
639
|
+
|
|
640
|
+
return False
|
|
641
|
+
|
|
642
|
+
@staticmethod
|
|
643
|
+
def _generate_string_suggestions(
|
|
644
|
+
concat_patterns: list[dict[str, t.Any]],
|
|
645
|
+
inefficient_joins: list[dict[str, t.Any]],
|
|
646
|
+
repeated_formatting: list[dict[str, t.Any]],
|
|
647
|
+
) -> str:
|
|
648
|
+
"""Generate string optimization suggestions.
|
|
649
|
+
|
|
650
|
+
Args:
|
|
651
|
+
concat_patterns: String concatenation patterns
|
|
652
|
+
inefficient_joins: Inefficient join patterns
|
|
653
|
+
repeated_formatting: Repeated formatting patterns
|
|
654
|
+
|
|
655
|
+
Returns:
|
|
656
|
+
Suggestion string
|
|
657
|
+
"""
|
|
658
|
+
suggestions = []
|
|
659
|
+
|
|
660
|
+
if concat_patterns:
|
|
661
|
+
high_impact = len(
|
|
662
|
+
[p for p in concat_patterns if p.get("estimated_impact", 1) >= 5]
|
|
663
|
+
)
|
|
664
|
+
suggestions.append(
|
|
665
|
+
f"String concatenation: {len(concat_patterns)} instances "
|
|
666
|
+
f"({high_impact} high-impact) - use list[t.Any].append + join"
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
if inefficient_joins:
|
|
670
|
+
suggestions.append(
|
|
671
|
+
f"Empty joins: {len(inefficient_joins)} - use empty string literal"
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
if repeated_formatting:
|
|
675
|
+
suggestions.append(
|
|
676
|
+
f"Repeated formatting: {len(repeated_formatting)} - cache format strings"
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
suggestions.append("Expected gains: 3-50x for string building in loops")
|
|
680
|
+
return "; ".join(suggestions)
|
|
681
|
+
|
|
682
|
+
def _detect_list_comprehension_opportunities(
|
|
683
|
+
self, tree: ast.AST
|
|
684
|
+
) -> list[dict[str, t.Any]]:
|
|
685
|
+
"""Detect opportunities to use list comprehensions.
|
|
686
|
+
|
|
687
|
+
Args:
|
|
688
|
+
tree: AST tree
|
|
689
|
+
|
|
690
|
+
Returns:
|
|
691
|
+
List of comprehension opportunities
|
|
692
|
+
"""
|
|
693
|
+
issues: list[dict[str, t.Any]] = []
|
|
694
|
+
|
|
695
|
+
class ComprehensionAnalyzer(ast.NodeVisitor):
|
|
696
|
+
def __init__(self) -> None:
|
|
697
|
+
self.opportunities: list[dict[str, t.Any]] = []
|
|
698
|
+
|
|
699
|
+
def visit_For(self, node: ast.For) -> None:
|
|
700
|
+
if (
|
|
701
|
+
len(node.body) == 1
|
|
702
|
+
and isinstance(node.body[0], ast.Expr)
|
|
703
|
+
and isinstance(
|
|
704
|
+
node.body[0].value,
|
|
705
|
+
ast.Call,
|
|
706
|
+
)
|
|
707
|
+
and isinstance(
|
|
708
|
+
node.body[0].value.func,
|
|
709
|
+
ast.Attribute,
|
|
710
|
+
)
|
|
711
|
+
and node.body[0].value.func.attr == "append"
|
|
712
|
+
):
|
|
713
|
+
self.opportunities.append(
|
|
714
|
+
{
|
|
715
|
+
"line_number": node.lineno,
|
|
716
|
+
"type": "append_loop_to_comprehension",
|
|
717
|
+
"optimization": "list_comprehension",
|
|
718
|
+
"performance_gain": "20-30% faster",
|
|
719
|
+
"readability": "improved",
|
|
720
|
+
}
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
self.generic_visit(node)
|
|
724
|
+
|
|
725
|
+
analyzer = ComprehensionAnalyzer()
|
|
726
|
+
analyzer.visit(tree)
|
|
727
|
+
|
|
728
|
+
if analyzer.opportunities:
|
|
729
|
+
issues.append(
|
|
730
|
+
{
|
|
731
|
+
"type": "list_comprehension_opportunities",
|
|
732
|
+
"instances": analyzer.opportunities,
|
|
733
|
+
"total_count": len(analyzer.opportunities),
|
|
734
|
+
"suggestion": f"Convert {len(analyzer.opportunities)} append loops"
|
|
735
|
+
f" to list[t.Any] comprehensions for better performance "
|
|
736
|
+
f"and readability",
|
|
737
|
+
}
|
|
738
|
+
)
|
|
739
|
+
|
|
740
|
+
return issues
|
|
741
|
+
|
|
742
|
+
def _detect_inefficient_builtin_usage(
|
|
743
|
+
self, tree: ast.AST, content: str
|
|
744
|
+
) -> list[dict[str, t.Any]]:
|
|
745
|
+
"""Detect inefficient builtin function usage in loops.
|
|
746
|
+
|
|
747
|
+
Args:
|
|
748
|
+
tree: AST tree
|
|
749
|
+
content: File content
|
|
750
|
+
|
|
751
|
+
Returns:
|
|
752
|
+
List of inefficient builtin issues
|
|
753
|
+
"""
|
|
754
|
+
issues: list[dict[str, t.Any]] = []
|
|
755
|
+
|
|
756
|
+
class BuiltinAnalyzer(ast.NodeVisitor):
|
|
757
|
+
def __init__(self) -> None:
|
|
758
|
+
self.inefficient_calls: list[dict[str, t.Any]] = []
|
|
759
|
+
self.in_loop = False
|
|
760
|
+
|
|
761
|
+
def visit_For(self, node: ast.For) -> None:
|
|
762
|
+
old_in_loop = self.in_loop
|
|
763
|
+
self.in_loop = True
|
|
764
|
+
self.generic_visit(node)
|
|
765
|
+
self.in_loop = old_in_loop
|
|
766
|
+
|
|
767
|
+
def visit_While(self, node: ast.While) -> None:
|
|
768
|
+
old_in_loop = self.in_loop
|
|
769
|
+
self.in_loop = True
|
|
770
|
+
self.generic_visit(node)
|
|
771
|
+
self.in_loop = old_in_loop
|
|
772
|
+
|
|
773
|
+
def visit_Call(self, node: ast.Call) -> None:
|
|
774
|
+
if self.in_loop and isinstance(node.func, ast.Name):
|
|
775
|
+
func_name = node.func.id
|
|
776
|
+
|
|
777
|
+
if func_name in ("len", "sum", "max", "min", "sorted"):
|
|
778
|
+
if node.args and isinstance(node.args[0], ast.Name):
|
|
779
|
+
self.inefficient_calls.append(
|
|
780
|
+
{
|
|
781
|
+
"line_number": node.lineno,
|
|
782
|
+
"function": func_name,
|
|
783
|
+
"type": "repeated_builtin_in_loop",
|
|
784
|
+
"optimization": f"Cache {func_name}() "
|
|
785
|
+
f"result outside loop",
|
|
786
|
+
"performance_gain": "2-10x depending on data size",
|
|
787
|
+
}
|
|
788
|
+
)
|
|
789
|
+
|
|
790
|
+
self.generic_visit(node)
|
|
791
|
+
|
|
792
|
+
analyzer = BuiltinAnalyzer()
|
|
793
|
+
analyzer.visit(tree)
|
|
794
|
+
|
|
795
|
+
if analyzer.inefficient_calls:
|
|
796
|
+
issues.append(
|
|
797
|
+
{
|
|
798
|
+
"type": "inefficient_builtin_usage",
|
|
799
|
+
"instances": analyzer.inefficient_calls,
|
|
800
|
+
"total_count": len(analyzer.inefficient_calls),
|
|
801
|
+
"suggestion": f"Cache {len(analyzer.inefficient_calls)} "
|
|
802
|
+
f"repeated builtin calls outside loops",
|
|
803
|
+
}
|
|
804
|
+
)
|
|
805
|
+
|
|
806
|
+
return issues
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
class NestedLoopAnalyzer(ast.NodeVisitor):
|
|
810
|
+
"""AST visitor for detecting nested loops."""
|
|
811
|
+
|
|
812
|
+
def __init__(self) -> None:
|
|
813
|
+
"""Initialize analyzer."""
|
|
814
|
+
self.nested_loops: list[dict[str, t.Any]] = []
|
|
815
|
+
self.complexity_hotspots: list[dict[str, t.Any]] = []
|
|
816
|
+
self._loop_stack: list[tuple[int, str]] = []
|
|
817
|
+
|
|
818
|
+
def visit_For(self, node: ast.For) -> None:
|
|
819
|
+
"""Visit for loop node."""
|
|
820
|
+
self._handle_loop_node(node, "for")
|
|
821
|
+
self.generic_visit(node)
|
|
822
|
+
|
|
823
|
+
def visit_While(self, node: ast.While) -> None:
|
|
824
|
+
"""Visit while loop node."""
|
|
825
|
+
self._handle_loop_node(node, "while")
|
|
826
|
+
self.generic_visit(node)
|
|
827
|
+
|
|
828
|
+
def _handle_loop_node(self, node: ast.For | ast.While, loop_type: str) -> None:
|
|
829
|
+
"""Handle loop node detection."""
|
|
830
|
+
self._loop_stack.append((node.lineno, loop_type))
|
|
831
|
+
|
|
832
|
+
if len(self._loop_stack) > 1:
|
|
833
|
+
depth = len(self._loop_stack)
|
|
834
|
+
complexity = self._calculate_loop_complexity(depth)
|
|
835
|
+
priority = self._determine_priority(depth)
|
|
836
|
+
|
|
837
|
+
self.nested_loops.append(
|
|
838
|
+
{
|
|
839
|
+
"line_number": node.lineno,
|
|
840
|
+
"depth": depth,
|
|
841
|
+
"complexity": complexity,
|
|
842
|
+
"priority": priority,
|
|
843
|
+
"type": loop_type,
|
|
844
|
+
}
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
self._loop_stack.pop()
|
|
848
|
+
|
|
849
|
+
@staticmethod
|
|
850
|
+
def _calculate_loop_complexity(depth: int) -> str:
|
|
851
|
+
"""Calculate complexity string from depth."""
|
|
852
|
+
if depth == 2:
|
|
853
|
+
return "O(n²)"
|
|
854
|
+
elif depth == 3:
|
|
855
|
+
return "O(n³)"
|
|
856
|
+
elif depth >= 4:
|
|
857
|
+
return "O(n⁴+)"
|
|
858
|
+
return "O(n)"
|
|
859
|
+
|
|
860
|
+
@staticmethod
|
|
861
|
+
def _determine_priority(depth: int) -> str:
|
|
862
|
+
"""Determine priority from depth."""
|
|
863
|
+
if depth >= 4:
|
|
864
|
+
return "critical"
|
|
865
|
+
elif depth == 3:
|
|
866
|
+
return "high"
|
|
867
|
+
return "medium"
|
|
868
|
+
|
|
869
|
+
|
|
870
|
+
class ListOpAnalyzer(ast.NodeVisitor):
|
|
871
|
+
"""AST visitor for detecting inefficient list operations."""
|
|
872
|
+
|
|
873
|
+
def __init__(self) -> None:
|
|
874
|
+
"""Initialize analyzer."""
|
|
875
|
+
self.list_ops: list[dict[str, t.Any]] = []
|
|
876
|
+
self._in_loop = False
|
|
877
|
+
|
|
878
|
+
def visit_For(self, node: ast.For) -> None:
|
|
879
|
+
"""Visit for loop."""
|
|
880
|
+
old_in_loop = self._in_loop
|
|
881
|
+
self._in_loop = True
|
|
882
|
+
self.generic_visit(node)
|
|
883
|
+
self._in_loop = old_in_loop
|
|
884
|
+
|
|
885
|
+
def visit_While(self, node: ast.While) -> None:
|
|
886
|
+
"""Visit while loop."""
|
|
887
|
+
old_in_loop = self._in_loop
|
|
888
|
+
self._in_loop = True
|
|
889
|
+
self.generic_visit(node)
|
|
890
|
+
self._in_loop = old_in_loop
|
|
891
|
+
|
|
892
|
+
def visit_AugAssign(self, node: ast.AugAssign) -> None:
|
|
893
|
+
"""Visit augmented assignment."""
|
|
894
|
+
if self._in_loop and isinstance(node.op, operator.Add):
|
|
895
|
+
if isinstance(node.value, ast.List):
|
|
896
|
+
impact = len(node.value.elts) if node.value.elts else 1
|
|
897
|
+
self.list_ops.append(
|
|
898
|
+
{
|
|
899
|
+
"line_number": node.lineno,
|
|
900
|
+
"type": "inefficient_list_concat",
|
|
901
|
+
"optimization": "append"
|
|
902
|
+
if len(node.value.elts) == 1
|
|
903
|
+
else "extend",
|
|
904
|
+
"impact_factor": impact,
|
|
905
|
+
"performance_gain": f"{max(2, min(50, impact * 5))}x",
|
|
906
|
+
}
|
|
907
|
+
)
|
|
908
|
+
|
|
909
|
+
self.generic_visit(node)
|