tree-sitter-analyzer 0.1.3__py3-none-any.whl → 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of tree-sitter-analyzer might be problematic. Click here for more details.

Files changed (79) hide show
  1. tree_sitter_analyzer/__init__.py +133 -121
  2. tree_sitter_analyzer/__main__.py +11 -12
  3. tree_sitter_analyzer/api.py +531 -539
  4. tree_sitter_analyzer/cli/__init__.py +39 -39
  5. tree_sitter_analyzer/cli/__main__.py +12 -13
  6. tree_sitter_analyzer/cli/commands/__init__.py +26 -27
  7. tree_sitter_analyzer/cli/commands/advanced_command.py +88 -88
  8. tree_sitter_analyzer/cli/commands/base_command.py +160 -155
  9. tree_sitter_analyzer/cli/commands/default_command.py +18 -19
  10. tree_sitter_analyzer/cli/commands/partial_read_command.py +141 -133
  11. tree_sitter_analyzer/cli/commands/query_command.py +81 -82
  12. tree_sitter_analyzer/cli/commands/structure_command.py +138 -121
  13. tree_sitter_analyzer/cli/commands/summary_command.py +101 -93
  14. tree_sitter_analyzer/cli/commands/table_command.py +232 -233
  15. tree_sitter_analyzer/cli/info_commands.py +120 -121
  16. tree_sitter_analyzer/cli_main.py +277 -276
  17. tree_sitter_analyzer/core/__init__.py +15 -20
  18. tree_sitter_analyzer/core/analysis_engine.py +591 -574
  19. tree_sitter_analyzer/core/cache_service.py +320 -330
  20. tree_sitter_analyzer/core/engine.py +557 -560
  21. tree_sitter_analyzer/core/parser.py +293 -288
  22. tree_sitter_analyzer/core/query.py +494 -502
  23. tree_sitter_analyzer/encoding_utils.py +458 -460
  24. tree_sitter_analyzer/exceptions.py +337 -340
  25. tree_sitter_analyzer/file_handler.py +217 -222
  26. tree_sitter_analyzer/formatters/__init__.py +1 -1
  27. tree_sitter_analyzer/formatters/base_formatter.py +167 -168
  28. tree_sitter_analyzer/formatters/formatter_factory.py +78 -74
  29. tree_sitter_analyzer/formatters/java_formatter.py +287 -270
  30. tree_sitter_analyzer/formatters/python_formatter.py +255 -235
  31. tree_sitter_analyzer/interfaces/__init__.py +9 -10
  32. tree_sitter_analyzer/interfaces/cli.py +528 -557
  33. tree_sitter_analyzer/interfaces/cli_adapter.py +322 -319
  34. tree_sitter_analyzer/interfaces/mcp_adapter.py +180 -170
  35. tree_sitter_analyzer/interfaces/mcp_server.py +405 -416
  36. tree_sitter_analyzer/java_analyzer.py +218 -219
  37. tree_sitter_analyzer/language_detector.py +398 -400
  38. tree_sitter_analyzer/language_loader.py +224 -228
  39. tree_sitter_analyzer/languages/__init__.py +10 -11
  40. tree_sitter_analyzer/languages/java_plugin.py +1129 -1113
  41. tree_sitter_analyzer/languages/python_plugin.py +737 -712
  42. tree_sitter_analyzer/mcp/__init__.py +31 -32
  43. tree_sitter_analyzer/mcp/resources/__init__.py +44 -47
  44. tree_sitter_analyzer/mcp/resources/code_file_resource.py +212 -213
  45. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +560 -550
  46. tree_sitter_analyzer/mcp/server.py +333 -345
  47. tree_sitter_analyzer/mcp/tools/__init__.py +30 -31
  48. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +621 -557
  49. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +242 -245
  50. tree_sitter_analyzer/mcp/tools/base_tool.py +54 -55
  51. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +300 -302
  52. tree_sitter_analyzer/mcp/tools/table_format_tool.py +362 -359
  53. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +543 -476
  54. tree_sitter_analyzer/mcp/utils/__init__.py +105 -106
  55. tree_sitter_analyzer/mcp/utils/error_handler.py +549 -549
  56. tree_sitter_analyzer/models.py +470 -481
  57. tree_sitter_analyzer/output_manager.py +261 -264
  58. tree_sitter_analyzer/plugins/__init__.py +333 -334
  59. tree_sitter_analyzer/plugins/base.py +477 -446
  60. tree_sitter_analyzer/plugins/java_plugin.py +608 -625
  61. tree_sitter_analyzer/plugins/javascript_plugin.py +446 -439
  62. tree_sitter_analyzer/plugins/manager.py +362 -355
  63. tree_sitter_analyzer/plugins/plugin_loader.py +85 -83
  64. tree_sitter_analyzer/plugins/python_plugin.py +606 -598
  65. tree_sitter_analyzer/plugins/registry.py +374 -366
  66. tree_sitter_analyzer/queries/__init__.py +26 -27
  67. tree_sitter_analyzer/queries/java.py +391 -394
  68. tree_sitter_analyzer/queries/javascript.py +148 -149
  69. tree_sitter_analyzer/queries/python.py +285 -286
  70. tree_sitter_analyzer/queries/typescript.py +229 -230
  71. tree_sitter_analyzer/query_loader.py +254 -260
  72. tree_sitter_analyzer/table_formatter.py +468 -448
  73. tree_sitter_analyzer/utils.py +277 -277
  74. tree_sitter_analyzer-0.3.0.dist-info/METADATA +346 -0
  75. tree_sitter_analyzer-0.3.0.dist-info/RECORD +77 -0
  76. tree_sitter_analyzer-0.1.3.dist-info/METADATA +0 -444
  77. tree_sitter_analyzer-0.1.3.dist-info/RECORD +0 -77
  78. {tree_sitter_analyzer-0.1.3.dist-info → tree_sitter_analyzer-0.3.0.dist-info}/WHEEL +0 -0
  79. {tree_sitter_analyzer-0.1.3.dist-info → tree_sitter_analyzer-0.3.0.dist-info}/entry_points.txt +0 -0
