tree-sitter-analyzer 1.9.17.1__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.
- tree_sitter_analyzer/__init__.py +132 -0
- tree_sitter_analyzer/__main__.py +11 -0
- tree_sitter_analyzer/api.py +853 -0
- tree_sitter_analyzer/cli/__init__.py +39 -0
- tree_sitter_analyzer/cli/__main__.py +12 -0
- tree_sitter_analyzer/cli/argument_validator.py +89 -0
- tree_sitter_analyzer/cli/commands/__init__.py +26 -0
- tree_sitter_analyzer/cli/commands/advanced_command.py +226 -0
- tree_sitter_analyzer/cli/commands/base_command.py +181 -0
- tree_sitter_analyzer/cli/commands/default_command.py +18 -0
- tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +188 -0
- tree_sitter_analyzer/cli/commands/list_files_cli.py +133 -0
- tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -0
- tree_sitter_analyzer/cli/commands/query_command.py +109 -0
- tree_sitter_analyzer/cli/commands/search_content_cli.py +161 -0
- tree_sitter_analyzer/cli/commands/structure_command.py +156 -0
- tree_sitter_analyzer/cli/commands/summary_command.py +116 -0
- tree_sitter_analyzer/cli/commands/table_command.py +414 -0
- tree_sitter_analyzer/cli/info_commands.py +124 -0
- tree_sitter_analyzer/cli_main.py +472 -0
- tree_sitter_analyzer/constants.py +85 -0
- tree_sitter_analyzer/core/__init__.py +15 -0
- tree_sitter_analyzer/core/analysis_engine.py +580 -0
- tree_sitter_analyzer/core/cache_service.py +333 -0
- tree_sitter_analyzer/core/engine.py +585 -0
- tree_sitter_analyzer/core/parser.py +293 -0
- tree_sitter_analyzer/core/query.py +605 -0
- tree_sitter_analyzer/core/query_filter.py +200 -0
- tree_sitter_analyzer/core/query_service.py +340 -0
- tree_sitter_analyzer/encoding_utils.py +530 -0
- tree_sitter_analyzer/exceptions.py +747 -0
- tree_sitter_analyzer/file_handler.py +246 -0
- tree_sitter_analyzer/formatters/__init__.py +1 -0
- tree_sitter_analyzer/formatters/base_formatter.py +201 -0
- tree_sitter_analyzer/formatters/csharp_formatter.py +367 -0
- tree_sitter_analyzer/formatters/formatter_config.py +197 -0
- tree_sitter_analyzer/formatters/formatter_factory.py +84 -0
- tree_sitter_analyzer/formatters/formatter_registry.py +377 -0
- tree_sitter_analyzer/formatters/formatter_selector.py +96 -0
- tree_sitter_analyzer/formatters/go_formatter.py +368 -0
- tree_sitter_analyzer/formatters/html_formatter.py +498 -0
- tree_sitter_analyzer/formatters/java_formatter.py +423 -0
- tree_sitter_analyzer/formatters/javascript_formatter.py +611 -0
- tree_sitter_analyzer/formatters/kotlin_formatter.py +268 -0
- tree_sitter_analyzer/formatters/language_formatter_factory.py +123 -0
- tree_sitter_analyzer/formatters/legacy_formatter_adapters.py +228 -0
- tree_sitter_analyzer/formatters/markdown_formatter.py +725 -0
- tree_sitter_analyzer/formatters/php_formatter.py +301 -0
- tree_sitter_analyzer/formatters/python_formatter.py +830 -0
- tree_sitter_analyzer/formatters/ruby_formatter.py +278 -0
- tree_sitter_analyzer/formatters/rust_formatter.py +233 -0
- tree_sitter_analyzer/formatters/sql_formatter_wrapper.py +689 -0
- tree_sitter_analyzer/formatters/sql_formatters.py +536 -0
- tree_sitter_analyzer/formatters/typescript_formatter.py +543 -0
- tree_sitter_analyzer/formatters/yaml_formatter.py +462 -0
- tree_sitter_analyzer/interfaces/__init__.py +9 -0
- tree_sitter_analyzer/interfaces/cli.py +535 -0
- tree_sitter_analyzer/interfaces/cli_adapter.py +359 -0
- tree_sitter_analyzer/interfaces/mcp_adapter.py +224 -0
- tree_sitter_analyzer/interfaces/mcp_server.py +428 -0
- tree_sitter_analyzer/language_detector.py +553 -0
- tree_sitter_analyzer/language_loader.py +271 -0
- tree_sitter_analyzer/languages/__init__.py +10 -0
- tree_sitter_analyzer/languages/csharp_plugin.py +1076 -0
- tree_sitter_analyzer/languages/css_plugin.py +449 -0
- tree_sitter_analyzer/languages/go_plugin.py +836 -0
- tree_sitter_analyzer/languages/html_plugin.py +496 -0
- tree_sitter_analyzer/languages/java_plugin.py +1299 -0
- tree_sitter_analyzer/languages/javascript_plugin.py +1622 -0
- tree_sitter_analyzer/languages/kotlin_plugin.py +656 -0
- tree_sitter_analyzer/languages/markdown_plugin.py +1928 -0
- tree_sitter_analyzer/languages/php_plugin.py +862 -0
- tree_sitter_analyzer/languages/python_plugin.py +1636 -0
- tree_sitter_analyzer/languages/ruby_plugin.py +757 -0
- tree_sitter_analyzer/languages/rust_plugin.py +673 -0
- tree_sitter_analyzer/languages/sql_plugin.py +2444 -0
- tree_sitter_analyzer/languages/typescript_plugin.py +1892 -0
- tree_sitter_analyzer/languages/yaml_plugin.py +695 -0
- tree_sitter_analyzer/legacy_table_formatter.py +860 -0
- tree_sitter_analyzer/mcp/__init__.py +34 -0
- tree_sitter_analyzer/mcp/resources/__init__.py +43 -0
- tree_sitter_analyzer/mcp/resources/code_file_resource.py +208 -0
- tree_sitter_analyzer/mcp/resources/project_stats_resource.py +586 -0
- tree_sitter_analyzer/mcp/server.py +869 -0
- tree_sitter_analyzer/mcp/tools/__init__.py +28 -0
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +779 -0
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +291 -0
- tree_sitter_analyzer/mcp/tools/base_tool.py +139 -0
- tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +816 -0
- tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +686 -0
- tree_sitter_analyzer/mcp/tools/list_files_tool.py +413 -0
- tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
- tree_sitter_analyzer/mcp/tools/query_tool.py +443 -0
- tree_sitter_analyzer/mcp/tools/read_partial_tool.py +464 -0
- tree_sitter_analyzer/mcp/tools/search_content_tool.py +836 -0
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +572 -0
- tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +653 -0
- tree_sitter_analyzer/mcp/utils/__init__.py +113 -0
- tree_sitter_analyzer/mcp/utils/error_handler.py +569 -0
- tree_sitter_analyzer/mcp/utils/file_output_factory.py +217 -0
- tree_sitter_analyzer/mcp/utils/file_output_manager.py +322 -0
- tree_sitter_analyzer/mcp/utils/gitignore_detector.py +358 -0
- tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -0
- tree_sitter_analyzer/mcp/utils/search_cache.py +343 -0
- tree_sitter_analyzer/models.py +840 -0
- tree_sitter_analyzer/mypy_current_errors.txt +2 -0
- tree_sitter_analyzer/output_manager.py +255 -0
- tree_sitter_analyzer/platform_compat/__init__.py +3 -0
- tree_sitter_analyzer/platform_compat/adapter.py +324 -0
- tree_sitter_analyzer/platform_compat/compare.py +224 -0
- tree_sitter_analyzer/platform_compat/detector.py +67 -0
- tree_sitter_analyzer/platform_compat/fixtures.py +228 -0
- tree_sitter_analyzer/platform_compat/profiles.py +217 -0
- tree_sitter_analyzer/platform_compat/record.py +55 -0
- tree_sitter_analyzer/platform_compat/recorder.py +155 -0
- tree_sitter_analyzer/platform_compat/report.py +92 -0
- tree_sitter_analyzer/plugins/__init__.py +280 -0
- tree_sitter_analyzer/plugins/base.py +647 -0
- tree_sitter_analyzer/plugins/manager.py +384 -0
- tree_sitter_analyzer/project_detector.py +328 -0
- tree_sitter_analyzer/queries/__init__.py +27 -0
- tree_sitter_analyzer/queries/csharp.py +216 -0
- tree_sitter_analyzer/queries/css.py +615 -0
- tree_sitter_analyzer/queries/go.py +275 -0
- tree_sitter_analyzer/queries/html.py +543 -0
- tree_sitter_analyzer/queries/java.py +402 -0
- tree_sitter_analyzer/queries/javascript.py +724 -0
- tree_sitter_analyzer/queries/kotlin.py +192 -0
- tree_sitter_analyzer/queries/markdown.py +258 -0
- tree_sitter_analyzer/queries/php.py +95 -0
- tree_sitter_analyzer/queries/python.py +859 -0
- tree_sitter_analyzer/queries/ruby.py +92 -0
- tree_sitter_analyzer/queries/rust.py +223 -0
- tree_sitter_analyzer/queries/sql.py +555 -0
- tree_sitter_analyzer/queries/typescript.py +871 -0
- tree_sitter_analyzer/queries/yaml.py +236 -0
- tree_sitter_analyzer/query_loader.py +272 -0
- tree_sitter_analyzer/security/__init__.py +22 -0
- tree_sitter_analyzer/security/boundary_manager.py +277 -0
- tree_sitter_analyzer/security/regex_checker.py +297 -0
- tree_sitter_analyzer/security/validator.py +599 -0
- tree_sitter_analyzer/table_formatter.py +782 -0
- tree_sitter_analyzer/utils/__init__.py +53 -0
- tree_sitter_analyzer/utils/logging.py +433 -0
- tree_sitter_analyzer/utils/tree_sitter_compat.py +289 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/METADATA +485 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/RECORD +149 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/WHEEL +4 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/entry_points.txt +25 -0
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Unified Analysis Engine - Common Analysis System for CLI and MCP (Fixed Version)
|
|
4
|
+
|
|
5
|
+
This module provides a unified engine that serves as the center of all analysis processing.
|
|
6
|
+
It is commonly used by CLI, MCP, and other interfaces.
|
|
7
|
+
|
|
8
|
+
Roo Code compliance:
|
|
9
|
+
- Type hints: Required for all functions
|
|
10
|
+
- MCP logging: Log output at each step
|
|
11
|
+
- docstring: Google Style docstring
|
|
12
|
+
- Performance-focused: Singleton pattern and cache sharing
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import hashlib
|
|
16
|
+
import threading
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from typing import Any, Optional, Protocol
|
|
19
|
+
|
|
20
|
+
from ..models import AnalysisResult
|
|
21
|
+
from ..plugins.base import LanguagePlugin as BaseLanguagePlugin
|
|
22
|
+
from ..plugins.manager import PluginManager
|
|
23
|
+
from ..security import SecurityValidator
|
|
24
|
+
from ..utils import log_debug, log_error, log_info, log_performance
|
|
25
|
+
from .cache_service import CacheService
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class UnsupportedLanguageError(Exception):
|
|
29
|
+
"""Unsupported language error"""
|
|
30
|
+
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class PluginRegistry(Protocol):
|
|
35
|
+
"""Protocol for plugin registration management"""
|
|
36
|
+
|
|
37
|
+
def get_plugin(self, language: str) -> Optional["LanguagePlugin"]:
|
|
38
|
+
"""Get language plugin"""
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class LanguagePlugin(Protocol):
|
|
43
|
+
"""Language plugin protocol"""
|
|
44
|
+
|
|
45
|
+
async def analyze_file(
|
|
46
|
+
self, file_path: str, request: "AnalysisRequest"
|
|
47
|
+
) -> AnalysisResult:
|
|
48
|
+
"""File analysis"""
|
|
49
|
+
...
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class PerformanceMonitor:
|
|
53
|
+
"""Performance monitoring (simplified version)"""
|
|
54
|
+
|
|
55
|
+
def __init__(self) -> None:
|
|
56
|
+
self._last_duration: float = 0.0
|
|
57
|
+
self._monitoring_active: bool = False
|
|
58
|
+
self._operation_stats: dict[str, Any] = {}
|
|
59
|
+
self._total_operations: int = 0
|
|
60
|
+
|
|
61
|
+
def measure_operation(self, operation_name: str) -> "PerformanceContext":
|
|
62
|
+
"""Return measurement context for operation"""
|
|
63
|
+
return PerformanceContext(operation_name, self)
|
|
64
|
+
|
|
65
|
+
def get_last_duration(self) -> float:
|
|
66
|
+
"""Get last operation time"""
|
|
67
|
+
return self._last_duration
|
|
68
|
+
|
|
69
|
+
def _set_duration(self, duration: float) -> None:
|
|
70
|
+
"""Set operation time (internal use)"""
|
|
71
|
+
self._last_duration = duration
|
|
72
|
+
|
|
73
|
+
def start_monitoring(self) -> None:
|
|
74
|
+
"""Start performance monitoring"""
|
|
75
|
+
self._monitoring_active = True
|
|
76
|
+
log_info("Performance monitoring started")
|
|
77
|
+
|
|
78
|
+
def stop_monitoring(self) -> None:
|
|
79
|
+
"""Stop performance monitoring"""
|
|
80
|
+
self._monitoring_active = False
|
|
81
|
+
log_info("Performance monitoring stopped")
|
|
82
|
+
|
|
83
|
+
def get_operation_stats(self) -> dict[str, Any]:
|
|
84
|
+
"""Get operation statistics"""
|
|
85
|
+
return self._operation_stats.copy()
|
|
86
|
+
|
|
87
|
+
def get_performance_summary(self) -> dict[str, Any]:
|
|
88
|
+
"""Get performance summary"""
|
|
89
|
+
return {
|
|
90
|
+
"total_operations": self._total_operations,
|
|
91
|
+
"monitoring_active": self._monitoring_active,
|
|
92
|
+
"last_duration": self._last_duration,
|
|
93
|
+
"operation_count": len(self._operation_stats),
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
def record_operation(self, operation_name: str, duration: float) -> None:
|
|
97
|
+
"""Record operation"""
|
|
98
|
+
if self._monitoring_active:
|
|
99
|
+
if operation_name not in self._operation_stats:
|
|
100
|
+
self._operation_stats[operation_name] = {
|
|
101
|
+
"count": 0,
|
|
102
|
+
"total_time": 0.0,
|
|
103
|
+
"avg_time": 0.0,
|
|
104
|
+
"min_time": float("inf"),
|
|
105
|
+
"max_time": 0.0,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
stats = self._operation_stats[operation_name]
|
|
109
|
+
stats["count"] += 1
|
|
110
|
+
stats["total_time"] += duration
|
|
111
|
+
stats["avg_time"] = stats["total_time"] / stats["count"]
|
|
112
|
+
stats["min_time"] = min(stats["min_time"], duration)
|
|
113
|
+
stats["max_time"] = max(stats["max_time"], duration)
|
|
114
|
+
|
|
115
|
+
self._total_operations += 1
|
|
116
|
+
|
|
117
|
+
def clear_metrics(self) -> None:
|
|
118
|
+
"""Clear collected metrics"""
|
|
119
|
+
self._operation_stats.clear()
|
|
120
|
+
self._total_operations = 0
|
|
121
|
+
self._last_duration = 0.0
|
|
122
|
+
log_info("Performance metrics cleared")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class PerformanceContext:
|
|
126
|
+
"""Performance measurement context"""
|
|
127
|
+
|
|
128
|
+
def __init__(self, operation_name: str, monitor: PerformanceMonitor) -> None:
|
|
129
|
+
self.operation_name = operation_name
|
|
130
|
+
self.monitor = monitor
|
|
131
|
+
self.start_time: float = 0.0
|
|
132
|
+
|
|
133
|
+
def __enter__(self) -> "PerformanceContext":
|
|
134
|
+
import time
|
|
135
|
+
|
|
136
|
+
self.start_time = time.time()
|
|
137
|
+
return self
|
|
138
|
+
|
|
139
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
140
|
+
import time
|
|
141
|
+
|
|
142
|
+
duration = time.time() - self.start_time
|
|
143
|
+
self.monitor._set_duration(duration)
|
|
144
|
+
self.monitor.record_operation(self.operation_name, duration)
|
|
145
|
+
log_performance(self.operation_name, duration, "Operation completed")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dataclass(frozen=True)
|
|
149
|
+
class AnalysisRequest:
|
|
150
|
+
"""
|
|
151
|
+
Analysis request
|
|
152
|
+
|
|
153
|
+
Attributes:
|
|
154
|
+
file_path: Path to target file to analyze
|
|
155
|
+
language: Programming language (auto-detected if None)
|
|
156
|
+
include_complexity: Whether to include complexity metrics
|
|
157
|
+
include_details: Whether to include detailed structure info
|
|
158
|
+
format_type: Output format
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
file_path: str
|
|
162
|
+
language: str | None = None
|
|
163
|
+
include_complexity: bool = True
|
|
164
|
+
include_details: bool = False
|
|
165
|
+
format_type: str = "json"
|
|
166
|
+
|
|
167
|
+
@classmethod
|
|
168
|
+
def from_mcp_arguments(cls, arguments: dict[str, Any]) -> "AnalysisRequest":
|
|
169
|
+
"""
|
|
170
|
+
Create analysis request from MCP tool arguments
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
arguments: MCP argument dictionary
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
AnalysisRequest
|
|
177
|
+
"""
|
|
178
|
+
return cls(
|
|
179
|
+
file_path=arguments.get("file_path", ""),
|
|
180
|
+
language=arguments.get("language"),
|
|
181
|
+
include_complexity=arguments.get("include_complexity", True),
|
|
182
|
+
include_details=arguments.get("include_details", False),
|
|
183
|
+
format_type=arguments.get("format_type", "json"),
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# SimplePluginRegistry removed - now using PluginManager
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class UnifiedAnalysisEngine:
|
|
191
|
+
"""
|
|
192
|
+
Unified analysis engine (revised)
|
|
193
|
+
|
|
194
|
+
Central engine shared by CLI, MCP and other interfaces, implemented as a
|
|
195
|
+
singleton to enable efficient resource usage and cache sharing.
|
|
196
|
+
|
|
197
|
+
Improvements:
|
|
198
|
+
- Fix async issues in destructor
|
|
199
|
+
- Provide explicit cleanup() method
|
|
200
|
+
|
|
201
|
+
Attributes:
|
|
202
|
+
_cache_service: Cache service
|
|
203
|
+
_plugin_manager: Plugin manager
|
|
204
|
+
_performance_monitor: Performance monitor
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
_instances: dict[str, "UnifiedAnalysisEngine"] = {}
|
|
208
|
+
_lock: threading.Lock = threading.Lock()
|
|
209
|
+
|
|
210
|
+
def __new__(cls, project_root: str | None = None) -> "UnifiedAnalysisEngine":
|
|
211
|
+
"""Singleton instance sharing (project_root aware)"""
|
|
212
|
+
# Create a key based on project_root for different instances
|
|
213
|
+
instance_key = project_root or "default"
|
|
214
|
+
|
|
215
|
+
if instance_key not in cls._instances:
|
|
216
|
+
with cls._lock:
|
|
217
|
+
if instance_key not in cls._instances:
|
|
218
|
+
instance = super().__new__(cls)
|
|
219
|
+
cls._instances[instance_key] = instance
|
|
220
|
+
# Mark as not initialized for this instance
|
|
221
|
+
instance._initialized = False
|
|
222
|
+
|
|
223
|
+
return cls._instances[instance_key]
|
|
224
|
+
|
|
225
|
+
def __init__(self, project_root: str | None = None) -> None:
|
|
226
|
+
"""Initialize (executed only once per instance)"""
|
|
227
|
+
if hasattr(self, "_initialized") and getattr(self, "_initialized", False):
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
self._cache_service = CacheService()
|
|
231
|
+
self._plugin_manager = PluginManager()
|
|
232
|
+
self._performance_monitor = PerformanceMonitor()
|
|
233
|
+
self._security_validator = SecurityValidator(project_root)
|
|
234
|
+
self._project_root = project_root
|
|
235
|
+
|
|
236
|
+
# Auto-load plugins
|
|
237
|
+
self._load_plugins()
|
|
238
|
+
|
|
239
|
+
self._initialized = True
|
|
240
|
+
|
|
241
|
+
log_debug(
|
|
242
|
+
f"UnifiedAnalysisEngine initialized with project root: {project_root}"
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
def _load_plugins(self) -> None:
|
|
246
|
+
"""Auto-load available plugins"""
|
|
247
|
+
log_debug("Loading plugins using PluginManager...")
|
|
248
|
+
|
|
249
|
+
try:
|
|
250
|
+
# PluginManagerの自動ロード機能を使用
|
|
251
|
+
loaded_plugins = self._plugin_manager.load_plugins()
|
|
252
|
+
|
|
253
|
+
final_languages = [plugin.get_language_name() for plugin in loaded_plugins]
|
|
254
|
+
log_debug(
|
|
255
|
+
f"Successfully loaded {len(final_languages)} language plugins: {', '.join(final_languages)}"
|
|
256
|
+
)
|
|
257
|
+
except Exception as e:
|
|
258
|
+
log_error(f"Failed to load plugins: {e}")
|
|
259
|
+
import traceback
|
|
260
|
+
|
|
261
|
+
log_error(f"Plugin loading traceback: {traceback.format_exc()}")
|
|
262
|
+
|
|
263
|
+
async def analyze(self, request: AnalysisRequest) -> AnalysisResult:
|
|
264
|
+
"""
|
|
265
|
+
Unified analysis method
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
request: Analysis request
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Analysis result
|
|
272
|
+
|
|
273
|
+
Raises:
|
|
274
|
+
UnsupportedLanguageError: When language is not supported
|
|
275
|
+
FileNotFoundError: When file is not found
|
|
276
|
+
"""
|
|
277
|
+
log_debug(f"Starting analysis for {request.file_path}")
|
|
278
|
+
|
|
279
|
+
# Security validation
|
|
280
|
+
is_valid, error_msg = self._security_validator.validate_file_path(
|
|
281
|
+
request.file_path
|
|
282
|
+
)
|
|
283
|
+
if not is_valid:
|
|
284
|
+
log_error(
|
|
285
|
+
f"Security validation failed for file path: {request.file_path} - {error_msg}"
|
|
286
|
+
)
|
|
287
|
+
raise ValueError(f"Invalid file path: {error_msg}")
|
|
288
|
+
|
|
289
|
+
# Cache check (shared across CLI/MCP)
|
|
290
|
+
cache_key = self._generate_cache_key(request)
|
|
291
|
+
cached_result = await self._cache_service.get(cache_key)
|
|
292
|
+
if cached_result:
|
|
293
|
+
log_info(f"Cache hit for {request.file_path}")
|
|
294
|
+
return cached_result # type: ignore
|
|
295
|
+
|
|
296
|
+
# Language detection
|
|
297
|
+
language = request.language or self._detect_language(request.file_path)
|
|
298
|
+
log_debug(f"Detected language: {language}")
|
|
299
|
+
|
|
300
|
+
# Debug: inspect registered plugins
|
|
301
|
+
supported_languages = self._plugin_manager.get_supported_languages()
|
|
302
|
+
log_debug(f"Supported languages: {supported_languages}")
|
|
303
|
+
log_debug(f"Looking for plugin for language: {language}")
|
|
304
|
+
|
|
305
|
+
# Get plugin
|
|
306
|
+
plugin = self._plugin_manager.get_plugin(language)
|
|
307
|
+
if not plugin:
|
|
308
|
+
error_msg = f"Language {language} not supported"
|
|
309
|
+
log_error(error_msg)
|
|
310
|
+
raise UnsupportedLanguageError(error_msg)
|
|
311
|
+
|
|
312
|
+
log_debug(f"Found plugin for {language}: {type(plugin)}")
|
|
313
|
+
|
|
314
|
+
# Run analysis (with performance monitoring)
|
|
315
|
+
with self._performance_monitor.measure_operation(f"analyze_{language}"):
|
|
316
|
+
log_debug(f"Calling plugin.analyze_file for {request.file_path}")
|
|
317
|
+
result = await plugin.analyze_file(request.file_path, request)
|
|
318
|
+
log_debug(
|
|
319
|
+
f"Plugin returned result: success={result.success}, elements={len(result.elements) if result.elements else 0}"
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# Ensure language field is set
|
|
323
|
+
if result.language == "unknown" or not result.language:
|
|
324
|
+
result.language = language
|
|
325
|
+
|
|
326
|
+
# Save to cache
|
|
327
|
+
await self._cache_service.set(cache_key, result)
|
|
328
|
+
|
|
329
|
+
log_performance(
|
|
330
|
+
"unified_analysis",
|
|
331
|
+
self._performance_monitor.get_last_duration(),
|
|
332
|
+
f"Analyzed {request.file_path} ({language})",
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
return result
|
|
336
|
+
|
|
337
|
+
async def analyze_file(self, file_path: str) -> AnalysisResult:
|
|
338
|
+
"""
|
|
339
|
+
Backward compatibility method for analyze_file.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
file_path: Path to the file to analyze
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
Analysis result
|
|
346
|
+
"""
|
|
347
|
+
# Security validation
|
|
348
|
+
is_valid, error_msg = self._security_validator.validate_file_path(file_path)
|
|
349
|
+
if not is_valid:
|
|
350
|
+
log_error(
|
|
351
|
+
f"Security validation failed for file path: {file_path} - {error_msg}"
|
|
352
|
+
)
|
|
353
|
+
raise ValueError(f"Invalid file path: {error_msg}")
|
|
354
|
+
|
|
355
|
+
request = AnalysisRequest(
|
|
356
|
+
file_path=file_path,
|
|
357
|
+
language=None, # Auto-detect
|
|
358
|
+
include_complexity=True,
|
|
359
|
+
include_details=True,
|
|
360
|
+
)
|
|
361
|
+
return await self.analyze(request)
|
|
362
|
+
|
|
363
|
+
def _generate_cache_key(self, request: AnalysisRequest) -> str:
|
|
364
|
+
"""
|
|
365
|
+
Generate cache key
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
request: Analysis request
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
Hashed cache key
|
|
372
|
+
"""
|
|
373
|
+
# 一意なキーを生成するための文字列を構築
|
|
374
|
+
key_components = [
|
|
375
|
+
request.file_path,
|
|
376
|
+
str(request.language),
|
|
377
|
+
str(request.include_complexity),
|
|
378
|
+
str(request.include_details),
|
|
379
|
+
request.format_type,
|
|
380
|
+
]
|
|
381
|
+
|
|
382
|
+
key_string = ":".join(key_components)
|
|
383
|
+
|
|
384
|
+
# SHA256でハッシュ化
|
|
385
|
+
return hashlib.sha256(key_string.encode("utf-8")).hexdigest()
|
|
386
|
+
|
|
387
|
+
def _detect_language(self, file_path: str) -> str:
|
|
388
|
+
"""
|
|
389
|
+
Detect language from file extension
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
file_path: File path
|
|
393
|
+
|
|
394
|
+
Returns:
|
|
395
|
+
Detected language name
|
|
396
|
+
"""
|
|
397
|
+
# 簡易的な拡張子ベース検出
|
|
398
|
+
from pathlib import Path
|
|
399
|
+
|
|
400
|
+
ext = Path(file_path).suffix
|
|
401
|
+
|
|
402
|
+
language_map = {
|
|
403
|
+
".java": "java",
|
|
404
|
+
".py": "python",
|
|
405
|
+
".js": "javascript",
|
|
406
|
+
".ts": "typescript",
|
|
407
|
+
".c": "c",
|
|
408
|
+
".cpp": "cpp",
|
|
409
|
+
".cc": "cpp",
|
|
410
|
+
".cxx": "cpp",
|
|
411
|
+
".rs": "rust",
|
|
412
|
+
".go": "go",
|
|
413
|
+
".sql": "sql",
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
detected = language_map.get(ext.lower(), "unknown")
|
|
417
|
+
log_debug(f"Language detection: {file_path} -> {detected}")
|
|
418
|
+
return detected
|
|
419
|
+
|
|
420
|
+
def clear_cache(self) -> None:
|
|
421
|
+
"""Clear cache (for tests)"""
|
|
422
|
+
self._cache_service.clear()
|
|
423
|
+
log_info("Analysis engine cache cleared")
|
|
424
|
+
|
|
425
|
+
def register_plugin(self, language: str, plugin: BaseLanguagePlugin) -> None:
|
|
426
|
+
"""
|
|
427
|
+
Register plugin
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
language: Language name (kept for compatibility, not used)
|
|
431
|
+
plugin: Language plugin instance
|
|
432
|
+
"""
|
|
433
|
+
self._plugin_manager.register_plugin(plugin)
|
|
434
|
+
|
|
435
|
+
def get_supported_languages(self) -> list[str]:
|
|
436
|
+
"""
|
|
437
|
+
Get list of supported languages
|
|
438
|
+
|
|
439
|
+
Returns:
|
|
440
|
+
List of language names
|
|
441
|
+
"""
|
|
442
|
+
return self._plugin_manager.get_supported_languages()
|
|
443
|
+
|
|
444
|
+
def get_cache_stats(self) -> dict[str, Any]:
|
|
445
|
+
"""
|
|
446
|
+
Get cache statistics
|
|
447
|
+
|
|
448
|
+
Returns:
|
|
449
|
+
Cache statistics dictionary
|
|
450
|
+
"""
|
|
451
|
+
return self._cache_service.get_stats()
|
|
452
|
+
|
|
453
|
+
async def invalidate_cache_pattern(self, pattern: str) -> int:
|
|
454
|
+
"""
|
|
455
|
+
Invalidate cached entries matching a pattern
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
pattern: Pattern to match keys
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
Number of invalidated keys
|
|
462
|
+
"""
|
|
463
|
+
return await self._cache_service.invalidate_pattern(pattern)
|
|
464
|
+
|
|
465
|
+
def measure_operation(self, operation_name: str) -> "PerformanceContext":
|
|
466
|
+
"""
|
|
467
|
+
Context manager for performance measurement
|
|
468
|
+
|
|
469
|
+
Args:
|
|
470
|
+
operation_name: Operation name
|
|
471
|
+
|
|
472
|
+
Returns:
|
|
473
|
+
PerformanceContext
|
|
474
|
+
"""
|
|
475
|
+
return self._performance_monitor.measure_operation(operation_name)
|
|
476
|
+
|
|
477
|
+
def start_monitoring(self) -> None:
|
|
478
|
+
"""Start performance monitoring"""
|
|
479
|
+
self._performance_monitor.start_monitoring()
|
|
480
|
+
|
|
481
|
+
def stop_monitoring(self) -> None:
|
|
482
|
+
"""Stop performance monitoring"""
|
|
483
|
+
self._performance_monitor.stop_monitoring()
|
|
484
|
+
|
|
485
|
+
def get_operation_stats(self) -> dict[str, Any]:
|
|
486
|
+
"""Get operation statistics"""
|
|
487
|
+
return self._performance_monitor.get_operation_stats()
|
|
488
|
+
|
|
489
|
+
def get_performance_summary(self) -> dict[str, Any]:
|
|
490
|
+
"""Get performance summary"""
|
|
491
|
+
return self._performance_monitor.get_performance_summary()
|
|
492
|
+
|
|
493
|
+
def clear_metrics(self) -> None:
|
|
494
|
+
"""
|
|
495
|
+
Clear collected performance metrics
|
|
496
|
+
|
|
497
|
+
Resets metrics collected by performance monitoring. Used in tests/debugging.
|
|
498
|
+
"""
|
|
499
|
+
# 新しいパフォーマンスモニターインスタンスを作成してリセット
|
|
500
|
+
self._performance_monitor = PerformanceMonitor()
|
|
501
|
+
log_info("Performance metrics cleared")
|
|
502
|
+
|
|
503
|
+
def cleanup(self) -> None:
|
|
504
|
+
"""
|
|
505
|
+
Explicit resource cleanup
|
|
506
|
+
|
|
507
|
+
Call explicitly (e.g., at end of tests) to clean up resources and avoid
|
|
508
|
+
async issues in destructors.
|
|
509
|
+
"""
|
|
510
|
+
try:
|
|
511
|
+
if hasattr(self, "_cache_service"):
|
|
512
|
+
self._cache_service.clear()
|
|
513
|
+
if hasattr(self, "_performance_monitor"):
|
|
514
|
+
self._performance_monitor.clear_metrics()
|
|
515
|
+
log_debug("UnifiedAnalysisEngine cleaned up")
|
|
516
|
+
except Exception as e:
|
|
517
|
+
log_error(f"Error during UnifiedAnalysisEngine cleanup: {e}")
|
|
518
|
+
|
|
519
|
+
def __del__(self) -> None:
|
|
520
|
+
"""
|
|
521
|
+
Destructor - keep minimal to avoid issues in async contexts
|
|
522
|
+
|
|
523
|
+
Performs no cleanup; use cleanup() explicitly when needed.
|
|
524
|
+
"""
|
|
525
|
+
# デストラクタでは何もしない(非同期コンテキストでの問題を避けるため)
|
|
526
|
+
pass
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
# 簡易的なプラグイン実装(テスト用)
|
|
530
|
+
class MockLanguagePlugin:
|
|
531
|
+
"""Mock plugin for testing"""
|
|
532
|
+
|
|
533
|
+
def __init__(self, language: str) -> None:
|
|
534
|
+
self.language = language
|
|
535
|
+
|
|
536
|
+
def get_language_name(self) -> str:
|
|
537
|
+
"""Get language name"""
|
|
538
|
+
return self.language
|
|
539
|
+
|
|
540
|
+
def get_file_extensions(self) -> list[str]:
|
|
541
|
+
"""Get supported file extensions"""
|
|
542
|
+
return [f".{self.language}"]
|
|
543
|
+
|
|
544
|
+
def create_extractor(self) -> None:
|
|
545
|
+
"""Create extractor (mock)"""
|
|
546
|
+
return None
|
|
547
|
+
|
|
548
|
+
async def analyze_file(
|
|
549
|
+
self, file_path: str, request: AnalysisRequest
|
|
550
|
+
) -> AnalysisResult:
|
|
551
|
+
"""Mock analysis implementation"""
|
|
552
|
+
log_info(f"Mock analysis for {file_path} ({self.language})")
|
|
553
|
+
|
|
554
|
+
# 簡易的な解析結果を返す
|
|
555
|
+
return AnalysisResult(
|
|
556
|
+
file_path=file_path,
|
|
557
|
+
line_count=10, # 新しいアーキテクチャ用
|
|
558
|
+
elements=[], # 新しいアーキテクチャ用
|
|
559
|
+
node_count=5, # 新しいアーキテクチャ用
|
|
560
|
+
query_results={}, # 新しいアーキテクチャ用
|
|
561
|
+
source_code="// Mock source code", # 新しいアーキテクチャ用
|
|
562
|
+
language=self.language, # 言語を設定
|
|
563
|
+
package=None,
|
|
564
|
+
analysis_time=0.1,
|
|
565
|
+
success=True,
|
|
566
|
+
error_message=None,
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def get_analysis_engine(project_root: str | None = None) -> UnifiedAnalysisEngine:
|
|
571
|
+
"""
|
|
572
|
+
Get unified analysis engine instance
|
|
573
|
+
|
|
574
|
+
Args:
|
|
575
|
+
project_root: Project root directory for security validation
|
|
576
|
+
|
|
577
|
+
Returns:
|
|
578
|
+
Singleton instance of UnifiedAnalysisEngine
|
|
579
|
+
"""
|
|
580
|
+
return UnifiedAnalysisEngine(project_root)
|