# 言語プラグイン仕様

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

---

## 1. 概要

本文書は、Tree-sitter Analyzerの言語プラグインシステムの仕様を記述する。新言語のサポート追加方法、プラグインAPI、実装ガイドラインを明確化する。

---

## 2. プラグインアーキテクチャ

### 2.1 設計原則

- ✅ **プラグインベース**: 言語ごとに独立したプラグイン
- ✅ **統一インターフェース**: `LanguagePlugin`基底クラス
- ✅ **拡張性**: 新言語追加が容易
- ✅ **独立性**: プラグイン間の依存なし

---

### 2.2 プラグイン構造

**実際の構造** (`tree_sitter_analyzer/`)

```
tree_sitter_analyzer/
├── plugins/
│   ├── base.py                  # 基底クラス (LanguagePlugin, ElementExtractor)
│   ├── manager.py               # プラグイン管理 (PluginManager)
│   └── __init__.py
├── languages/
│   ├── python_plugin.py         # Pythonプラグイン + Extractor
│   ├── java_plugin.py           # Javaプラグイン + Extractor
│   ├── javascript_plugin.py     # JavaScriptプラグイン + Extractor
│   ├── typescript_plugin.py     # TypeScriptプラグイン + Extractor
│   ├── css_plugin.py            # CSSプラグイン + Extractor
│   ├── html_plugin.py           # HTMLプラグイン + Extractor
│   ├── markdown_plugin.py       # Markdownプラグイン + Extractor
│   └── __init__.py
└── ...
```

**特徴:**
- `plugins/`に基底クラスと管理機能
- `languages/`に各言語の実装
- 各プラグインファイルには`Plugin`クラスと`Extractor`クラスの両方を含む

---

## 3. LanguagePlugin基底クラス

### 3.1 インターフェース定義

**実際の実装** (`tree_sitter_analyzer/plugins/base.py`):

```python
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from ..core.analysis_engine import AnalysisRequest
    from ..models import AnalysisResult

class LanguagePlugin(ABC):
    """
    Abstract base class for language-specific plugins.

    Language plugins provide language-specific functionality including
    element extraction, file extension mapping, and language identification.
    """

    @abstractmethod
    def get_language_name(self) -> str:
        """
        Return the name of the programming language this plugin supports.

        Returns:
            Language name (e.g., "java", "python", "javascript")
        """
        pass

    @abstractmethod
    def get_file_extensions(self) -> list[str]:
        """
        Return list of file extensions this plugin supports.

        Returns:
            List of file extensions (e.g., [".java", ".class"])
        """
        pass

    @abstractmethod
    def create_extractor(self) -> ElementExtractor:
        """
        Create and return an element extractor for this language.

        Returns:
            ElementExtractor instance for this language
        """
        pass

    @abstractmethod
    async def analyze_file(
        self, file_path: str, request: "AnalysisRequest"
    ) -> "AnalysisResult":
        """
        Analyze a file and return analysis results.

        Args:
            file_path: Path to the file to analyze
            request: Analysis request with configuration

        Returns:
            AnalysisResult containing extracted information
        """
        pass

    def get_supported_element_types(self) -> list[str]:
        """
        Return list of supported CodeElement types.

        Returns:
            List of element types (e.g., ["function", "class", "variable"])
        """
        return ["function", "class", "variable", "import"]

    def get_queries(self) -> dict[str, str]:
        """
        Return language-specific tree-sitter queries.

        Returns:
            Dictionary mapping query names to query strings
        """
        return {}

    def execute_query_strategy(
        self, query_key: str | None, language: str
    ) -> str | None:
        """
        Execute query strategy for this language plugin.

        Args:
            query_key: Query key to execute
            language: Programming language

        Returns:
            Query string or None if not supported
        """
        queries = self.get_queries()
        return queries.get(query_key) if query_key else None
```

**主要な違い:**
- `@property`デコレータの代わりに通常のメソッド
- `get_parser()`は削除（パーサーは`Parser`クラスで管理）
- `extract_elements()`は削除（`ElementExtractor`クラスで実装）
- `calculate_complexity()`は削除（`ElementExtractor`内で実装）
- `analyze_file()`が非同期メソッドとして追加
- `execute_query_strategy()`が追加

