# コア解析エンジン仕様

**文書番号:** SPEC-005  
**バージョン:** 1.0  
**作成日:** 2025-11-03  
**最終更新:** 2025-11-03

---

## 1. 概要

本文書は、Tree-sitter Analyzerのコア解析エンジン（AnalysisEngine）の詳細仕様を記述する。パース処理、クエリ実行、メトリクス計算の実装を明確化する。

---

## 2. アーキテクチャ

### 2.1 コンポーネント構成

```
┌─────────────────────────────────────┐
│      UnifiedAnalysisEngine          │
│  (統合解析エンジン - エントリーポイント)│
└─────────────────────────────────────┘
                ↓
┌─────────────────────────────────────┐
│        AnalysisEngine               │
│   (コア解析オーケストレーター)        │
│  ┌──────────────────────────────┐  │
│  │  Parser                      │  │
│  │  - Tree-sitter統合          │  │
│  │  - 言語検出                  │  │
│  └──────────────────────────────┘  │
│  ┌──────────────────────────────┐  │
│  │  QueryExecutor               │  │
│  │  - クエリ実行                │  │
│  │  - ノード抽出                │  │
│  └──────────────────────────────┘  │
│  ┌──────────────────────────────┐  │
│  │  PluginManager               │  │
│  │  - 言語プラグイン管理        │  │
│  └──────────────────────────────┘  │
└─────────────────────────────────────┘
```

---

## 3. Parser（パーサー）

### 3.1 責務
- Tree-sitterライブラリとの統合
- ソースコードのAST生成
- エラーハンドリング

### 3.2 主要メソッド

```python
class Parser:
    def parse(self, source_code: str, language: str) -> ParseResult:
        """ソースコードをパースしてASTを生成"""
        
    def parse_file(self, file_path: str, language: str | None = None) -> ParseResult:
        """ファイルを読み込んでパース"""
```

### 3.3 ParseResult構造

実装では`NamedTuple`として定義されています (`tree_sitter_analyzer/core/parser.py`):

```python
class ParseResult(NamedTuple):
    """
    Result of parsing operation containing tree and metadata.

    Attributes:
        tree: The parsed Tree-sitter tree (None if parsing failed)
        source_code: The source code that was parsed
        language: The programming language used for parsing
        file_path: Path to the file (if parsing from file)
        success: Whether parsing was successful
        error_message: Error message if parsing failed
    """

    tree: Tree | None
    source_code: str
    language: str
    file_path: str | None
    success: bool
    error_message: str | None
```

**特徴:**
- `NamedTuple`による不変性（immutable）
- 属性の順序が固定
- パフォーマンスが良い
- 型安全性が高い

---

## 4. QueryExecutor（クエリ実行エンジン）

### 4.1 責務
- Tree-sitterクエリの実行
- マッチしたノードの抽出
- クエリ結果の整形

### 4.2 クエリ実行

```python
class QueryExecutor:
    def execute_query(
        self,
        tree: Tree,
        query_string: str,
        language_obj: Any
    ) -> List[Capture]:
        """クエリを実行してマッチしたノードを返す"""
```

### 4.3 サポートされるクエリパターン

**関数定義抽出:**
```scheme
(function_definition
  name: (identifier) @function_name
) @function
```

**クラス定義抽出:**
```scheme
(class_definition
  name: (identifier) @class_name
) @class
```

**インポート文抽出:**
```scheme
(import_statement) @import
```

---

## 5. メトリクス計算

### 5.1 計算メトリクス一覧

| メトリクス | 説明 | 計算方法 | 実装状況 |
|----------|------|---------|---------|
| **total_lines** | 総行数 | ファイル全体の行数 | ✅ 実装済み |
| **code_lines** | コード行数 | コメント・空行を除く | ✅ 実装済み |
| **comment_lines** | コメント行数 | 単一行・複数行コメント | ✅ 実装済み |
| **blank_lines** | 空行数 | 空白のみの行 | ✅ 実装済み |
| **cyclomatic_complexity** | 循環的複雑度 | 分岐点の数 + 1 | ✅ 実装済み (関数/メソッド単位) |
| **cognitive_complexity** | 認知的複雑度 | ネスト深度考慮 | ⚠️ 未実装 |

