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.
Files changed (149) hide show
  1. tree_sitter_analyzer/__init__.py +132 -0
  2. tree_sitter_analyzer/__main__.py +11 -0
  3. tree_sitter_analyzer/api.py +853 -0
  4. tree_sitter_analyzer/cli/__init__.py +39 -0
  5. tree_sitter_analyzer/cli/__main__.py +12 -0
  6. tree_sitter_analyzer/cli/argument_validator.py +89 -0
  7. tree_sitter_analyzer/cli/commands/__init__.py +26 -0
  8. tree_sitter_analyzer/cli/commands/advanced_command.py +226 -0
  9. tree_sitter_analyzer/cli/commands/base_command.py +181 -0
  10. tree_sitter_analyzer/cli/commands/default_command.py +18 -0
  11. tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +188 -0
  12. tree_sitter_analyzer/cli/commands/list_files_cli.py +133 -0
  13. tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -0
  14. tree_sitter_analyzer/cli/commands/query_command.py +109 -0
  15. tree_sitter_analyzer/cli/commands/search_content_cli.py +161 -0
  16. tree_sitter_analyzer/cli/commands/structure_command.py +156 -0
  17. tree_sitter_analyzer/cli/commands/summary_command.py +116 -0
  18. tree_sitter_analyzer/cli/commands/table_command.py +414 -0
  19. tree_sitter_analyzer/cli/info_commands.py +124 -0
  20. tree_sitter_analyzer/cli_main.py +472 -0
  21. tree_sitter_analyzer/constants.py +85 -0
  22. tree_sitter_analyzer/core/__init__.py +15 -0
  23. tree_sitter_analyzer/core/analysis_engine.py +580 -0
  24. tree_sitter_analyzer/core/cache_service.py +333 -0
  25. tree_sitter_analyzer/core/engine.py +585 -0
  26. tree_sitter_analyzer/core/parser.py +293 -0
  27. tree_sitter_analyzer/core/query.py +605 -0
  28. tree_sitter_analyzer/core/query_filter.py +200 -0
  29. tree_sitter_analyzer/core/query_service.py +340 -0
  30. tree_sitter_analyzer/encoding_utils.py +530 -0
  31. tree_sitter_analyzer/exceptions.py +747 -0
  32. tree_sitter_analyzer/file_handler.py +246 -0
  33. tree_sitter_analyzer/formatters/__init__.py +1 -0
  34. tree_sitter_analyzer/formatters/base_formatter.py +201 -0
  35. tree_sitter_analyzer/formatters/csharp_formatter.py +367 -0
  36. tree_sitter_analyzer/formatters/formatter_config.py +197 -0
  37. tree_sitter_analyzer/formatters/formatter_factory.py +84 -0
  38. tree_sitter_analyzer/formatters/formatter_registry.py +377 -0
  39. tree_sitter_analyzer/formatters/formatter_selector.py +96 -0
  40. tree_sitter_analyzer/formatters/go_formatter.py +368 -0
  41. tree_sitter_analyzer/formatters/html_formatter.py +498 -0
  42. tree_sitter_analyzer/formatters/java_formatter.py +423 -0
  43. tree_sitter_analyzer/formatters/javascript_formatter.py +611 -0
  44. tree_sitter_analyzer/formatters/kotlin_formatter.py +268 -0
  45. tree_sitter_analyzer/formatters/language_formatter_factory.py +123 -0
  46. tree_sitter_analyzer/formatters/legacy_formatter_adapters.py +228 -0
  47. tree_sitter_analyzer/formatters/markdown_formatter.py +725 -0
  48. tree_sitter_analyzer/formatters/php_formatter.py +301 -0
  49. tree_sitter_analyzer/formatters/python_formatter.py +830 -0
  50. tree_sitter_analyzer/formatters/ruby_formatter.py +278 -0
  51. tree_sitter_analyzer/formatters/rust_formatter.py +233 -0
  52. tree_sitter_analyzer/formatters/sql_formatter_wrapper.py +689 -0
  53. tree_sitter_analyzer/formatters/sql_formatters.py +536 -0
  54. tree_sitter_analyzer/formatters/typescript_formatter.py +543 -0
  55. tree_sitter_analyzer/formatters/yaml_formatter.py +462 -0
  56. tree_sitter_analyzer/interfaces/__init__.py +9 -0
  57. tree_sitter_analyzer/interfaces/cli.py +535 -0
  58. tree_sitter_analyzer/interfaces/cli_adapter.py +359 -0
  59. tree_sitter_analyzer/interfaces/mcp_adapter.py +224 -0
  60. tree_sitter_analyzer/interfaces/mcp_server.py +428 -0
  61. tree_sitter_analyzer/language_detector.py +553 -0
  62. tree_sitter_analyzer/language_loader.py +271 -0
  63. tree_sitter_analyzer/languages/__init__.py +10 -0
  64. tree_sitter_analyzer/languages/csharp_plugin.py +1076 -0
  65. tree_sitter_analyzer/languages/css_plugin.py +449 -0
  66. tree_sitter_analyzer/languages/go_plugin.py +836 -0
  67. tree_sitter_analyzer/languages/html_plugin.py +496 -0
  68. tree_sitter_analyzer/languages/java_plugin.py +1299 -0
  69. tree_sitter_analyzer/languages/javascript_plugin.py +1622 -0
  70. tree_sitter_analyzer/languages/kotlin_plugin.py +656 -0
  71. tree_sitter_analyzer/languages/markdown_plugin.py +1928 -0
  72. tree_sitter_analyzer/languages/php_plugin.py +862 -0
  73. tree_sitter_analyzer/languages/python_plugin.py +1636 -0
  74. tree_sitter_analyzer/languages/ruby_plugin.py +757 -0
  75. tree_sitter_analyzer/languages/rust_plugin.py +673 -0
  76. tree_sitter_analyzer/languages/sql_plugin.py +2444 -0
  77. tree_sitter_analyzer/languages/typescript_plugin.py +1892 -0
  78. tree_sitter_analyzer/languages/yaml_plugin.py +695 -0
  79. tree_sitter_analyzer/legacy_table_formatter.py +860 -0
  80. tree_sitter_analyzer/mcp/__init__.py +34 -0
  81. tree_sitter_analyzer/mcp/resources/__init__.py +43 -0
  82. tree_sitter_analyzer/mcp/resources/code_file_resource.py +208 -0
  83. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +586 -0
  84. tree_sitter_analyzer/mcp/server.py +869 -0
  85. tree_sitter_analyzer/mcp/tools/__init__.py +28 -0
  86. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +779 -0
  87. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +291 -0
  88. tree_sitter_analyzer/mcp/tools/base_tool.py +139 -0
  89. tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +816 -0
  90. tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +686 -0
  91. tree_sitter_analyzer/mcp/tools/list_files_tool.py +413 -0
  92. tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
  93. tree_sitter_analyzer/mcp/tools/query_tool.py +443 -0
  94. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +464 -0
  95. tree_sitter_analyzer/mcp/tools/search_content_tool.py +836 -0
  96. tree_sitter_analyzer/mcp/tools/table_format_tool.py +572 -0
  97. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +653 -0
  98. tree_sitter_analyzer/mcp/utils/__init__.py +113 -0
  99. tree_sitter_analyzer/mcp/utils/error_handler.py +569 -0
  100. tree_sitter_analyzer/mcp/utils/file_output_factory.py +217 -0
  101. tree_sitter_analyzer/mcp/utils/file_output_manager.py +322 -0
  102. tree_sitter_analyzer/mcp/utils/gitignore_detector.py +358 -0
  103. tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -0
  104. tree_sitter_analyzer/mcp/utils/search_cache.py +343 -0
  105. tree_sitter_analyzer/models.py +840 -0
  106. tree_sitter_analyzer/mypy_current_errors.txt +2 -0
  107. tree_sitter_analyzer/output_manager.py +255 -0
  108. tree_sitter_analyzer/platform_compat/__init__.py +3 -0
  109. tree_sitter_analyzer/platform_compat/adapter.py +324 -0
  110. tree_sitter_analyzer/platform_compat/compare.py +224 -0
  111. tree_sitter_analyzer/platform_compat/detector.py +67 -0
  112. tree_sitter_analyzer/platform_compat/fixtures.py +228 -0
  113. tree_sitter_analyzer/platform_compat/profiles.py +217 -0
  114. tree_sitter_analyzer/platform_compat/record.py +55 -0
  115. tree_sitter_analyzer/platform_compat/recorder.py +155 -0
  116. tree_sitter_analyzer/platform_compat/report.py +92 -0
  117. tree_sitter_analyzer/plugins/__init__.py +280 -0
  118. tree_sitter_analyzer/plugins/base.py +647 -0
  119. tree_sitter_analyzer/plugins/manager.py +384 -0
  120. tree_sitter_analyzer/project_detector.py +328 -0
  121. tree_sitter_analyzer/queries/__init__.py +27 -0
  122. tree_sitter_analyzer/queries/csharp.py +216 -0
  123. tree_sitter_analyzer/queries/css.py +615 -0
  124. tree_sitter_analyzer/queries/go.py +275 -0
  125. tree_sitter_analyzer/queries/html.py +543 -0
  126. tree_sitter_analyzer/queries/java.py +402 -0
  127. tree_sitter_analyzer/queries/javascript.py +724 -0
  128. tree_sitter_analyzer/queries/kotlin.py +192 -0
  129. tree_sitter_analyzer/queries/markdown.py +258 -0
  130. tree_sitter_analyzer/queries/php.py +95 -0
  131. tree_sitter_analyzer/queries/python.py +859 -0
  132. tree_sitter_analyzer/queries/ruby.py +92 -0
  133. tree_sitter_analyzer/queries/rust.py +223 -0
  134. tree_sitter_analyzer/queries/sql.py +555 -0
  135. tree_sitter_analyzer/queries/typescript.py +871 -0
  136. tree_sitter_analyzer/queries/yaml.py +236 -0
  137. tree_sitter_analyzer/query_loader.py +272 -0
  138. tree_sitter_analyzer/security/__init__.py +22 -0
  139. tree_sitter_analyzer/security/boundary_manager.py +277 -0
  140. tree_sitter_analyzer/security/regex_checker.py +297 -0
  141. tree_sitter_analyzer/security/validator.py +599 -0
  142. tree_sitter_analyzer/table_formatter.py +782 -0
  143. tree_sitter_analyzer/utils/__init__.py +53 -0
  144. tree_sitter_analyzer/utils/logging.py +433 -0
  145. tree_sitter_analyzer/utils/tree_sitter_compat.py +289 -0
  146. tree_sitter_analyzer-1.9.17.1.dist-info/METADATA +485 -0
  147. tree_sitter_analyzer-1.9.17.1.dist-info/RECORD +149 -0
  148. tree_sitter_analyzer-1.9.17.1.dist-info/WHEEL +4 -0
  149. 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)