### 3.2 ElementExtractor基底クラス

**実装** (`tree_sitter_analyzer/plugins/base.py`):

```python
class ElementExtractor(ABC):
    """
    Abstract base class for language-specific element extractors.

    Element extractors are responsible for parsing ASTs and extracting
    meaningful code elements like functions, classes, variables, etc.
    """

    def __init__(self) -> None:
        """Initialize the element extractor."""
        self.current_file: str = ""  # Current file being processed

    @abstractmethod
    def extract_functions(
        self, tree: "tree_sitter.Tree", source_code: str
    ) -> list[ModelFunction]:
        """
        Extract function definitions from the syntax tree.

        Args:
            tree: Tree-sitter AST
            source_code: Original source code

        Returns:
            List of extracted function objects
        """
        pass

    @abstractmethod
    def extract_classes(
        self, tree: "tree_sitter.Tree", source_code: str
    ) -> list[ModelClass]:
        """
        Extract class definitions from the syntax tree.

        Args:
            tree: Tree-sitter AST
            source_code: Original source code

        Returns:
            List of extracted class objects
        """
        pass

    @abstractmethod
    def extract_variables(
        self, tree: "tree_sitter.Tree", source_code: str
    ) -> list[ModelVariable]:
        """
        Extract variable declarations from the syntax tree.

        Args:
            tree: Tree-sitter AST
            source_code: Original source code

        Returns:
            List of extracted variable objects
        """
        pass

    @abstractmethod
    def extract_imports(
        self, tree: "tree_sitter.Tree", source_code: str
    ) -> list[ModelImport]:
        """
        Extract import statements from the syntax tree.

        Args:
            tree: Tree-sitter AST
            source_code: Original source code

        Returns:
            List of extracted import objects
        """
        pass

    def extract_all_elements(
        self, tree: "tree_sitter.Tree", source_code: str
    ) -> list[CodeElement]:
        """
        Extract all code elements from the syntax tree.

        Args:
            tree: Tree-sitter AST
            source_code: Original source code

        Returns:
            List of all extracted code elements
        """
        elements: list[CodeElement] = []
        elements.extend(self.extract_functions(tree, source_code))
        elements.extend(self.extract_classes(tree, source_code))
        elements.extend(self.extract_variables(tree, source_code))
        elements.extend(self.extract_imports(tree, source_code))
        return elements
```

**責務:**
- Tree-sitter ASTから言語固有の要素を抽出
- 関数、クラス、変数、インポートの抽出
- 複雑度計算（実装クラスで行う）

---

## 4. サポート言語とプラグイン実装

### 4.1 Python プラグイン

**実際の実装** (`tree_sitter_analyzer/languages/python_plugin.py`):