**注意:**
- `cyclomatic_complexity`は各関数/メソッド単位で計算され、`Function`オブジェクトの`complexity_score`フィールドに格納されます
- ファイル全体の複雑度は各関数の複雑度の合計として計算可能です
- `cognitive_complexity`は現時点では実装されていません

---

### 5.2 行数計算アルゴリズム

```python
def calculate_file_metrics(file_path: str, language: str) -> dict:
    """ファイルメトリクスを計算"""
    with open(file_path, 'r', encoding='utf-8') as f:
        lines = f.readlines()
    
    code_lines = 0
    comment_lines = 0
    blank_lines = 0
    in_multiline_comment = False
    
    for line in lines:
        stripped = line.strip()
        
        # 空行チェック
        if not stripped:
            blank_lines += 1
            continue
        
        # コメントチェック（言語別）
        if language == 'Python':
            if stripped.startswith('#'):
                comment_lines += 1
            elif '"""' in stripped or "'''" in stripped:
                # 複数行コメント処理
                ...
            else:
                code_lines += 1
    
    return {
        'total_lines': len(lines),
        'code_lines': code_lines,
        'comment_lines': comment_lines,
        'blank_lines': blank_lines
    }
```

---

### 5.3 循環的複雑度（Cyclomatic Complexity）

**定義:**  
コードの複雑さを測る指標。分岐点（if, for, while等）の数に基づく。

**計算式:**
```
CC = E - N + 2P

E: エッジ数
N: ノード数
P: 連結成分数

簡易版: CC = (分岐点の数) + 1
```

**実装:**

各言語プラグインに`_calculate_complexity_optimized()`メソッドとして実装されています。

**Python実装例** (`tree_sitter_analyzer/languages/python_plugin.py`):

```python
def _calculate_complexity_optimized(self, node: "tree_sitter.Node") -> int:
    """Calculate cyclomatic complexity efficiently"""
    import re

    node_id = id(node)
    if node_id in self._complexity_cache:
        return self._complexity_cache[node_id]

    complexity = 1
    try:
        node_text = self._get_node_text_optimized(node).lower()
        keywords = [
            "if",
            "elif",
            "while",
            "for",
            "except",
            "and",
            "or",
            "with",
            "match",
            "case",
        ]
        for keyword in keywords:
            # More flexible keyword matching
            pattern = rf"\b{keyword}\b"
            matches = re.findall(pattern, node_text)
            complexity += len(matches)
    except Exception as e:
        log_debug(f"Failed to calculate complexity: {e}")

    self._complexity_cache[node_id] = complexity
    return complexity
```

**特徴:**
- ノードIDベースのキャッシュで効率化
- 正規表現によるキーワードマッチング
- 基本複雑度1から開始
- 各言語プラグイン（Java, TypeScript, JavaScript）にも同様の実装あり

**キーワードの定義:**
- Python: `if`, `elif`, `while`, `for`, `except`, `and`, `or`, `with`, `match`, `case`
- Java: 同様のキーワードを使用
- TypeScript/JavaScript: 同様のキーワードを使用

**複雑度の評価基準:**
- 1-10: シンプル（低リスク）
- 11-20: やや複雑（中リスク）
- 21-50: 複雑（高リスク）
- 50+: 非常に複雑（要リファクタリング）

---

### 5.4 認知的複雑度（Cognitive Complexity）

**実装状況:**  
⚠️ **注意:** 現在の実装では、認知的複雑度（Cognitive Complexity）の計算機能は**実装されていません**。
サイクロマティック複雑度のみが実装されています。

**理論的定義:**  
人間がコードを理解する難しさを測る指標。ネストの深さを考慮。

**計算ルール（理論）:**
1. 基本分岐: +1
2. ネストレベルごと: +1
3. 論理演算子: +1（同じレベルの連続は除く）

**今後の実装について:**

認知的複雑度を追加する場合は、各言語プラグインに以下のようなメソッドを実装する必要があります：

```python
def _calculate_cognitive_complexity(self, node: "tree_sitter.Node") -> int:
    """認知的複雑度を計算（未実装）"""
    complexity = 0
    nesting_level = 0
    
    def traverse(node: Node, level: int):
        nonlocal complexity
        
        if node.type in ['if_statement', 'for_statement', 'while_statement']:
            complexity += 1 + level  # 基本 + ネスト
            traverse_children(node, level + 1)
        elif node.type in ['elif_clause', 'else_clause']:
            complexity += 1  # ネストなし
            traverse_children(node, level)
        else:
            traverse_children(node, level)
    
    traverse(node, 0)
    return complexity
```