@@ -1,574 +1,591 @@
1
- #!/usr/bin/env python3
2
- """
3
- 統一解析エンジン - CLI・MCP共通解析システム(修正版)
4
-
5
- このモジュールは、すべての解析処理の中心となる統一エンジンを提供します。
6
- CLI、MCP、その他のインターフェースから共通して使用されます。
7
-
8
- Roo Code規約準拠:
9
- - 型ヒント: 全関数に型ヒント必須
10
- - MCPログ: 各ステップでログ出力
11
- - docstring: Google Style docstring
12
- - パフォーマンス重視: シングルトンパターンとキャッシュ共有
13
- """
14
-
15
- import asyncio
16
- import hashlib
17
- import threading
18
- from dataclasses import dataclass
19
- from typing import Any, Dict, Optional, Protocol
20
- from abc import ABC, abstractmethod
21
-
22
- from ..utils import log_debug, log_info, log_error, log_performance
23
- from .cache_service import CacheService
24
- from ..models import AnalysisResult
25
-
26
-
27
- class UnsupportedLanguageError(Exception):
28
- """サポートされていない言語エラー"""
29
- pass
30
-
31
-
32
- class PluginRegistry(Protocol):
33
- """プラグイン登録管理のプロトコル"""
34
-
35
- def get_plugin(self, language: str) -> Optional['LanguagePlugin']:
36
- """言語プラグインを取得"""
37
- ...
38
-
39
-
40
- class LanguagePlugin(Protocol):
41
- """言語プラグインのプロトコル"""
42
-
43
- async def analyze_file(self, file_path: str, request: 'AnalysisRequest') -> AnalysisResult:
44
- """ファイル解析"""
45
- ...
46
-
47
-
48
- class PerformanceMonitor:
49
- """パフォーマンス監視(簡易版)"""
50
-
51
- def __init__(self) -> None:
52
- self._last_duration: float = 0.0
53
- self._monitoring_active: bool = False
54
- self._operation_stats: Dict[str, Any] = {}
55
- self._total_operations: int = 0
56
-
57
- def measure_operation(self, operation_name: str) -> 'PerformanceContext':
58
- """操作の測定コンテキストを返す"""
59
- return PerformanceContext(operation_name, self)
60
-
61
- def get_last_duration(self) -> float:
62
- """最後の操作時間を取得"""
63
- return self._last_duration
64
-
65
- def _set_duration(self, duration: float) -> None:
66
- """操作時間を設定(内部用)"""
67
- self._last_duration = duration
68
-
69
- def start_monitoring(self) -> None:
70
- """パフォーマンス監視を開始"""
71
- self._monitoring_active = True
72
- log_info("Performance monitoring started")
73
-
74
- def stop_monitoring(self) -> None:
75
- """パフォーマンス監視を停止"""
76
- self._monitoring_active = False
77
- log_info("Performance monitoring stopped")
78
-
79
- def get_operation_stats(self) -> Dict[str, Any]:
80
- """操作統計を取得"""
81
- return self._operation_stats.copy()
82
-
83
- def get_performance_summary(self) -> Dict[str, Any]:
84
- """パフォーマンス要約を取得"""
85
- return {
86
- "total_operations": self._total_operations,
87
- "monitoring_active": self._monitoring_active,
88
- "last_duration": self._last_duration,
89
- "operation_count": len(self._operation_stats)
90
- }
91
-
92
- def record_operation(self, operation_name: str, duration: float) -> None:
93
- """操作を記録"""
94
- if self._monitoring_active:
95
- if operation_name not in self._operation_stats:
96
- self._operation_stats[operation_name] = {
97
- "count": 0,
98
- "total_time": 0.0,
99
- "avg_time": 0.0,
100
- "min_time": float('inf'),
101
- "max_time": 0.0
102
- }
103
-
104
- stats = self._operation_stats[operation_name]
105
- stats["count"] += 1
106
- stats["total_time"] += duration
107
- stats["avg_time"] = stats["total_time"] / stats["count"]
108
- stats["min_time"] = min(stats["min_time"], duration)
109
- stats["max_time"] = max(stats["max_time"], duration)
110
-
111
- self._total_operations += 1
112
-
113
- def clear_metrics(self) -> None:
114
- """メトリクスをクリア"""
115
- self._operation_stats.clear()
116
- self._total_operations = 0
117
- self._last_duration = 0.0
118
- log_info("Performance metrics cleared")
119
-
120
-
121
- class PerformanceContext:
122
- """パフォーマンス測定コンテキスト"""
123
-
124
- def __init__(self, operation_name: str, monitor: PerformanceMonitor) -> None:
125
- self.operation_name = operation_name
126
- self.monitor = monitor
127
- self.start_time: float = 0.0
128
-
129
- def __enter__(self) -> 'PerformanceContext':
130
- import time
131
- self.start_time = time.time()
132
- return self
133
-
134
- def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
135
- import time
136
- duration = time.time() - self.start_time
137
- self.monitor._set_duration(duration)
138
- self.monitor.record_operation(self.operation_name, duration)
139
- log_performance(self.operation_name, duration, f"Operation completed")
140
-
141
-
142
- @dataclass(frozen=True)
143
- class AnalysisRequest:
144
- """
145
- 解析リクエスト
146
-
147
- Attributes:
148
- file_path: 解析対象ファイルパス
149
- language: プログラミング言語(Noneの場合は自動検出)
150
- include_complexity: 複雑度計算を含むか
151
- include_details: 詳細情報を含むか
152
- format_type: 出力フォーマット
153
- """
154
- file_path: str
155
- language: Optional[str] = None
156
- include_complexity: bool = True
157
- include_details: bool = False
158
- format_type: str = "json"
159
-
160
- @classmethod
161
- def from_mcp_arguments(cls, arguments: Dict[str, Any]) -> 'AnalysisRequest':
162
- """
163
- MCP引数から解析リクエストを作成
164
-
165
- Args:
166
- arguments: MCP引数辞書
167
-
168
- Returns:
169
- 解析リクエスト
170
- """
171
- return cls(
172
- file_path=arguments.get("file_path", ""),
173
- language=arguments.get("language"),
174
- include_complexity=arguments.get("include_complexity", True),
175
- include_details=arguments.get("include_details", False),
176
- format_type=arguments.get("format_type", "json")
177
- )
178
-
179
-
180
- class SimplePluginRegistry:
181
- """簡易プラグイン登録管理"""
182
-
183
- def __init__(self) -> None:
184
- self._plugins: Dict[str, LanguagePlugin] = {}
185
-
186
- def register_plugin(self, language: str, plugin: LanguagePlugin) -> None:
187
- """プラグインを登録"""
188
- self._plugins[language] = plugin
189
- log_info(f"Plugin registered for language: {language}")
190
-
191
- def get_plugin(self, language: str) -> Optional[LanguagePlugin]:
192
- """プラグインを取得"""
193
- return self._plugins.get(language)
194
-
195
- def get_supported_languages(self) -> list[str]:
196
- """サポートされている言語一覧を取得"""
197
- return list(self._plugins.keys())
198
-
199
-
200
- class UnifiedAnalysisEngine:
201
- """
202
- 統一解析エンジン(修正版)
203
-
204
- CLI・MCP・その他のインターフェースから共通して使用される
205
- 中央集権的な解析エンジン。シングルトンパターンで実装し、
206
- リソースの効率的な利用とキャッシュの共有を実現。
207
-
208
- 修正点:
209
- - デストラクタでの非同期処理問題を解決
210
- - 明示的なクリーンアップメソッドを提供
211
-
212
- Attributes:
213
- _cache_service: キャッシュサービス
214
- _plugin_registry: プラグイン登録管理
215
- _performance_monitor: パフォーマンス監視
216
- """
217
-
218
- _instance: Optional['UnifiedAnalysisEngine'] = None
219
- _lock: threading.Lock = threading.Lock()
220
-
221
- def __new__(cls) -> 'UnifiedAnalysisEngine':
222
- """シングルトンパターンでインスタンス共有"""
223
- if cls._instance is None:
224
- with cls._lock:
225
- if cls._instance is None:
226
- cls._instance = super().__new__(cls)
227
- return cls._instance
228
-
229
- def __init__(self) -> None:
230
- """初期化(一度のみ実行)"""
231
- if hasattr(self, '_initialized'):
232
- return
233
-
234
- self._cache_service = CacheService()
235
- self._plugin_registry = SimplePluginRegistry()
236
- self._performance_monitor = PerformanceMonitor()
237
-
238
- # プラグインを自動ロード
239
- self._load_plugins()
240
-
241
- self._initialized = True
242
-
243
- log_info("UnifiedAnalysisEngine initialized")
244
-
245
- def _load_plugins(self) -> None:
246
- """利用可能なプラグインを自動ロード"""
247
- log_info("Loading plugins...")
248
-
249
- try:
250
- # Javaプラグインの登録
251
- log_debug("Attempting to load Java plugin...")
252
- from ..languages.java_plugin import JavaPlugin
253
- java_plugin = JavaPlugin()
254
- self._plugin_registry.register_plugin("java", java_plugin)
255
- log_debug("Loaded Java plugin")
256
- except Exception as e:
257
- log_error(f"Failed to load Java plugin: {e}")
258
- import traceback
259
- log_error(f"Java plugin traceback: {traceback.format_exc()}")
260
-
261
- try:
262
- # JavaScriptプラグインの登録
263
- log_debug("Attempting to load JavaScript plugin...")
264
- from ..plugins.javascript_plugin import JavaScriptPlugin
265
- js_plugin = JavaScriptPlugin()
266
- self._plugin_registry.register_plugin("javascript", js_plugin)
267
- log_debug("Loaded JavaScript plugin")
268
- except Exception as e:
269
- log_error(f"Failed to load JavaScript plugin: {e}")
270
- import traceback
271
- log_error(f"JavaScript plugin traceback: {traceback.format_exc()}")
272
-
273
- try:
274
- # Pythonプラグインの登録
275
- log_debug("Attempting to load Python plugin...")
276
- from ..languages.python_plugin import PythonPlugin
277
- python_plugin = PythonPlugin()
278
- self._plugin_registry.register_plugin("python", python_plugin)
279
- log_debug("Loaded Python plugin")
280
- except Exception as e:
281
- log_error(f"Failed to load Python plugin: {e}")
282
- import traceback
283
- log_error(f"Python plugin traceback: {traceback.format_exc()}")
284
-
285
- final_languages = self._plugin_registry.get_supported_languages()
286
- log_info(f"Successfully loaded {len(final_languages)} language plugins: {', '.join(final_languages)}")
287
-
288
- async def analyze(self, request: AnalysisRequest) -> AnalysisResult:
289
- """
290
- 統一解析メソッド
291
-
292
- Args:
293
- request: 解析リクエスト
294
-
295
- Returns:
296
- 解析結果
297
-
298
- Raises:
299
- UnsupportedLanguageError: サポートされていない言語
300
- FileNotFoundError: ファイルが見つからない
301
- """
302
- log_info(f"Starting analysis for {request.file_path}")
303
-
304
- # キャッシュチェック(CLI・MCP間で共有)
305
- cache_key = self._generate_cache_key(request)
306
- cached_result = await self._cache_service.get(cache_key)
307
- if cached_result:
308
- log_info(f"Cache hit for {request.file_path}")
309
- return cached_result
310
-
311
- # 言語検出
312
- language = request.language or self._detect_language(request.file_path)
313
- log_debug(f"Detected language: {language}")
314
-
315
- # デバッグ:登録されているプラグインを確認
316
- supported_languages = self._plugin_registry.get_supported_languages()
317
- log_debug(f"Supported languages: {supported_languages}")
318
- log_debug(f"Looking for plugin for language: {language}")
319
-
320
- # プラグイン取得
321
- plugin = self._plugin_registry.get_plugin(language)
322
- if not plugin:
323
- error_msg = f"Language {language} not supported"
324
- log_error(error_msg)
325
- raise UnsupportedLanguageError(error_msg)
326
-
327
- log_debug(f"Found plugin for {language}: {type(plugin)}")
328
-
329
- # 解析実行(パフォーマンス監視付き)
330
- with self._performance_monitor.measure_operation(f"analyze_{language}"):
331
- log_debug(f"Calling plugin.analyze_file for {request.file_path}")
332
- result = await plugin.analyze_file(request.file_path, request)
333
- log_debug(f"Plugin returned result: success={result.success}, elements={len(result.elements) if result.elements else 0}")
334
-
335
- # 言語情報を確実に設定
336
- if result.language == "unknown" or not result.language:
337
- result.language = language
338
-
339
- # キャッシュ保存
340
- await self._cache_service.set(cache_key, result)
341
-
342
- log_performance(
343
- "unified_analysis",
344
- self._performance_monitor.get_last_duration(),
345
- f"Analyzed {request.file_path} ({language})"
346
- )
347
-
348
- return result
349
-
350
- async def analyze_file(self, file_path: str) -> AnalysisResult:
351
- """
352
- Backward compatibility method for analyze_file.
353
-
354
- Args:
355
- file_path: Path to the file to analyze
356
-
357
- Returns:
358
- Analysis result
359
- """
360
- request = AnalysisRequest(
361
- file_path=file_path,
362
- language=None, # Auto-detect
363
- include_complexity=True,
364
- include_details=True
365
- )
366
- return await self.analyze(request)
367
-
368
- def _generate_cache_key(self, request: AnalysisRequest) -> str:
369
- """
370
- キャッシュキーを生成
371
-
372
- Args:
373
- request: 解析リクエスト
374
-
375
- Returns:
376
- ハッシュ化されたキャッシュキー
377
- """
378
- # 一意なキーを生成するための文字列を構築
379
- key_components = [
380
- request.file_path,
381
- str(request.language),
382
- str(request.include_complexity),
383
- str(request.include_details),
384
- request.format_type
385
- ]
386
-
387
- key_string = ":".join(key_components)
388
-
389
- # SHA256でハッシュ化
390
- return hashlib.sha256(key_string.encode('utf-8')).hexdigest()
391
-
392
- def _detect_language(self, file_path: str) -> str:
393
- """
394
- 言語検出
395
-
396
- Args:
397
- file_path: ファイルパス
398
-
399
- Returns:
400
- 検出された言語
401
- """
402
- # 簡易的な拡張子ベース検出
403
- import os
404
- _, ext = os.path.splitext(file_path)
405
-
406
- language_map = {
407
- '.java': 'java',
408
- '.py': 'python',
409
- '.js': 'javascript',
410
- '.ts': 'typescript',
411
- '.c': 'c',
412
- '.cpp': 'cpp',
413
- '.cc': 'cpp',
414
- '.cxx': 'cpp',
415
- '.rs': 'rust',
416
- '.go': 'go'
417
- }
418
-
419
- detected = language_map.get(ext.lower(), 'unknown')
420
- log_debug(f"Language detection: {file_path} -> {detected}")
421
- return detected
422
-
423
- def clear_cache(self) -> None:
424
- """キャッシュクリア(テスト用)"""
425
- self._cache_service.clear()
426
- log_info("Analysis engine cache cleared")
427
-
428
- def register_plugin(self, language: str, plugin: LanguagePlugin) -> None:
429
- """
430
- プラグインを登録
431
-
432
- Args:
433
- language: 言語名
434
- plugin: 言語プラグイン
435
- """
436
- self._plugin_registry.register_plugin(language, plugin)
437
-
438
- def get_supported_languages(self) -> list[str]:
439
- """
440
- サポートされている言語一覧を取得
441
-
442
- Returns:
443
- サポート言語のリスト
444
- """
445
- return self._plugin_registry.get_supported_languages()
446
-
447
- def get_cache_stats(self) -> Dict[str, Any]:
448
- """
449
- キャッシュ統計を取得
450
-
451
- Returns:
452
- キャッシュ統計情報
453
- """
454
- return self._cache_service.get_stats()
455
-
456
- async def invalidate_cache_pattern(self, pattern: str) -> int:
457
- """
458
- パターンに一致するキャッシュを無効化
459
-
460
- Args:
461
- pattern: 無効化するキーのパターン
462
-
463
- Returns:
464
- 無効化されたキー数
465
- """
466
- return await self._cache_service.invalidate_pattern(pattern)
467
-
468
- def measure_operation(self, operation_name: str) -> 'PerformanceContext':
469
- """
470
- パフォーマンス計測のためのコンテキストマネージャ
471
-
472
- Args:
473
- operation_name: 操作名
474
-
475
- Returns:
476
- パフォーマンス測定コンテキスト
477
- """
478
- return self._performance_monitor.measure_operation(operation_name)
479
-
480
- def start_monitoring(self) -> None:
481
- """パフォーマンス監視を開始"""
482
- self._performance_monitor.start_monitoring()
483
-
484
- def stop_monitoring(self) -> None:
485
- """パフォーマンス監視を停止"""
486
- self._performance_monitor.stop_monitoring()
487
-
488
- def get_operation_stats(self) -> Dict[str, Any]:
489
- """操作統計を取得"""
490
- return self._performance_monitor.get_operation_stats()
491
-
492
- def get_performance_summary(self) -> Dict[str, Any]:
493
- """パフォーマンス要約を取得"""
494
- return self._performance_monitor.get_performance_summary()
495
-
496
- def clear_metrics(self) -> None:
497
- """
498
- 収集したパフォーマンスメトリクスをクリア
499
-
500
- パフォーマンス監視で収集されたメトリクスをリセットします。
501
- テストやデバッグ時に使用されます。
502
- """
503
- # 新しいパフォーマンスモニターインスタンスを作成してリセット
504
- self._performance_monitor = PerformanceMonitor()
505
- log_info("Performance metrics cleared")
506
-
507
- def cleanup(self) -> None:
508
- """
509
- 明示的なリソースクリーンアップ
510
-
511
- テスト終了時などに明示的に呼び出してリソースをクリーンアップします。
512
- デストラクタでの非同期処理問題を避けるため、明示的な呼び出しが必要です。
513
- """
514
- try:
515
- if hasattr(self, '_cache_service'):
516
- self._cache_service.clear()
517
- if hasattr(self, '_performance_monitor'):
518
- self._performance_monitor.clear_metrics()
519
- log_debug("UnifiedAnalysisEngine cleaned up")
520
- except Exception as e:
521
- log_error(f"Error during UnifiedAnalysisEngine cleanup: {e}")
522
-
523
- def __del__(self) -> None:
524
- """
525
- デストラクタ - 非同期コンテキストでの問題を避けるため最小限の処理
526
-
527
- デストラクタでは何もしません。これは非同期コンテキストでの
528
- ガベージコレクション時に発生する問題を避けるためです。
529
- 明示的なクリーンアップはcleanup()メソッドを使用してください。
530
- """
531
- # デストラクタでは何もしない(非同期コンテキストでの問題を避けるため)
532
- pass
533
-
534
-
535
- # 簡易的なプラグイン実装(テスト用)
536
- class MockLanguagePlugin:
537
- """テスト用のモックプラグイン"""
538
-
539
- def __init__(self, language: str) -> None:
540
- self.language = language
541
-
542
- async def analyze_file(self, file_path: str, request: AnalysisRequest) -> AnalysisResult:
543
- """モック解析実装"""
544
- log_info(f"Mock analysis for {file_path} ({self.language})")
545
-
546
- # 簡易的な解析結果を返す
547
- return AnalysisResult(
548
- file_path=file_path,
549
- line_count=10, # 新しいアーキテクチャ用
550
- elements=[], # 新しいアーキテクチャ用
551
- node_count=5, # 新しいアーキテクチャ用
552
- query_results={}, # 新しいアーキテクチャ用
553
- source_code="// Mock source code", # 新しいアーキテクチャ用
554
- language=self.language, # 言語を設定
555
- package=None,
556
- imports=[],
557
- classes=[],
558
- methods=[],
559
- fields=[],
560
- annotations=[],
561
- analysis_time=0.1,
562
- success=True,
563
- error_message=None
564
- )
565
-
566
-
567
- def get_analysis_engine() -> UnifiedAnalysisEngine:
568
- """
569
- 統一解析エンジンのインスタンスを取得
570
-
571
- Returns:
572
- 統一解析エンジンのシングルトンインスタンス
573
- """
574
- return UnifiedAnalysisEngine()
1
+ #!/usr/bin/env python3
2
+ """
3
+ 統一解析エンジン - CLI・MCP共通解析システム(修正版)
4
+
5
+ このモジュールは、すべての解析処理の中心となる統一エンジンを提供します。
6
+ CLI、MCP、その他のインターフェースから共通して使用されます。
7
+
8
+ Roo Code規約準拠:
9
+ - 型ヒント: 全関数に型ヒント必須
10
+ - MCPログ: 各ステップでログ出力
11
+ - docstring: Google Style docstring
12
+ - パフォーマンス重視: シングルトンパターンとキャッシュ共有
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 ..utils import log_debug, log_error, log_info, log_performance
22
+ from .cache_service import CacheService
23
+
24
+
25
+ class UnsupportedLanguageError(Exception):
26
+ """サポートされていない言語エラー"""
27
+
28
+ pass
29
+
30
+
31
+ class PluginRegistry(Protocol):
32
+ """プラグイン登録管理のプロトコル"""
33
+
34
+ def get_plugin(self, language: str) -> Optional["LanguagePlugin"]:
35
+ """言語プラグインを取得"""
36
+ ...
37
+
38
+
39
+ class LanguagePlugin(Protocol):
40
+ """言語プラグインのプロトコル"""
41
+
42
+ async def analyze_file(
43
+ self, file_path: str, request: "AnalysisRequest"
44
+ ) -> AnalysisResult:
45
+ """ファイル解析"""
46
+ ...
47
+
48
+
49
+ class PerformanceMonitor:
50
+ """パフォーマンス監視(簡易版)"""
51
+
52
+ def __init__(self) -> None:
53
+ self._last_duration: float = 0.0
54
+ self._monitoring_active: bool = False
55
+ self._operation_stats: dict[str, Any] = {}
56
+ self._total_operations: int = 0
57
+
58
+ def measure_operation(self, operation_name: str) -> "PerformanceContext":
59
+ """操作の測定コンテキストを返す"""
60
+ return PerformanceContext(operation_name, self)
61
+
62
+ def get_last_duration(self) -> float:
63
+ """最後の操作時間を取得"""
64
+ return self._last_duration
65
+
66
+ def _set_duration(self, duration: float) -> None:
67
+ """操作時間を設定(内部用)"""
68
+ self._last_duration = duration
69
+
70
+ def start_monitoring(self) -> None:
71
+ """パフォーマンス監視を開始"""
72
+ self._monitoring_active = True
73
+ log_info("Performance monitoring started")
74
+
75
+ def stop_monitoring(self) -> None:
76
+ """パフォーマンス監視を停止"""
77
+ self._monitoring_active = False
78
+ log_info("Performance monitoring stopped")
79
+
80
+ def get_operation_stats(self) -> dict[str, Any]:
81
+ """操作統計を取得"""
82
+ return self._operation_stats.copy()
83
+
84
+ def get_performance_summary(self) -> dict[str, Any]:
85
+ """パフォーマンス要約を取得"""
86
+ return {
87
+ "total_operations": self._total_operations,
88
+ "monitoring_active": self._monitoring_active,
89
+ "last_duration": self._last_duration,
90
+ "operation_count": len(self._operation_stats),
91
+ }
92
+
93
+ def record_operation(self, operation_name: str, duration: float) -> None:
94
+ """操作を記録"""
95
+ if self._monitoring_active:
96
+ if operation_name not in self._operation_stats:
97
+ self._operation_stats[operation_name] = {
98
+ "count": 0,
99
+ "total_time": 0.0,
100
+ "avg_time": 0.0,
101
+ "min_time": float("inf"),
102
+ "max_time": 0.0,
103
+ }
104
+
105
+ stats = self._operation_stats[operation_name]
106
+ stats["count"] += 1
107
+ stats["total_time"] += duration
108
+ stats["avg_time"] = stats["total_time"] / stats["count"]
109
+ stats["min_time"] = min(stats["min_time"], duration)
110
+ stats["max_time"] = max(stats["max_time"], duration)
111
+
112
+ self._total_operations += 1
113
+
114
+ def clear_metrics(self) -> None:
115
+ """メトリクスをクリア"""
116
+ self._operation_stats.clear()
117
+ self._total_operations = 0
118
+ self._last_duration = 0.0
119
+ log_info("Performance metrics cleared")
120
+
121
+
122
+ class PerformanceContext:
123
+ """パフォーマンス測定コンテキスト"""
124
+
125
+ def __init__(self, operation_name: str, monitor: PerformanceMonitor) -> None:
126
+ self.operation_name = operation_name
127
+ self.monitor = monitor
128
+ self.start_time: float = 0.0
129
+
130
+ def __enter__(self) -> "PerformanceContext":
131
+ import time
132
+
133
+ self.start_time = time.time()
134
+ return self
135
+
136
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
137
+ import time
138
+
139
+ duration = time.time() - self.start_time
140
+ self.monitor._set_duration(duration)
141
+ self.monitor.record_operation(self.operation_name, duration)
142
+ log_performance(self.operation_name, duration, "Operation completed")
143
+
144
+
145
+ @dataclass(frozen=True)
146
+ class AnalysisRequest:
147
+ """
148
+ 解析リクエスト
149
+
150
+ Attributes:
151
+ file_path: 解析対象ファイルパス
152
+ language: プログラミング言語(Noneの場合は自動検出)
153
+ include_complexity: 複雑度計算を含むか
154
+ include_details: 詳細情報を含むか
155
+ format_type: 出力フォーマット
156
+ """
157
+
158
+ file_path: str
159
+ language: str | None = None
160
+ include_complexity: bool = True
161
+ include_details: bool = False
162
+ format_type: str = "json"
163
+
164
+ @classmethod
165
+ def from_mcp_arguments(cls, arguments: dict[str, Any]) -> "AnalysisRequest":
166
+ """
167
+ MCP引数から解析リクエストを作成
168
+
169
+ Args:
170
+ arguments: MCP引数辞書
171
+
172
+ Returns:
173
+ 解析リクエスト
174
+ """
175
+ return cls(
176
+ file_path=arguments.get("file_path", ""),
177
+ language=arguments.get("language"),
178
+ include_complexity=arguments.get("include_complexity", True),
179
+ include_details=arguments.get("include_details", False),
180
+ format_type=arguments.get("format_type", "json"),
181
+ )
182
+
183
+
184
+ class SimplePluginRegistry:
185
+ """簡易プラグイン登録管理"""
186
+
187
+ def __init__(self) -> None:
188
+ self._plugins: dict[str, LanguagePlugin] = {}
189
+
190
+ def register_plugin(self, language: str, plugin: LanguagePlugin) -> None:
191
+ """プラグインを登録"""
192
+ self._plugins[language] = plugin
193
+ log_info(f"Plugin registered for language: {language}")
194
+
195
+ def get_plugin(self, language: str) -> LanguagePlugin | None:
196
+ """プラグインを取得"""
197
+ return self._plugins.get(language)
198
+
199
+ def get_supported_languages(self) -> list[str]:
200
+ """サポートされている言語一覧を取得"""
201
+ return list(self._plugins.keys())
202
+
203
+
204
+ class UnifiedAnalysisEngine:
205
+ """
206
+ 統一解析エンジン(修正版)
207
+
208
+ CLI・MCP・その他のインターフェースから共通して使用される
209
+ 中央集権的な解析エンジン。シングルトンパターンで実装し、
210
+ リソースの効率的な利用とキャッシュの共有を実現。
211
+
212
+ 修正点:
213
+ - デストラクタでの非同期処理問題を解決
214
+ - 明示的なクリーンアップメソッドを提供
215
+
216
+ Attributes:
217
+ _cache_service: キャッシュサービス
218
+ _plugin_registry: プラグイン登録管理
219
+ _performance_monitor: パフォーマンス監視
220
+ """
221
+
222
+ _instance: Optional["UnifiedAnalysisEngine"] = None
223
+ _lock: threading.Lock = threading.Lock()
224
+
225
+ def __new__(cls) -> "UnifiedAnalysisEngine":
226
+ """シングルトンパターンでインスタンス共有"""
227
+ if cls._instance is None:
228
+ with cls._lock:
229
+ if cls._instance is None:
230
+ cls._instance = super().__new__(cls)
231
+ return cls._instance
232
+
233
+ def __init__(self) -> None:
234
+ """初期化(一度のみ実行)"""
235
+ if hasattr(self, "_initialized"):
236
+ return
237
+
238
+ self._cache_service = CacheService()
239
+ self._plugin_registry = SimplePluginRegistry()
240
+ self._performance_monitor = PerformanceMonitor()
241
+
242
+ # プラグインを自動ロード
243
+ self._load_plugins()
244
+
245
+ self._initialized = True
246
+
247
+ log_info("UnifiedAnalysisEngine initialized")
248
+
249
+ def _load_plugins(self) -> None:
250
+ """利用可能なプラグインを自動ロード"""
251
+ log_info("Loading plugins...")
252
+
253
+ try:
254
+ # Javaプラグインの登録
255
+ log_debug("Attempting to load Java plugin...")
256
+ from ..languages.java_plugin import JavaPlugin
257
+
258
+ java_plugin = JavaPlugin()
259
+ self._plugin_registry.register_plugin("java", java_plugin)
260
+ log_debug("Loaded Java plugin")
261
+ except Exception as e:
262
+ log_error(f"Failed to load Java plugin: {e}")
263
+ import traceback
264
+
265
+ log_error(f"Java plugin traceback: {traceback.format_exc()}")
266
+
267
+ try:
268
+ # JavaScriptプラグインの登録
269
+ log_debug("Attempting to load JavaScript plugin...")
270
+ from ..plugins.javascript_plugin import JavaScriptPlugin
271
+
272
+ js_plugin = JavaScriptPlugin()
273
+ self._plugin_registry.register_plugin("javascript", js_plugin)
274
+ log_debug("Loaded JavaScript plugin")
275
+ except Exception as e:
276
+ log_error(f"Failed to load JavaScript plugin: {e}")
277
+ import traceback
278
+
279
+ log_error(f"JavaScript plugin traceback: {traceback.format_exc()}")
280
+
281
+ try:
282
+ # Pythonプラグインの登録
283
+ log_debug("Attempting to load Python plugin...")
284
+ from ..languages.python_plugin import PythonPlugin
285
+
286
+ python_plugin = PythonPlugin()
287
+ self._plugin_registry.register_plugin("python", python_plugin)
288
+ log_debug("Loaded Python plugin")
289
+ except Exception as e:
290
+ log_error(f"Failed to load Python plugin: {e}")
291
+ import traceback
292
+
293
+ log_error(f"Python plugin traceback: {traceback.format_exc()}")
294
+
295
+ final_languages = self._plugin_registry.get_supported_languages()
296
+ log_info(
297
+ f"Successfully loaded {len(final_languages)} language plugins: {', '.join(final_languages)}"
298
+ )
299
+
300
+ async def analyze(self, request: AnalysisRequest) -> AnalysisResult:
301
+ """
302
+ 統一解析メソッド
303
+
304
+ Args:
305
+ request: 解析リクエスト
306
+
307
+ Returns:
308
+ 解析結果
309
+
310
+ Raises:
311
+ UnsupportedLanguageError: サポートされていない言語
312
+ FileNotFoundError: ファイルが見つからない
313
+ """
314
+ log_info(f"Starting analysis for {request.file_path}")
315
+
316
+ # キャッシュチェック(CLI・MCP間で共有)
317
+ cache_key = self._generate_cache_key(request)
318
+ cached_result = await self._cache_service.get(cache_key)
319
+ if cached_result:
320
+ log_info(f"Cache hit for {request.file_path}")
321
+ return cached_result
322
+
323
+ # 言語検出
324
+ language = request.language or self._detect_language(request.file_path)
325
+ log_debug(f"Detected language: {language}")
326
+
327
+ # デバッグ:登録されているプラグインを確認
328
+ supported_languages = self._plugin_registry.get_supported_languages()
329
+ log_debug(f"Supported languages: {supported_languages}")
330
+ log_debug(f"Looking for plugin for language: {language}")
331
+
332
+ # プラグイン取得
333
+ plugin = self._plugin_registry.get_plugin(language)
334
+ if not plugin:
335
+ error_msg = f"Language {language} not supported"
336
+ log_error(error_msg)
337
+ raise UnsupportedLanguageError(error_msg)
338
+
339
+ log_debug(f"Found plugin for {language}: {type(plugin)}")
340
+
341
+ # 解析実行(パフォーマンス監視付き)
342
+ with self._performance_monitor.measure_operation(f"analyze_{language}"):
343
+ log_debug(f"Calling plugin.analyze_file for {request.file_path}")
344
+ result = await plugin.analyze_file(request.file_path, request)
345
+ log_debug(
346
+ f"Plugin returned result: success={result.success}, elements={len(result.elements) if result.elements else 0}"
347
+ )
348
+
349
+ # 言語情報を確実に設定
350
+ if result.language == "unknown" or not result.language:
351
+ result.language = language
352
+
353
+ # キャッシュ保存
354
+ await self._cache_service.set(cache_key, result)
355
+
356
+ log_performance(
357
+ "unified_analysis",
358
+ self._performance_monitor.get_last_duration(),
359
+ f"Analyzed {request.file_path} ({language})",
360
+ )
361
+
362
+ return result
363
+
364
+ async def analyze_file(self, file_path: str) -> AnalysisResult:
365
+ """
366
+ Backward compatibility method for analyze_file.
367
+
368
+ Args:
369
+ file_path: Path to the file to analyze
370
+
371
+ Returns:
372
+ Analysis result
373
+ """
374
+ request = AnalysisRequest(
375
+ file_path=file_path,
376
+ language=None, # Auto-detect
377
+ include_complexity=True,
378
+ include_details=True,
379
+ )
380
+ return await self.analyze(request)
381
+
382
+ def _generate_cache_key(self, request: AnalysisRequest) -> str:
383
+ """
384
+ キャッシュキーを生成
385
+
386
+ Args:
387
+ request: 解析リクエスト
388
+
389
+ Returns:
390
+ ハッシュ化されたキャッシュキー
391
+ """
392
+ # 一意なキーを生成するための文字列を構築
393
+ key_components = [
394
+ request.file_path,
395
+ str(request.language),
396
+ str(request.include_complexity),
397
+ str(request.include_details),
398
+ request.format_type,
399
+ ]
400
+
401
+ key_string = ":".join(key_components)
402
+
403
+ # SHA256でハッシュ化
404
+ return hashlib.sha256(key_string.encode("utf-8")).hexdigest()
405
+
406
+ def _detect_language(self, file_path: str) -> str:
407
+ """
408
+ 言語検出
409
+
410
+ Args:
411
+ file_path: ファイルパス
412
+
413
+ Returns:
414
+ 検出された言語
415
+ """
416
+ # 簡易的な拡張子ベース検出
417
+ import os
418
+
419
+ _, ext = os.path.splitext(file_path)
420
+
421
+ language_map = {
422
+ ".java": "java",
423
+ ".py": "python",
424
+ ".js": "javascript",
425
+ ".ts": "typescript",
426
+ ".c": "c",
427
+ ".cpp": "cpp",
428
+ ".cc": "cpp",
429
+ ".cxx": "cpp",
430
+ ".rs": "rust",
431
+ ".go": "go",
432
+ }
433
+
434
+ detected = language_map.get(ext.lower(), "unknown")
435
+ log_debug(f"Language detection: {file_path} -> {detected}")
436
+ return detected
437
+
438
+ def clear_cache(self) -> None:
439
+ """キャッシュクリア(テスト用)"""
440
+ self._cache_service.clear()
441
+ log_info("Analysis engine cache cleared")
442
+
443
+ def register_plugin(self, language: str, plugin: LanguagePlugin) -> None:
444
+ """
445
+ プラグインを登録
446
+
447
+ Args:
448
+ language: 言語名
449
+ plugin: 言語プラグイン
450
+ """
451
+ self._plugin_registry.register_plugin(language, plugin)
452
+
453
+ def get_supported_languages(self) -> list[str]:
454
+ """
455
+ サポートされている言語一覧を取得
456
+
457
+ Returns:
458
+ サポート言語のリスト
459
+ """
460
+ return self._plugin_registry.get_supported_languages()
461
+
462
+ def get_cache_stats(self) -> dict[str, Any]:
463
+ """
464
+ キャッシュ統計を取得
465
+
466
+ Returns:
467
+ キャッシュ統計情報
468
+ """
469
+ return self._cache_service.get_stats()
470
+
471
+ async def invalidate_cache_pattern(self, pattern: str) -> int:
472
+ """
473
+ パターンに一致するキャッシュを無効化
474
+
475
+ Args:
476
+ pattern: 無効化するキーのパターン
477
+
478
+ Returns:
479
+ 無効化されたキー数
480
+ """
481
+ return await self._cache_service.invalidate_pattern(pattern)
482
+
483
+ def measure_operation(self, operation_name: str) -> "PerformanceContext":
484
+ """
485
+ パフォーマンス計測のためのコンテキストマネージャ
486
+
487
+ Args:
488
+ operation_name: 操作名
489
+
490
+ Returns:
491
+ パフォーマンス測定コンテキスト
492
+ """
493
+ return self._performance_monitor.measure_operation(operation_name)
494
+
495
+ def start_monitoring(self) -> None:
496
+ """パフォーマンス監視を開始"""
497
+ self._performance_monitor.start_monitoring()
498
+
499
+ def stop_monitoring(self) -> None:
500
+ """パフォーマンス監視を停止"""
501
+ self._performance_monitor.stop_monitoring()
502
+
503
+ def get_operation_stats(self) -> dict[str, Any]:
504
+ """操作統計を取得"""
505
+ return self._performance_monitor.get_operation_stats()
506
+
507
+ def get_performance_summary(self) -> dict[str, Any]:
508
+ """パフォーマンス要約を取得"""
509
+ return self._performance_monitor.get_performance_summary()
510
+
511
+ def clear_metrics(self) -> None:
512
+ """
513
+ 収集したパフォーマンスメトリクスをクリア
514
+
515
+ パフォーマンス監視で収集されたメトリクスをリセットします。
516
+ テストやデバッグ時に使用されます。
517
+ """
518
+ # 新しいパフォーマンスモニターインスタンスを作成してリセット
519
+ self._performance_monitor = PerformanceMonitor()
520
+ log_info("Performance metrics cleared")
521
+
522
+ def cleanup(self) -> None:
523
+ """
524
+ 明示的なリソースクリーンアップ
525
+
526
+ テスト終了時などに明示的に呼び出してリソースをクリーンアップします。
527
+ デストラクタでの非同期処理問題を避けるため、明示的な呼び出しが必要です。
528
+ """
529
+ try:
530
+ if hasattr(self, "_cache_service"):
531
+ self._cache_service.clear()
532
+ if hasattr(self, "_performance_monitor"):
533
+ self._performance_monitor.clear_metrics()
534
+ log_debug("UnifiedAnalysisEngine cleaned up")
535
+ except Exception as e:
536
+ log_error(f"Error during UnifiedAnalysisEngine cleanup: {e}")
537
+
538
+ def __del__(self) -> None:
539
+ """
540
+ デストラクタ - 非同期コンテキストでの問題を避けるため最小限の処理
541
+
542
+ デストラクタでは何もしません。これは非同期コンテキストでの
543
+ ガベージコレクション時に発生する問題を避けるためです。
544
+ 明示的なクリーンアップはcleanup()メソッドを使用してください。
545
+ """
546
+ # デストラクタでは何もしない(非同期コンテキストでの問題を避けるため)
547
+ pass
548
+
549
+
550
+ # 簡易的なプラグイン実装(テスト用)
551
+ class MockLanguagePlugin:
552
+ """テスト用のモックプラグイン"""
553
+
554
+ def __init__(self, language: str) -> None:
555
+ self.language = language
556
+
557
+ async def analyze_file(
558
+ self, file_path: str, request: AnalysisRequest
559
+ ) -> AnalysisResult:
560
+ """モック解析実装"""
561
+ log_info(f"Mock analysis for {file_path} ({self.language})")
562
+
563
+ # 簡易的な解析結果を返す
564
+ return AnalysisResult(
565
+ file_path=file_path,
566
+ line_count=10, # 新しいアーキテクチャ用
567
+ elements=[], # 新しいアーキテクチャ用
568
+ node_count=5, # 新しいアーキテクチャ用
569
+ query_results={}, # 新しいアーキテクチャ用
570
+ source_code="// Mock source code", # 新しいアーキテクチャ用
571
+ language=self.language, # 言語を設定
572
+ package=None,
573
+ imports=[],
574
+ classes=[],
575
+ methods=[],
576
+ fields=[],
577
+ annotations=[],
578
+ analysis_time=0.1,
579
+ success=True,
580
+ error_message=None,
581
+ )
582
+
583
+
584
+ def get_analysis_engine() -> UnifiedAnalysisEngine:
585
+ """
586
+ 統一解析エンジンのインスタンスを取得
587
+
588
+ Returns:
589
+ 統一解析エンジンのシングルトンインスタンス
590
+ """
591
+ return UnifiedAnalysisEngine()