```python
import tree_sitter
import tree_sitter_python as tspython
from ..plugins.base import LanguagePlugin, ElementExtractor
from ..models import Function, Class, Variable, Import

class PythonElementExtractor(ElementExtractor):
    """Python用の要素抽出器"""
    # ... 抽出ロジック実装 ...
    
    def extract_functions(
        self, tree: "tree_sitter.Tree", source_code: str
    ) -> list[Function]:
        """関数を抽出"""
        # 実装...
    
    def extract_classes(
        self, tree: "tree_sitter.Tree", source_code: str
    ) -> list[Class]:
        """クラスを抽出"""
        # 実装...
    
    def _calculate_complexity_optimized(self, node: "tree_sitter.Node") -> int:
        """循環的複雑度を効率的に計算"""
        complexity = 1
        node_text = self._get_node_text_optimized(node).lower()
        keywords = [
            "if", "elif", "while", "for", "except",
            "and", "or", "with", "match", "case",
        ]
        for keyword in keywords:
            pattern = rf"\b{keyword}\b"
            matches = re.findall(pattern, node_text)
            complexity += len(matches)
        return complexity


class PythonPlugin(LanguagePlugin):
    """Python language plugin for the new architecture"""

    def __init__(self) -> None:
        """Initialize the Python plugin"""
        super().__init__()
        self._language_cache: tree_sitter.Language | None = None
        self._extractor: PythonElementExtractor | None = None
        self.language = "python"
        self.extractor = self.get_extractor()

    def get_language_name(self) -> str:
        """Return the name of the programming language this plugin supports"""
        return "python"

    def get_file_extensions(self) -> list[str]:
        """Return list of file extensions this plugin supports"""
        return [".py", ".pyw", ".pyi"]

    def create_extractor(self) -> ElementExtractor:
        """Create and return an element extractor for this language"""
        return PythonElementExtractor()

    def get_extractor(self) -> ElementExtractor:
        """Get the cached extractor instance, creating it if necessary"""
        if self._extractor is None:
            self._extractor = PythonElementExtractor()
        return self._extractor

    def get_tree_sitter_language(self) -> Optional["tree_sitter.Language"]:
        """Get the Tree-sitter language object for Python"""
        if self._language_cache is None:
            try:
                language_capsule = tspython.language()
                self._language_cache = tree_sitter.Language(language_capsule)
            except Exception as e:
                log_error(f"Failed to load Python language: {e}")
                return None
        return self._language_cache

    def get_supported_queries(self) -> list[str]:
        """Get list of supported query names for this language"""
        return [
            "function", "class", "variable", "import",
            "async_function", "method", "decorator", "exception",
            "comprehension", "lambda", "context_manager", "type_hint",
            "docstring", "django_model", "flask_route", "fastapi_endpoint",
        ]

    async def analyze_file(
        self, file_path: str, request: "AnalysisRequest"
    ) -> "AnalysisResult":
        """
        Analyze a Python file and return analysis results.
        
        Args:
            file_path: Path to the Python file
            request: Analysis request with configuration
            
        Returns:
            AnalysisResult containing extracted information
        """
        # 実装...
        pass
```

**特徴:**
- `ElementExtractor`と`Plugin`が分離
- `Extractor`が実際の抽出ロジックを担当
- `Plugin`がメタデータとエントリーポイントを提供
- 複雑度計算は`Extractor`内で実装
            if child.type in decision_nodes:
                complexity += 1
            complexity += self.calculate_complexity(child)
        
        return complexity
```

---

### 4.2 Java プラグイン

```python
import tree_sitter_java

class JavaPlugin(LanguagePlugin):
    """Javaプラグイン"""
    
    @property
    def name(self) -> str:
        return "Java"
    
    @property
    def file_extensions(self) -> List[str]:
        return [".java"]
    
    def get_parser(self) -> Parser:
        parser = Parser()
        parser.set_language(tree_sitter_java.language())
        return parser
    
    def extract_elements(self, tree: Tree) -> List[CodeElement]:
        """Javaの要素を抽出"""
        elements = []
        
        # クラス・インターフェース抽出
        class_query = """
        [
          (class_declaration
            name: (identifier) @name
          ) @class
          (interface_declaration
            name: (identifier) @name
          ) @interface
        ]
        """
        
        # メソッド抽出
        method_query = """
        (method_declaration
          name: (identifier) @method_name
        ) @method
        """
        
        # フィールド抽出
        field_query = """
        (field_declaration
          declarator: (variable_declarator
            name: (identifier) @field_name
          )
        ) @field
        """
        
        elements.extend(self._extract_with_query(tree, class_query))
        elements.extend(self._extract_with_query(tree, method_query))
        elements.extend(self._extract_with_query(tree, field_query))
        
        return elements
    
    def calculate_complexity(self, node: Node) -> int:
        """Javaの循環的複雑度を計算"""
        complexity = 1
        
        decision_nodes = [
            'if_statement',
            'for_statement',
            'while_statement',
            'do_statement',
            'switch_statement',
            'catch_clause',
            'conditional_expression',
            'binary_expression'  # && や ||
        ]
        
        for child in node.children:
            if child.type in decision_nodes:
                complexity += 1
            complexity += self.calculate_complexity(child)
        
        return complexity
```

---

### 4.3 JavaScript/TypeScript プラグイン

```python
import tree_sitter_javascript
import tree_sitter_typescript