---

## 6. 要素抽出（Element Extraction）

### 6.1 CodeElement構造

```python
@dataclass
class CodeElement:
    element_type: str       # 'class', 'function', 'variable', 'import'
    name: str              # 要素名
    start_line: int        # 開始行
    end_line: int          # 終了行
    start_column: int      # 開始列
    end_column: int        # 終了列
    complexity_score: int  # 複雑度スコア（関数の場合）
    parent_name: str | None  # 親要素名（メソッドの場合はクラス名）
```

---

### 6.2 言語別抽出ロジック

#### Python
```python
def extract_python_elements(tree: Tree) -> List[CodeElement]:
    """Pythonの要素を抽出"""
    elements = []
    
    # クラス抽出
    class_query = """
    (class_definition
      name: (identifier) @class_name
    ) @class
    """
    
    # 関数抽出
    function_query = """
    (function_definition
      name: (identifier) @func_name
    ) @function
    """
    
    # クエリ実行して要素を生成
    ...
```

#### Java
```python
def extract_java_elements(tree: Tree) -> List[CodeElement]:
    """Javaの要素を抽出"""
    elements = []
    
    # クラス・インターフェース抽出
    class_query = """
    (class_declaration
      name: (identifier) @class_name
    ) @class
    """
    
    # メソッド抽出
    method_query = """
    (method_declaration
      name: (identifier) @method_name
    ) @method
    """
    
    ...
```

---

## 7. パフォーマンス最適化

### 7.1 パフォーマンスモニター

実装は `tree_sitter_analyzer/core/analysis_engine.py` に含まれています:

```python
class PerformanceMonitor:
    """Performance monitoring (simplified version)"""

    def __init__(self) -> None:
        self._last_duration: float = 0.0
        self._monitoring_active: bool = False
        self._operation_stats: dict[str, Any] = {}
        self._total_operations: int = 0

    def measure_operation(self, operation_name: str) -> "PerformanceContext":
        """Return measurement context for operation"""
        return PerformanceContext(operation_name, self)

    def get_last_duration(self) -> float:
        """Get last operation time"""
        return self._last_duration

    def start_monitoring(self) -> None:
        """Start performance monitoring"""
        self._monitoring_active = True

    def stop_monitoring(self) -> None:
        """Stop performance monitoring"""
        self._monitoring_active = False

    def get_operation_stats(self) -> dict[str, Any]:
        """Get operation statistics"""
        return self._operation_stats.copy()

    def get_performance_summary(self) -> dict[str, Any]:
        """Get performance summary"""
        return {
            "total_operations": self._total_operations,
            "monitoring_active": self._monitoring_active,
            "last_duration": self._last_duration,
            "operation_count": len(self._operation_stats),
        }

    def record_operation(self, operation_name: str, duration: float) -> None:
        """Record operation"""
        if self._monitoring_active:
            if operation_name not in self._operation_stats:
                self._operation_stats[operation_name] = {
                    "count": 0,
                    "total_time": 0.0,
                    "avg_time": 0.0,
                    "min_time": float("inf"),
                    "max_time": 0.0,
                }

            stats = self._operation_stats[operation_name]
            stats["count"] += 1
            stats["total_time"] += duration
            stats["avg_time"] = stats["total_time"] / stats["count"]
            stats["min_time"] = min(stats["min_time"], duration)
            stats["max_time"] = max(stats["max_time"], duration)

            self._total_operations += 1

    def clear_metrics(self) -> None:
        """Clear collected metrics"""
        self._operation_stats.clear()
        self._total_operations = 0
        self._last_duration = 0.0


class PerformanceContext:
    """Performance measurement context"""

    def __init__(self, operation_name: str, monitor: PerformanceMonitor) -> None:
        self.operation_name = operation_name
        self.monitor = monitor
        self.start_time: float = 0.0

    def __enter__(self) -> "PerformanceContext":
        import time

        self.start_time = time.time()
        return self

    def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
        import time

        duration = time.time() - self.start_time
        self.monitor._set_duration(duration)
        self.monitor.record_operation(self.operation_name, duration)
        log_performance(self.operation_name, duration, "Operation completed")
```