class JavaScriptPlugin(LanguagePlugin):
    """JavaScriptプラグイン"""
    
    @property
    def name(self) -> str:
        return "JavaScript"
    
    @property
    def file_extensions(self) -> List[str]:
        return [".js", ".jsx", ".mjs"]
    
    def get_parser(self) -> Parser:
        parser = Parser()
        parser.set_language(tree_sitter_javascript.language())
        return parser
    
    def extract_elements(self, tree: Tree) -> List[CodeElement]:
        """JavaScriptの要素を抽出"""
        elements = []
        
        # クラス抽出
        class_query = """
        (class_declaration
          name: (identifier) @class_name
        ) @class
        """
        
        # 関数抽出（通常の関数とアロー関数）
        function_query = """
        [
          (function_declaration
            name: (identifier) @func_name
          ) @function
          (arrow_function) @arrow_function
        ]
        """
        
        elements.extend(self._extract_with_query(tree, class_query))
        elements.extend(self._extract_with_query(tree, function_query))
        
        return elements

class TypeScriptPlugin(JavaScriptPlugin):
    """TypeScriptプラグイン（JavaScriptを継承）"""
    
    @property
    def name(self) -> str:
        return "TypeScript"
    
    @property
    def file_extensions(self) -> List[str]:
        return [".ts", ".tsx"]
    
    def get_parser(self) -> Parser:
        parser = Parser()
        parser.set_language(tree_sitter_typescript.language_typescript())
        return parser
```

---

## 5. 新言語プラグインの作成ガイド

### 5.1 ステップバイステップガイド

#### ステップ1: tree-sitterライブラリの確認

```bash
# PyPIで利用可能か確認
pip search tree-sitter-<language>

# 例: Ruby
pip search tree-sitter-ruby
```

#### ステップ2: プラグインファイル作成

```python
# tree_sitter_analyzer/plugins/ruby_plugin.py

import tree_sitter_ruby
from .base_plugin import LanguagePlugin

class RubyPlugin(LanguagePlugin):
    @property
    def name(self) -> str:
        return "Ruby"
    
    @property
    def file_extensions(self) -> List[str]:
        return [".rb"]
    
    def get_parser(self) -> Parser:
        parser = Parser()
        parser.set_language(tree_sitter_ruby.language())
        return parser
    
    def extract_elements(self, tree: Tree) -> List[CodeElement]:
        # Ruby固有の抽出ロジック
        pass
    
    def calculate_complexity(self, node: Node) -> int:
        # Ruby固有の複雑度計算
        pass
```

#### ステップ3: プラグイン登録

```python
# tree_sitter_analyzer/plugin_manager.py

class PluginManager:
    def __init__(self):
        self.plugins = {
            'Python': PythonPlugin(),
            'Java': JavaPlugin(),
            'JavaScript': JavaScriptPlugin(),
            'Ruby': RubyPlugin(),  # 追加
        }
```

#### ステップ4: テスト作成

```python
# tests/test_ruby_plugin.py

def test_ruby_plugin():
    plugin = RubyPlugin()
    
    # パーサーテスト
    parser = plugin.get_parser()
    assert parser is not None
    
    # 解析テスト
    source = """
    class MyClass
      def my_method
        puts "Hello"
      end
    end
    """
    
    tree = parser.parse(bytes(source, 'utf8'))
    elements = plugin.extract_elements(tree)
    
    assert len(elements) == 2  # 1クラス + 1メソッド
```

#### ステップ5: ドキュメント更新

- `README.md`: サポート言語リストに追加
- `docs/ja/specifications/06_言語プラグイン仕様.md`: プラグイン情報追加

---

## 6. プラグイン開発のベストプラクティス

### 6.1 クエリの最適化

**効率的なクエリ:**
```scheme
; ❌ 非効率（全ノード走査）
(identifier) @all_identifiers

; ✅ 効率的（特定ノードのみ）
(function_definition
  name: (identifier) @func_name
) @function
```

---

### 6.2 エラーハンドリング

```python
def extract_elements(self, tree: Tree) -> List[CodeElement]:
    """エラーハンドリング付き要素抽出"""
    try:
        elements = []
        
        # クエリ実行
        results = self._execute_query(tree, self.class_query)
        
        for result in results:
            try:
                element = self._create_element(result)
                elements.append(element)
            except Exception as e:
                logger.warning(f"Failed to create element: {e}")
                continue
        
        return elements
    
    except Exception as e:
        logger.error(f"Failed to extract elements: {e}")
        return []
```

---

### 6.3 言語固有の最適化

**Python: Docstring処理**
```python
def extract_docstring(self, node: Node) -> str | None:
    """関数/クラスのdocstringを抽出"""
    if node.children and node.children[0].type == 'expression_statement':
        expr = node.children[0]
        if expr.children and expr.children[0].type == 'string':
            return expr.children[0].text.decode('utf8')
    return None
```

**Java: アノテーション処理**
```python
def extract_annotations(self, node: Node) -> List[str]:
    """メソッド/クラスのアノテーションを抽出"""
    annotations = []
    for child in node.children:
        if child.type == 'marker_annotation':
            name_node = child.child_by_field_name('name')
            if name_node:
                annotations.append(name_node.text.decode('utf8'))
    return annotations
```

---

## 7. プラグインマネージャー

### 7.1 自動検出と登録

```python
class PluginManager:
    """プラグイン管理クラス"""
    
    def __init__(self):
        self.plugins: Dict[str, LanguagePlugin] = {}
        self._discover_plugins()
    
    def _discover_plugins(self):
        """プラグインを自動検出"""
        plugin_dir = Path(__file__).parent / 'plugins'
        
        for file in plugin_dir.glob('*_plugin.py'):
            module_name = file.stem
            try:
                module = importlib.import_module(
                    f'tree_sitter_analyzer.plugins.{module_name}'
                )
                
                # プラグインクラスを検出
                for name, obj in inspect.getmembers(module, inspect.isclass):
                    if issubclass(obj, LanguagePlugin) and obj != LanguagePlugin:
                        plugin = obj()
                        self.plugins[plugin.name] = plugin
                        logger.info(f"Loaded plugin: {plugin.name}")
            
            except Exception as e:
                logger.warning(f"Failed to load plugin {module_name}: {e}")
    
    def get_plugin(self, language: str) -> LanguagePlugin | None:
        """言語のプラグインを取得"""
        return self.plugins.get(language)
    
    def get_supported_languages(self) -> List[str]:
        """サポート言語リストを取得"""
        return list(self.plugins.keys())
```

---

## 8. サポート言語一覧

| 言語 | プラグインクラス | 拡張子 | tree-sitterライブラリ | 状態 |
|------|----------------|--------|---------------------|------|
| Python | `PythonPlugin` | .py, .pyi | `tree-sitter-python` | ✅ 完全対応 |
| Java | `JavaPlugin` | .java | `tree-sitter-java` | ✅ 完全対応 |
| JavaScript | `JavaScriptPlugin` | .js, .jsx | `tree-sitter-javascript` | ✅ 完全対応 |
| TypeScript | `TypeScriptPlugin` | .ts, .tsx | `tree-sitter-typescript` | ✅ 完全対応 |
| C | `CPlugin` | .c, .h | `tree-sitter-c` | ✅ 完全対応 |
| C++ | `CppPlugin` | .cpp, .hpp | `tree-sitter-cpp` | ✅ 完全対応 |
| Go | `GoPlugin` | .go | `tree-sitter-go` | ✅ 完全対応 |
| Rust | `RustPlugin` | .rs | `tree-sitter-rust` | ✅ 完全対応 |
| C# | `CSharpPlugin` | .cs | `tree-sitter-c-sharp` | ✅ 完全対応 |
| PHP | `PhpPlugin` | .php | `tree-sitter-php` | ✅ 完全対応 |
| Ruby | `RubyPlugin` | .rb | `tree-sitter-ruby` | 🚧 計画中 |

---

## 9. 改訂履歴

| バージョン | 日付 | 変更内容 | 承認者 |
|-----------|------|---------|--------|
| 1.0 | 2025-11-03 | 初版作成 | aisheng.yu |
| 1.1 | 2025-01-XX | 実装コードとの照合・修正<br>- プラグイン構造: plugins/とlanguages/の分離を反映<br>- LanguagePlugin: 実際のインターフェース定義に修正<br>- ElementExtractor: 基底クラスの説明を追加<br>- Pythonプラグイン実装例を実際のコードに修正 | GitHub Copilot |

---

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