**使用例:**
```python
from tree_sitter_analyzer.core.analysis_engine import UnifiedAnalysisEngine

engine = UnifiedAnalysisEngine.get_instance()
monitor = engine._performance_monitor

# 監視開始
monitor.start_monitoring()

# 操作の測定
with monitor.measure_operation('parse'):
    result = engine.analyze_file('large_file.py', AnalysisRequest(
        file_path='large_file.py',
        language='python'
    ))

# 統計取得
summary = monitor.get_performance_summary()
print(f"Total operations: {summary['total_operations']}")
print(f"Last duration: {summary['last_duration']:.3f}s")

stats = monitor.get_operation_stats()
if 'parse' in stats:
    parse_stats = stats['parse']
    print(f"Parse count: {parse_stats['count']}")
    print(f"Avg time: {parse_stats['avg_time']:.3f}s")
    print(f"Min time: {parse_stats['min_time']:.3f}s")
    print(f"Max time: {parse_stats['max_time']:.3f}s")
```

---

### 7.2 最適化手法

**1. 遅延評価:**
- AST情報は必要時のみ生成
- `include_details=False`でメトリクスのみ返却

**2. ストリーミング処理:**
- 大規模ファイルは分割処理
- メモリ使用量を抑制

**3. 並行処理:**
- 複数ファイルの並行解析
- セマフォによる同時実行数制御

---

## 8. エラーハンドリング

### 8.1 エラータイプ

| エラー | 原因 | 処理 |
|-------|------|------|
| **ParseError** | 構文エラー | 部分的なASTを返却 |
| **FileNotFoundError** | ファイル不在 | エラー結果を返却 |
| **UnsupportedLanguageError** | 未対応言語 | エラー結果を返却 |
| **TimeoutError** | タイムアウト | 処理を中断 |

---

### 8.2 エラーハンドリング実装

```python
def analyze_file(self, file_path: str) -> AnalysisResult:
    """ファイル解析（エラーハンドリング付き）"""
    try:
        parse_result = self.parser.parse_file(file_path)
        
        if not parse_result.success:
            return self._create_error_result(
                file_path,
                error=parse_result.error_message
            )
        
        return self._perform_analysis(parse_result)
    
    except FileNotFoundError:
        return self._create_error_result(
            file_path,
            error='File not found'
        )
    
    except UnsupportedLanguageError as e:
        return self._create_error_result(
            file_path,
            error=f'Unsupported language: {e}'
        )
    
    except Exception as e:
        logger.exception('Unexpected error')
        return self._create_error_result(
            file_path,
            error=f'Internal error: {e}'
        )
```

---

## 9. テスト戦略

### 9.1 単体テスト

**パーサーテスト:**
```python
def test_parser_python():
    parser = Parser()
    source = "def hello(): pass"
    
    result = parser.parse(source, 'Python')
    
    assert result.success
    assert result.tree is not None
    assert result.language == 'Python'
```

**メトリクス計算テスト:**
```python
def test_calculate_metrics():
    engine = AnalysisEngine()
    result = engine.analyze_file('test_file.py')
    
    assert result.metrics['total_lines'] > 0
    assert result.metrics['code_lines'] > 0
```

---

### 9.2 統合テスト

**E2Eテスト:**
```python
def test_e2e_analysis():
    engine = AnalysisEngine()
    
    # 実ファイル解析
    result = engine.analyze_file('examples/sample.py')
    
    assert result.success
    assert len(result.elements) > 0
    assert result.metrics['cyclomatic_complexity'] >= 1
```

---

## 10. 改訂履歴

| バージョン | 日付 | 変更内容 | 承認者 |
|-----------|------|---------|--------|
| 1.0 | 2025-11-03 | 初版作成 | aisheng.yu |
| 1.1 | 2025-01-XX | 実装コードとの照合・修正<br>- ParseResult: dataclassからNamedTupleへ修正<br>- 循環的複雑度: 実際の実装(_calculate_complexity_optimized)を反映<br>- 認知的複雑度: 未実装であることを明記<br>- PerformanceMonitor: 実際の実装詳細を追加 | GitHub Copilot |

---

**最終更新:** 2025-11-03  
**管理者:** aisheng.yu  
**連絡先:** aimasteracc@gmail.com
