From 18eea24425f294ebf435184597e6ed88c48e46c8 Mon Sep 17 00:00:00 2001 From: johnhuang316 <134570882+johnhuang316@users.noreply.github.com> Date: Mon, 18 Aug 2025 10:34:00 +0800 Subject: [PATCH 1/2] refactor: remove deprecated SCIP relationship implementation and related files --- SCIP_RELATIONSHIP_IMPLEMENTATION_PLAN.md | 284 ------------ diff.txt | Bin 551926 -> 0 bytes parse_scip_index.py | 95 ---- temp_js_end.py | 1 - temp_js_strategy.py | 535 ----------------------- 5 files changed, 915 deletions(-) delete mode 100644 SCIP_RELATIONSHIP_IMPLEMENTATION_PLAN.md delete mode 100644 diff.txt delete mode 100644 parse_scip_index.py delete mode 100644 temp_js_end.py delete mode 100644 temp_js_strategy.py diff --git a/SCIP_RELATIONSHIP_IMPLEMENTATION_PLAN.md b/SCIP_RELATIONSHIP_IMPLEMENTATION_PLAN.md deleted file mode 100644 index cbed682..0000000 --- a/SCIP_RELATIONSHIP_IMPLEMENTATION_PLAN.md +++ /dev/null @@ -1,284 +0,0 @@ -# SCIP 關係圖實施計畫 - -**版本**: 1.0 -**日期**: 2025-01-14 -**狀態**: 規劃階段 - -## 📋 問題分析 - -### 當前狀況 -- ✅ SCIP Protocol Buffer 結構完整實現 -- ✅ 符號定義和出現位置正確處理 -- ❌ **關鍵缺失**: SCIP Relationship 功能完全未實現 -- ❌ 內部 `CallRelationships` 與標準 SCIP `Relationship` 完全分離 - -### 影響評估 -- **合規性**: 目前僅 60-70% 符合 SCIP 標準 -- **功能性**: 關係圖和跨符號導航功能不可用 -- **兼容性**: 無法與標準 SCIP 工具鏈集成 - -## 🎯 目標 - -### 主要目標 -1. **100% SCIP 標準合規性**: 完整實現 `scip_pb2.Relationship` 支援 -2. **關係圖功能**: 啟用函數調用、繼承、實現等關係追蹤 -3. **多語言支援**: 6 種程式語言的關係提取 -4. **向後兼容**: 不破壞現有功能 - -### 成功指標 -- ✅ 所有符號包含正確的 SCIP Relationship 信息 -- ✅ 通過官方 SCIP 驗證工具檢查 -- ✅ 關係查詢 API 正常運作 -- ✅ 性能影響 < 20% - -## 🏗️ 技術架構 - -### 當前架構問題 -``` -[CallRelationships (內部格式)] ❌ 斷層 ❌ [SCIP Relationship (標準格式)] -``` - -### 目標架構 -``` -[程式碼分析] → [關係提取] → [關係管理器] → [SCIP Relationship] → [SymbolInformation] -``` - -### 核心組件 - -#### 1. 關係管理器 (`relationship_manager.py`) -```python -class SCIPRelationshipManager: - """SCIP 關係轉換和管理核心""" - - def create_relationship(self, target_symbol: str, relationship_type: RelationshipType) -> scip_pb2.Relationship - def add_relationships_to_symbol(self, symbol_info: scip_pb2.SymbolInformation, relationships: List[Relationship]) - def convert_call_relationships(self, call_rels: CallRelationships) -> List[scip_pb2.Relationship] -``` - -#### 2. 關係類型定義 (`relationship_types.py`) -```python -class RelationshipType(Enum): - CALLS = "calls" # 函數調用關係 - INHERITS = "inherits" # 繼承關係 - IMPLEMENTS = "implements" # 實現關係 - REFERENCES = "references" # 引用關係 - TYPE_DEFINITION = "type_definition" # 類型定義關係 -``` - -## 📁 檔案修改計畫 - -### 🆕 新增檔案 (4個) - -#### 核心組件 -``` -src/code_index_mcp/scip/core/ -├── relationship_manager.py # 關係轉換核心 (優先級 1) -└── relationship_types.py # 關係類型定義 (優先級 1) -``` - -#### 測試檔案 -``` -tests/ -├── scip/test_relationship_manager.py # 單元測試 (優先級 3) -└── integration/test_scip_relationships.py # 整合測試 (優先級 3) -``` - -### 🔄 修改現有檔案 (9個) - -#### 核心系統 -``` -src/code_index_mcp/scip/core/ -└── local_reference_resolver.py # 關係存儲和查詢 (優先級 1) -``` - -#### 策略層 -``` -src/code_index_mcp/scip/strategies/ -├── base_strategy.py # 基礎關係處理 (優先級 1) -├── python_strategy.py # Python 關係提取 (優先級 2) -├── javascript_strategy.py # JavaScript 關係提取 (優先級 2) -├── java_strategy.py # Java 關係提取 (優先級 2) -├── objective_c_strategy.py # Objective-C 關係提取 (優先級 2) -├── zig_strategy.py # Zig 關係提取 (優先級 2) -└── fallback_strategy.py # 後備關係處理 (優先級 2) -``` - -#### 分析工具 -``` -src/code_index_mcp/tools/scip/ -├── symbol_definitions.py # 關係數據結構增強 (優先級 2) -└── scip_symbol_analyzer.py # 關係分析整合 (優先級 2) -``` - -## 🗓️ 實施時程 - -### 階段 1:核心基礎 (第1-2週) - 優先級 1 -- [ ] **Week 1.1**: 創建 `relationship_manager.py` - - SCIP Relationship 創建和轉換邏輯 - - 關係類型映射功能 - - 基礎 API 設計 - -- [ ] **Week 1.2**: 創建 `relationship_types.py` - - 內部關係類型枚舉定義 - - SCIP 標準關係映射 - - 關係驗證邏輯 - -- [ ] **Week 2.1**: 修改 `base_strategy.py` - - 新增 `_create_scip_relationships` 方法 - - 修改 `_create_scip_symbol_information` 加入關係處理 - - 新增抽象方法 `_build_symbol_relationships` - -- [ ] **Week 2.2**: 更新 `local_reference_resolver.py` - - 新增關係存儲功能 - - 實現 `add_symbol_relationship` 方法 - - 實現 `get_symbol_relationships` 方法 - -### 階段 2:語言實現 (第3-4週) - 優先級 2 - -#### Week 3: 主要語言策略 -- [ ] **Week 3.1**: Python 策略 (`python_strategy.py`) - - 函數調用關係提取 - - 類繼承關係檢測 - - 方法重寫關係處理 - -- [ ] **Week 3.2**: JavaScript 策略 (`javascript_strategy.py`) - - 函數調用和原型鏈關係 - - ES6 類繼承關係 - - 模組導入關係 - -#### Week 4: 其他語言策略 -- [ ] **Week 4.1**: Java 策略 (`java_strategy.py`) - - 類繼承和介面實現關係 - - 方法調用關係 - - 包導入關係 - -- [ ] **Week 4.2**: Objective-C 和 Zig 策略 - - Objective-C 協議和繼承關係 - - Zig 結構體和函數關係 - - 後備策略更新 - -- [ ] **Week 4.3**: 工具層更新 - - 更新 `symbol_definitions.py` - - 整合 `scip_symbol_analyzer.py` - -### 階段 3:測試驗證 (第5週) - 優先級 3 -- [ ] **Week 5.1**: 單元測試 - - 關係管理器測試 - - 關係類型轉換測試 - - 各語言策略關係提取測試 - -- [ ] **Week 5.2**: 整合測試 - - 端到端關係功能測試 - - 多語言項目關係測試 - - 性能回歸測試 - -- [ ] **Week 5.3**: SCIP 合規性驗證 - - 使用官方 SCIP 工具驗證 - - 關係格式正確性檢查 - - 兼容性測試 - -### 階段 4:優化完善 (第6週) - 優先級 4 -- [ ] **Week 6.1**: 性能優化 - - 關係查詢 API 優化 - - 記憶體使用優化 - - 大型項目支援測試 - -- [ ] **Week 6.2**: 文檔和工具 - - 更新 ARCHITECTURE.md - - 更新 API 文檔 - - 使用範例和指南 - -- [ ] **Week 6.3**: 發布準備 - - 版本號更新 - - 變更日誌準備 - - 向後兼容性最終檢查 - -## 🧪 測試策略 - -### 單元測試範圍 -```python -# test_relationship_manager.py -def test_create_scip_relationship() -def test_convert_call_relationships() -def test_relationship_type_mapping() - -# test_python_relationships.py -def test_function_call_extraction() -def test_class_inheritance_detection() -def test_method_override_relationships() -``` - -### 整合測試範圍 -```python -# test_scip_relationships.py -def test_end_to_end_relationship_flow() -def test_multi_language_relationship_support() -def test_cross_file_relationship_resolution() -def test_scip_compliance_validation() -``` - -### 測試數據 -- 使用現有 `test/sample-projects/` 中的範例項目 -- 新增特定關係測試案例 -- 包含邊界情況和錯誤處理測試 - -## 📊 風險評估與緩解 - -### 高風險項目 -1. **性能影響**: 關係處理可能影響索引速度 - - **緩解**: 增量關係更新、並行處理 - -2. **複雜度增加**: 多語言關係邏輯複雜 - - **緩解**: 分階段實施、詳細測試 - -3. **向後兼容**: 現有 API 可能受影響 - - **緩解**: 保持現有接口、漸進式更新 - -### 中風險項目 -1. **SCIP 標準理解**: 關係映射可能不精確 - - **緩解**: 參考官方實現、社群驗證 - -2. **語言特性差異**: 不同語言關係模型差異大 - - **緩解**: 分語言設計、彈性架構 - -## 🚀 預期成果 - -### 功能改進 -- ✅ 完整的 SCIP 關係圖支援 -- ✅ 跨文件符號導航功能 -- ✅ 與標準 SCIP 工具鏈兼容 -- ✅ 6 種程式語言的關係分析 - -### 合規性提升 -- **當前**: 60-70% SCIP 標準合規 -- **目標**: 95%+ SCIP 標準合規 -- **關鍵**: 100% Relationship 功能合規 - -### 性能目標 -- 索引速度影響 < 15% -- 記憶體使用增長 < 20% -- 大型項目 (1000+ 檔案) 支援良好 - -## 📝 變更管理 - -### 版本控制策略 -- 功能分支開發 (`feature/scip-relationships`) -- 增量 PR 提交,便於審查 -- 完整功能後合併到主分支 - -### 文檔更新 -- [ ] 更新 `ARCHITECTURE.md` 包含關係架構 -- [ ] 更新 `README.md` 功能描述 -- [ ] 新增關係 API 使用指南 -- [ ] 更新 `SCIP_OFFICIAL_STANDARDS.md` 實現狀態 - -### 發布策略 -- 作為主要版本發布 (v3.0.0) -- 提供升級指南和遷移文檔 -- 社群通知和反饋收集 - ---- - -**負責人**: Claude Code -**審查者**: 項目維護者 -**最後更新**: 2025-01-14 \ No newline at end of file diff --git a/diff.txt b/diff.txt deleted file mode 100644 index 4a804fdc99e22e72f8bce482a9657716ea17969c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 551926 zcmeFa`;%2ilINK}ui5?&xnkO&no>&WRjMANyD5aK=~YV@&~0NwX)sBkS0khasj6s3 z|N8Fw=Xie8!{c!tH*W$erD5c`_nvbiJpAn*9ufcF|NG$Jn}aXy-{HaQ_U|kE_M?6N z?BI=qrw30CzOd^T2j3q&ICyw)-|ju0KK<1`e`mk{Fg^8!{mv(zdt%SMaq!4KePL}p zvgi2aOZ(6DvHeOre12-rJUaOEbmz$4e`dXYKDGB^d;LET{*Pi@hX?N)+@Bj%pBb$0 z9UR#|VE*#p9UJ*)cK7h$%E6oV?>+nFodezmZ?C^3oYDW~>EFx4-ajAwvyJ|B!^>|E zerKO9pM2sD5FQ@9HvRkPgf9*cZckV(`NDrI`GQst53U=(d~f{n)V_Ubw0dN39(MT3 zfAjZ$GA{nc-g<0L{>481c<}W^WjOoLxQ!OSwRXO+J4eURIi&vg8{_XE?3pK%G{Dba zO|tRW{_+Hn(8hPx+Klq zKiPf8Nne*=P+!1s%`o=tm}W6TIRD2;hcN7}u7 z44EY6ros26!SuvN74N{sj1ew(jz**QT16|sRe!O+=-)#dOJL|};{E6w8T!fkbZuR} zfem;tVaIWjVTMPZn1s+H*o>a$-y1=H-`YX8oF3jIbh}{x!3>mpVpk*xebU3g-QyMx z?fzpM6%zaB>5ZT4mkj?H4pAAtkW7m<)~_yAIto!xENNR0cxVPdyk94JvT<*$Fo} zT=5rPVQ+M)3y&$u2xwD-QZfB!joKj2f`7T4e! zd4k!_jInpeAo=k?I=jyW(WX2V;Unt=Y@q8A>9@B`pS?3_H6nz|2XEQG-`RJ5-6&dT_qPvi>w#YrOk88Fz?A+>bu|aq^oU+n7eU(

@ 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 {rel.symbol}") - print(f" is_reference: {rel.is_reference}") - print(f" is_implementation: {rel.is_implementation}") - print(f" is_type_definition: {rel.is_type_definition}") - - # 統計信息 - total_symbols = sum(len(doc.symbols) for doc in scip_index.documents) - total_occurrences = sum(len(doc.occurrences) for doc in scip_index.documents) - total_relationships = sum(len(symbol.relationships) for doc in scip_index.documents for symbol in doc.symbols) - - print(f"\n📊 統計信息:") - print(f" 總文檔數: {len(scip_index.documents)}") - print(f" 總符號數: {total_symbols}") - print(f" 總出現次數: {total_occurrences}") - print(f" 總關係數: {total_relationships}") - - return True - - except Exception as e: - print(f"❌ 解析失敗: {e}") - import traceback - traceback.print_exc() - return False - -if __name__ == "__main__": - print("🚀 開始解析 SCIP 索引文件...") - success = parse_scip_index() - - if success: - print("\n✅ SCIP 索引解析完成!") - else: - print("\n❌ SCIP 索引解析失敗") \ No newline at end of file diff --git a/temp_js_end.py b/temp_js_end.py deleted file mode 100644 index a72b0cb..0000000 --- a/temp_js_end.py +++ /dev/null @@ -1 +0,0 @@ - return symbol_info \ No newline at end of file diff --git a/temp_js_strategy.py b/temp_js_strategy.py deleted file mode 100644 index 24fcf0b..0000000 --- a/temp_js_strategy.py +++ /dev/null @@ -1,535 +0,0 @@ -"""JavaScript/TypeScript SCIP indexing strategy - SCIP standard compliant.""" - -import logging -import os -from typing import List, Optional, Dict, Any, Set - -from .base_strategy import SCIPIndexerStrategy, StrategyError -from ..proto import scip_pb2 -from ..core.position_calculator import PositionCalculator -from ..core.relationship_types import InternalRelationshipType - -# Tree-sitter imports -try: - import tree_sitter - from tree_sitter_javascript import language as js_language - from tree_sitter_typescript import language as ts_language - TREE_SITTER_AVAILABLE = True -except ImportError: - TREE_SITTER_AVAILABLE = False - tree_sitter = None - js_language = None - ts_language = None - -logger = logging.getLogger(__name__) - - -class JavaScriptStrategy(SCIPIndexerStrategy): - """SCIP-compliant JavaScript/TypeScript indexing strategy using Tree-sitter.""" - - SUPPORTED_EXTENSIONS = {'.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs'} - - def __init__(self, priority: int = 95): - """Initialize the JavaScript/TypeScript strategy.""" - super().__init__(priority) - - # Initialize parsers if Tree-sitter is available - if TREE_SITTER_AVAILABLE: - try: - js_lang = tree_sitter.Language(js_language()) - ts_lang = tree_sitter.Language(ts_language()) - - self.js_parser = tree_sitter.Parser(js_lang) - self.ts_parser = tree_sitter.Parser(ts_lang) - logger.info("JavaScript strategy initialized with Tree-sitter support") - except Exception as e: - logger.warning(f"Failed to initialize Tree-sitter parsers: {e}") - self.js_parser = None - self.ts_parser = None - else: - self.js_parser = None - self.ts_parser = None - raise StrategyError("Tree-sitter not available for JavaScript/TypeScript strategy") - - def can_handle(self, extension: str, file_path: str) -> bool: - """Check if this strategy can handle the file type.""" - return extension.lower() in self.SUPPORTED_EXTENSIONS - - def get_language_name(self) -> str: - """Get the language name for SCIP symbol generation.""" - return "javascript" # Use 'javascript' for both JS and TS - - def is_available(self) -> bool: - """Check if this strategy is available.""" - if not TREE_SITTER_AVAILABLE or not self.js_parser or not self.ts_parser: - raise StrategyError("Tree-sitter not available for JavaScript/TypeScript strategy") - return True - - def _collect_symbol_definitions(self, files: List[str], project_path: str) -> None: - """Phase 1: Collect all symbol definitions from JavaScript/TypeScript files.""" - logger.debug(f"JavaScriptStrategy Phase 1: Processing {len(files)} files for symbol collection") - processed_count = 0 - error_count = 0 - - for i, file_path in enumerate(files, 1): - relative_path = os.path.relpath(file_path, project_path) - - try: - self._collect_symbols_from_file(file_path, project_path) - processed_count += 1 - - if i % 10 == 0 or i == len(files): - logger.debug(f"Phase 1 progress: {i}/{len(files)} files, last file: {relative_path}") - - except Exception as e: - error_count += 1 - logger.warning(f"Phase 1 failed for {relative_path}: {e}") - continue - - logger.info(f"Phase 1 summary: {processed_count} files processed, {error_count} errors") - - def _generate_documents_with_references(self, files: List[str], project_path: str, relationships: Optional[Dict[str, List[tuple]]] = None) -> List[scip_pb2.Document]: - """Phase 2: Generate complete SCIP documents with resolved references.""" - documents = [] - logger.debug(f"JavaScriptStrategy Phase 2: Generating documents for {len(files)} files") - processed_count = 0 - error_count = 0 - total_occurrences = 0 - total_symbols = 0 - - for i, file_path in enumerate(files, 1): - relative_path = os.path.relpath(file_path, project_path) - - try: - document = self._analyze_js_file(file_path, project_path, relationships) - if document: - documents.append(document) - total_occurrences += len(document.occurrences) - total_symbols += len(document.symbols) - processed_count += 1 - - if i % 10 == 0 or i == len(files): - logger.debug(f"Phase 2 progress: {i}/{len(files)} files, " - f"last file: {relative_path}, " - f"{len(document.occurrences) if document else 0} occurrences") - - except Exception as e: - error_count += 1 - logger.error(f"Phase 2 failed for {relative_path}: {e}") - continue - - logger.info(f"Phase 2 summary: {processed_count} documents generated, {error_count} errors, " - f"{total_occurrences} total occurrences, {total_symbols} total symbols") - - return documents - - def _build_symbol_relationships(self, files: List[str], project_path: str) -> Dict[str, List[tuple]]: - """ - Build relationships between JavaScript/TypeScript symbols. - - Args: - files: List of file paths to process - project_path: Project root path - - Returns: - Dictionary mapping symbol_id -> [(target_symbol_id, relationship_type), ...] - """ - logger.debug(f"JavaScriptStrategy: Building symbol relationships for {len(files)} files") - - all_relationships = {} - - for file_path in files: - try: - file_relationships = self._extract_js_relationships_from_file(file_path, project_path) - all_relationships.update(file_relationships) - except Exception as e: - logger.warning(f"Failed to extract relationships from {file_path}: {e}") - - total_symbols_with_relationships = len(all_relationships) - total_relationships = sum(len(rels) for rels in all_relationships.values()) - - logger.debug(f"JavaScriptStrategy: Built {total_relationships} relationships for {total_symbols_with_relationships} symbols") - return all_relationships - - def _collect_symbols_from_file(self, file_path: str, project_path: str) -> None: - """Collect symbol definitions from a single JavaScript/TypeScript file.""" - - # Read file content - content = self._read_file_content(file_path) - if not content: - logger.debug(f"Empty file skipped: {os.path.relpath(file_path, project_path)}") - return - - # Parse with Tree-sitter - try: - tree = self._parse_js_content(content, file_path) - if not tree or not tree.root_node: - raise StrategyError(f"Failed to parse {os.path.relpath(file_path, project_path)}") - except Exception as e: - raise StrategyError(f"Parse error in {os.path.relpath(file_path, project_path)}: {e}") - - # Collect symbols using Tree-sitter - relative_path = self._get_relative_path(file_path, project_path) - self._collect_symbols_from_tree(tree, relative_path, content) - logger.debug(f"Symbol collection - {relative_path}") - - def _analyze_js_file(self, file_path: str, project_path: str, relationships: Optional[Dict[str, List[tuple]]] = None) -> Optional[scip_pb2.Document]: - """Analyze a single JavaScript/TypeScript file and generate complete SCIP document.""" - relative_path = self._get_relative_path(file_path, project_path) - - # Read file content - content = self._read_file_content(file_path) - if not content: - logger.debug(f"Empty file skipped: {relative_path}") - return None - - # Parse with Tree-sitter - try: - tree = self._parse_js_content(content, file_path) - if not tree or not tree.root_node: - raise StrategyError(f"Failed to parse {relative_path}") - except Exception as e: - raise StrategyError(f"Parse error in {relative_path}: {e}") - - # Create SCIP document - document = scip_pb2.Document() - document.relative_path = relative_path - document.language = self.get_language_name() - - # Analyze tree and generate occurrences - self.position_calculator = PositionCalculator(content) - occurrences, symbols = self._analyze_tree_for_document(tree, relative_path, content, relationships) - - # Add results to document - document.occurrences.extend(occurrences) - document.symbols.extend(symbols) - - logger.debug(f"Document analysis - {relative_path}: " - f"-> {len(document.occurrences)} occurrences, {len(document.symbols)} symbols") - - return document - - def _extract_js_relationships_from_file(self, file_path: str, project_path: str) -> Dict[str, List[tuple]]: - """ - Extract relationships from a single JavaScript/TypeScript file. - - Args: - file_path: File to analyze - project_path: Project root path - - Returns: - Dictionary mapping symbol_id -> [(target_symbol_id, relationship_type), ...] - """ - content = self._read_file_content(file_path) - if not content: - return {} - - try: - tree = self._parse_js_content(content, file_path) - if not tree or not tree.root_node: - raise StrategyError(f"Failed to parse {file_path} for relationship extraction") - except Exception as e: - raise StrategyError(f"Parse error in {file_path}: {e}") - - return self._extract_relationships_from_tree(tree, file_path, project_path) - - def _parse_js_content(self, content: str, file_path: str): - """Parse JavaScript/TypeScript content using Tree-sitter parser.""" - if not TREE_SITTER_AVAILABLE or not self.js_parser or not self.ts_parser: - raise StrategyError("Tree-sitter not available for JavaScript/TypeScript parsing") - - # Determine parser based on file extension - extension = os.path.splitext(file_path)[1].lower() - - if extension in {'.ts', '.tsx'}: - parser = self.ts_parser - else: - parser = self.js_parser - - try: - content_bytes = content.encode('utf-8') - return parser.parse(content_bytes) - except Exception as e: - raise StrategyError(f"Failed to parse {file_path} with Tree-sitter: {e}") - - - def _collect_symbols_from_tree(self, tree, file_path: str, content: str) -> None: - """Collect symbols from Tree-sitter tree.""" - - def visit_node(node, scope_stack=[]): - node_type = node.type - - if node_type in ['function_declaration', 'method_definition', 'arrow_function']: - self._register_js_function(node, file_path, scope_stack) - elif node_type in ['class_declaration']: - self._register_js_class(node, file_path, scope_stack) - - # Recursively visit children - for child in node.children: - visit_node(child, scope_stack) - - visit_node(tree.root_node) - - def _analyze_tree_for_document(self, tree, file_path: str, content: str, relationships: Optional[Dict[str, List[tuple]]] = None) -> tuple: - """Analyze Tree-sitter tree to generate occurrences and symbols for SCIP document.""" - occurrences = [] - symbols = [] - - def visit_node(node, scope_stack=[]): - node_type = node.type - - if node_type in ['function_declaration', 'method_definition', 'arrow_function']: - name = self._get_js_function_name(node) - if name: - symbol_id = self._create_js_function_symbol_id(name, file_path, scope_stack) - occurrence = self._create_js_function_occurrence(node, symbol_id) - symbol_relationships = relationships.get(symbol_id, []) if relationships else [] - scip_relationships = self._create_scip_relationships(symbol_relationships) if symbol_relationships else [] - symbol_info = self._create_js_function_symbol_info(node, symbol_id, name, scip_relationships) - - if occurrence: - occurrences.append(occurrence) - if symbol_info: - symbols.append(symbol_info) - - elif node_type in ['class_declaration']: - name = self._get_js_class_name(node) - if name: - symbol_id = self._create_js_class_symbol_id(name, file_path, scope_stack) - occurrence = self._create_js_class_occurrence(node, symbol_id) - symbol_relationships = relationships.get(symbol_id, []) if relationships else [] - scip_relationships = self._create_scip_relationships(symbol_relationships) if symbol_relationships else [] - symbol_info = self._create_js_class_symbol_info(node, symbol_id, name, scip_relationships) - - if occurrence: - occurrences.append(occurrence) - if symbol_info: - symbols.append(symbol_info) - - # Recursively visit children - for child in node.children: - visit_node(child, scope_stack) - - visit_node(tree.root_node) - return occurrences, symbols - - def _extract_relationships_from_tree(self, tree, file_path: str, project_path: str) -> Dict[str, List[tuple]]: - """Extract relationships from Tree-sitter tree.""" - relationships = {} - - def visit_node(node, scope_stack=[]): - node_type = node.type - - if node_type == 'class_declaration': - # Extract inheritance relationships for ES6 classes - class_name = self._get_js_class_name(node) - if class_name: - class_symbol_id = self._create_js_class_symbol_id(class_name, file_path, scope_stack) - - # Look for extends clause - for child in node.children: - if child.type == 'class_heritage': - for heritage_child in child.children: - if heritage_child.type == 'identifier': - parent_name = self._get_node_text(heritage_child) - if parent_name: - parent_symbol_id = self._create_js_class_symbol_id(parent_name, file_path, scope_stack) - if class_symbol_id not in relationships: - relationships[class_symbol_id] = [] - relationships[class_symbol_id].append((parent_symbol_id, InternalRelationshipType.INHERITS)) - - elif node_type in ['function_declaration', 'method_definition', 'arrow_function']: - # Extract function call relationships - function_name = self._get_js_function_name(node) - if function_name: - function_symbol_id = self._create_js_function_symbol_id(function_name, file_path, scope_stack) - - # Find call expressions within this function - self._extract_calls_from_node(node, function_symbol_id, relationships, file_path, scope_stack) - - # Recursively visit children - for child in node.children: - visit_node(child, scope_stack) - - visit_node(tree.root_node) - return relationships - - def _extract_calls_from_node(self, node, source_symbol_id: str, relationships: Dict, file_path: str, scope_stack: List): - """Extract function calls from a node.""" - - def visit_for_calls(n): - if n.type == 'call_expression': - # Get the function being called - function_node = n.children[0] if n.children else None - if function_node: - if function_node.type == 'identifier': - target_name = self._get_node_text(function_node) - if target_name: - target_symbol_id = self._create_js_function_symbol_id(target_name, file_path, scope_stack) - if source_symbol_id not in relationships: - relationships[source_symbol_id] = [] - relationships[source_symbol_id].append((target_symbol_id, InternalRelationshipType.CALLS)) - - for child in n.children: - visit_for_calls(child) - - visit_for_calls(node) - - # Helper methods for Tree-sitter node processing - def _get_node_text(self, node) -> Optional[str]: - """Get text content of a Tree-sitter node.""" - if hasattr(node, 'text'): - try: - return node.text.decode('utf-8') - except: - pass - return None - - def _get_js_function_name(self, node) -> Optional[str]: - """Extract function name from function node.""" - for child in node.children: - if child.type == 'identifier': - return self._get_node_text(child) - return None - - def _get_js_class_name(self, node) -> Optional[str]: - """Extract class name from class node.""" - for child in node.children: - if child.type == 'identifier': - return self._get_node_text(child) - return None - - # Symbol registration and creation methods - def _register_js_function(self, node, file_path: str, scope_stack: List[str]) -> None: - """Register a JavaScript function symbol definition.""" - name = self._get_js_function_name(node) - if not name: - return - - symbol_id = self.symbol_manager.create_local_symbol( - language="javascript", - file_path=file_path, - symbol_path=scope_stack + [name], - descriptor="()." - ) - - # Create a dummy range for registration - dummy_range = scip_pb2.Range() - dummy_range.start.extend([0, 0]) - dummy_range.end.extend([0, 1]) - - self.reference_resolver.register_symbol_definition( - symbol_id=symbol_id, - file_path=file_path, - definition_range=dummy_range, - symbol_kind=scip_pb2.Function, - display_name=name, - documentation=["JavaScript function"] - ) - - def _register_js_class(self, node, file_path: str, scope_stack: List[str]) -> None: - """Register a JavaScript class symbol definition.""" - name = self._get_js_class_name(node) - if not name: - return - - symbol_id = self.symbol_manager.create_local_symbol( - language="javascript", - file_path=file_path, - symbol_path=scope_stack + [name], - descriptor="#" - ) - - # Create a dummy range for registration - dummy_range = scip_pb2.Range() - dummy_range.start.extend([0, 0]) - dummy_range.end.extend([0, 1]) - - self.reference_resolver.register_symbol_definition( - symbol_id=symbol_id, - file_path=file_path, - definition_range=dummy_range, - symbol_kind=scip_pb2.Class, - display_name=name, - documentation=["JavaScript class"] - ) - - def _create_js_function_symbol_id(self, name: str, file_path: str, scope_stack: List[str]) -> str: - """Create symbol ID for JavaScript function.""" - return self.symbol_manager.create_local_symbol( - language="javascript", - file_path=file_path, - symbol_path=scope_stack + [name], - descriptor="()." - ) - - def _create_js_class_symbol_id(self, name: str, file_path: str, scope_stack: List[str]) -> str: - """Create symbol ID for JavaScript class.""" - return self.symbol_manager.create_local_symbol( - language="javascript", - file_path=file_path, - symbol_path=scope_stack + [name], - descriptor="#" - ) - - def _create_js_function_occurrence(self, node, symbol_id: str) -> Optional[scip_pb2.Occurrence]: - """Create SCIP occurrence for JavaScript function.""" - if not self.position_calculator: - return None - - try: - # Convert Tree-sitter node to range (simplified) - range_obj = scip_pb2.Range() - range_obj.start.extend([node.start_point[0], node.start_point[1]]) - range_obj.end.extend([node.end_point[0], node.end_point[1]]) - - occurrence = scip_pb2.Occurrence() - occurrence.symbol = symbol_id - occurrence.symbol_roles = scip_pb2.Definition - occurrence.syntax_kind = scip_pb2.IdentifierFunction - occurrence.range.CopyFrom(range_obj) - return occurrence - except: - return None - - def _create_js_class_occurrence(self, node, symbol_id: str) -> Optional[scip_pb2.Occurrence]: - """Create SCIP occurrence for JavaScript class.""" - if not self.position_calculator: - return None - - try: - # Convert Tree-sitter node to range (simplified) - range_obj = scip_pb2.Range() - range_obj.start.extend([node.start_point[0], node.start_point[1]]) - range_obj.end.extend([node.end_point[0], node.end_point[1]]) - - occurrence = scip_pb2.Occurrence() - occurrence.symbol = symbol_id - occurrence.symbol_roles = scip_pb2.Definition - occurrence.syntax_kind = scip_pb2.IdentifierType - occurrence.range.CopyFrom(range_obj) - return occurrence - except: - return None - - def _create_js_function_symbol_info(self, node, symbol_id: str, name: str, relationships: Optional[List[scip_pb2.Relationship]] = None) -> scip_pb2.SymbolInformation: - """Create SCIP symbol information for JavaScript function.""" - symbol_info = scip_pb2.SymbolInformation() - symbol_info.symbol = symbol_id - symbol_info.display_name = name - symbol_info.kind = scip_pb2.Function - symbol_info.documentation.append("JavaScript function") - if relationships and self.relationship_manager: - self.relationship_manager.add_relationships_to_symbol(symbol_info, relationships) - return symbol_info - - def _create_js_class_symbol_info(self, node, symbol_id: str, name: str, relationships: Optional[List[scip_pb2.Relationship]] = None) -> scip_pb2.SymbolInformation: - """Create SCIP symbol information for JavaScript class.""" - symbol_info = scip_pb2.SymbolInformation() - symbol_info.symbol = symbol_id - symbol_info.display_name = name - symbol_info.kind = scip_pb2.Class - symbol_info.documentation.append("JavaScript class") - if relationships and self.relationship_manager: - self.relationship_manager.add_relationships_to_symbol(symbol_info, relationships) - return symbol_info From 596fdf8929158f6280607efbd215a2a7d854e925 Mon Sep 17 00:00:00 2001 From: johnhuang316 <134570882+johnhuang316@users.noreply.github.com> Date: Mon, 18 Aug 2025 17:14:21 +0800 Subject: [PATCH 2/2] Enhance SCIPSymbolAnalyzer with external symbol import extraction - Updated _organize_results to include scip_index for external symbol extraction. - Added _extract_imports_from_external_symbols method to handle imports from SCIP index. - Implemented framework name extraction from symbol strings. - Classified external symbols into standard_library and third_party categories. - Removed obsolete inspect_doc_symbols.py script. - Updated version of code-index-mcp to 2.1.2 and added libclang dependency. - Removed tree-sitter-c dependency from the project. --- pyproject.toml | 4 +- requirements.txt | 3 +- src/code_index_mcp/scip/factory.py | 15 +- .../scip/strategies/javascript_strategy.py | 670 ++++++- .../scip/strategies/objective_c_strategy.py | 1698 +++++++++-------- .../tools/scip/scip_symbol_analyzer.py | 99 +- src/scripts/inspect_doc_symbols.py | 68 - uv.lock | 38 +- 8 files changed, 1646 insertions(+), 949 deletions(-) delete mode 100644 src/scripts/inspect_doc_symbols.py diff --git a/pyproject.toml b/pyproject.toml index 0702c16..2c0d989 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "code-index-mcp" -version = "2.1.1" +version = "2.1.2" description = "Code indexing and analysis tools for LLMs using MCP" readme = "README.md" requires-python = ">=3.10" @@ -20,9 +20,9 @@ dependencies = [ "tree-sitter-javascript>=0.20.0", "tree-sitter-typescript>=0.20.0", "tree-sitter-java>=0.20.0", - "tree-sitter-c>=0.20.0", "tree-sitter-zig>=0.20.0", "pathspec>=0.12.1", + "libclang>=16.0.0", ] [project.urls] diff --git a/requirements.txt b/requirements.txt index c90f59f..1a80b2f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,5 +5,6 @@ tree-sitter>=0.20.0 tree-sitter-javascript>=0.20.0 tree-sitter-typescript>=0.20.0 tree-sitter-java>=0.20.0 -tree-sitter-c>=0.20.0 +tree-sitter-zig>=0.20.0 pathspec>=0.12.1 +libclang>=16.0.0 diff --git a/src/code_index_mcp/scip/factory.py b/src/code_index_mcp/scip/factory.py index 5f750d4..1620d8b 100644 --- a/src/code_index_mcp/scip/factory.py +++ b/src/code_index_mcp/scip/factory.py @@ -7,7 +7,13 @@ from .strategies.javascript_strategy import JavaScriptStrategy from .strategies.java_strategy import JavaStrategy from .strategies.objective_c_strategy import ObjectiveCStrategy -from .strategies.zig_strategy import ZigStrategy +# Optional strategies - import only if available +try: + from .strategies.zig_strategy import ZigStrategy + ZIG_AVAILABLE = True +except ImportError: + ZigStrategy = None + ZIG_AVAILABLE = False from .strategies.fallback_strategy import FallbackStrategy from ..constants import SUPPORTED_EXTENSIONS @@ -35,9 +41,12 @@ def _register_all_strategies(self): (JavaScriptStrategy, 95), (JavaStrategy, 95), (ObjectiveCStrategy, 95), - (ZigStrategy, 95), ] + # Add optional strategies if available + if ZIG_AVAILABLE and ZigStrategy: + strategy_classes.append((ZigStrategy, 95)) + for strategy_class, priority in strategy_classes: try: strategy = strategy_class(priority=priority) @@ -127,7 +136,7 @@ def list_supported_extensions(self) -> Set[str]: supported.update({'.java'}) elif isinstance(strategy, ObjectiveCStrategy): supported.update({'.m', '.mm'}) - elif isinstance(strategy, ZigStrategy): + elif ZIG_AVAILABLE and isinstance(strategy, ZigStrategy): supported.update({'.zig', '.zon'}) elif isinstance(strategy, FallbackStrategy): # Fallback supports everything, but we don't want to list everything here diff --git a/src/code_index_mcp/scip/strategies/javascript_strategy.py b/src/code_index_mcp/scip/strategies/javascript_strategy.py index ab5104d..489fd37 100644 --- a/src/code_index_mcp/scip/strategies/javascript_strategy.py +++ b/src/code_index_mcp/scip/strategies/javascript_strategy.py @@ -14,6 +14,7 @@ from tree_sitter_javascript import language as js_language from tree_sitter_typescript import language_typescript as ts_language + logger = logging.getLogger(__name__) @@ -27,12 +28,26 @@ def __init__(self, priority: int = 95): super().__init__(priority) # Initialize parsers - js_lang = tree_sitter.Language(js_language()) - ts_lang = tree_sitter.Language(ts_language()) - - self.js_parser = tree_sitter.Parser(js_lang) - self.ts_parser = tree_sitter.Parser(ts_lang) - logger.info("JavaScript strategy initialized with Tree-sitter support") + try: + js_lang = tree_sitter.Language(js_language()) + ts_lang = tree_sitter.Language(ts_language()) + + self.js_parser = tree_sitter.Parser(js_lang) + self.ts_parser = tree_sitter.Parser(ts_lang) + logger.info("JavaScript strategy initialized with Tree-sitter support") + except Exception as e: + logger.error(f"Failed to initialize JavaScript strategy: {e}") + self.js_parser = None + self.ts_parser = None + + # Initialize dependency tracking + self.dependencies = { + 'imports': { + 'standard_library': [], + 'third_party': [], + 'local': [] + } + } def can_handle(self, extension: str, file_path: str) -> bool: """Check if this strategy can handle the file type.""" @@ -40,7 +55,7 @@ def can_handle(self, extension: str, file_path: str) -> bool: def get_language_name(self) -> str: """Get the language name for SCIP symbol generation.""" - return "javascript" # Use 'javascript' for both JS and TS + return "javascript" def is_available(self) -> bool: """Check if this strategy is available.""" @@ -59,7 +74,7 @@ def _collect_symbol_definitions(self, files: List[str], project_path: str) -> No self._collect_symbols_from_file(file_path, project_path) processed_count += 1 - if i % 10 == 0 or i == len(files): + if i % 10 == 0 or i == len(files): # Progress every 10 files or at end logger.debug(f"Phase 1 progress: {i}/{len(files)} files, last file: {relative_path}") except Exception as e: @@ -82,14 +97,14 @@ def _generate_documents_with_references(self, files: List[str], project_path: st relative_path = os.path.relpath(file_path, project_path) try: - document = self._analyze_js_file(file_path, project_path, relationships) + document = self._analyze_javascript_file(file_path, project_path, relationships) if document: documents.append(document) total_occurrences += len(document.occurrences) total_symbols += len(document.symbols) processed_count += 1 - if i % 10 == 0 or i == len(files): + if i % 10 == 0 or i == len(files): # Progress every 10 files or at end logger.debug(f"Phase 2 progress: {i}/{len(files)} files, " f"last file: {relative_path}, " f"{len(document.occurrences) if document else 0} occurrences") @@ -121,7 +136,7 @@ def _build_symbol_relationships(self, files: List[str], project_path: str) -> Di for file_path in files: try: - file_relationships = self._extract_js_relationships_from_file(file_path, project_path) + file_relationships = self._extract_relationships_from_file(file_path, project_path) all_relationships.update(file_relationships) except Exception as e: logger.warning(f"Failed to extract relationships from {file_path}: {e}") @@ -135,6 +150,9 @@ def _build_symbol_relationships(self, files: List[str], project_path: str) -> Di def _collect_symbols_from_file(self, file_path: str, project_path: str) -> None: """Collect symbol definitions from a single JavaScript/TypeScript file.""" + # Reset dependencies for this file + self._reset_dependencies() + # Read file content content = self._read_file_content(file_path) if not content: @@ -147,14 +165,15 @@ def _collect_symbols_from_file(self, file_path: str, project_path: str) -> None: if not tree or not tree.root_node: raise StrategyError(f"Failed to parse {os.path.relpath(file_path, project_path)}") except Exception as e: - raise StrategyError(f"Parse error in {os.path.relpath(file_path, project_path)}: {e}") + logger.warning(f"Parse error in {os.path.relpath(file_path, project_path)}: {e}") + return - # Collect symbols using Tree-sitter + # Collect symbols using integrated visitor relative_path = self._get_relative_path(file_path, project_path) self._collect_symbols_from_tree(tree, relative_path, content) logger.debug(f"Symbol collection - {relative_path}") - def _analyze_js_file(self, file_path: str, project_path: str, relationships: Optional[Dict[str, List[tuple]]] = None) -> Optional[scip_pb2.Document]: + def _analyze_javascript_file(self, file_path: str, project_path: str, relationships: Optional[Dict[str, List[tuple]]] = None) -> Optional[scip_pb2.Document]: """Analyze a single JavaScript/TypeScript file and generate complete SCIP document.""" relative_path = self._get_relative_path(file_path, project_path) @@ -170,7 +189,8 @@ def _analyze_js_file(self, file_path: str, project_path: str, relationships: Opt if not tree or not tree.root_node: raise StrategyError(f"Failed to parse {relative_path}") except Exception as e: - raise StrategyError(f"Parse error in {relative_path}: {e}") + logger.warning(f"Parse error in {relative_path}: {e}") + return None # Create SCIP document document = scip_pb2.Document() @@ -179,6 +199,7 @@ def _analyze_js_file(self, file_path: str, project_path: str, relationships: Opt # Analyze tree and generate occurrences self.position_calculator = PositionCalculator(content) + occurrences, symbols = self._analyze_tree_for_document(tree, relative_path, content, relationships) # Add results to document @@ -190,7 +211,7 @@ def _analyze_js_file(self, file_path: str, project_path: str, relationships: Opt return document - def _extract_js_relationships_from_file(self, file_path: str, project_path: str) -> Dict[str, List[tuple]]: + def _extract_relationships_from_file(self, file_path: str, project_path: str) -> Dict[str, List[tuple]]: """ Extract relationships from a single JavaScript/TypeScript file. @@ -210,7 +231,8 @@ def _extract_js_relationships_from_file(self, file_path: str, project_path: str) if not tree or not tree.root_node: raise StrategyError(f"Failed to parse {file_path} for relationship extraction") except Exception as e: - raise StrategyError(f"Parse error in {file_path}: {e}") + logger.warning(f"Parse error in {file_path}: {e}") + return {} return self._extract_relationships_from_tree(tree, file_path, project_path) @@ -224,43 +246,83 @@ def _parse_js_content(self, content: str, file_path: str): else: parser = self.js_parser + if not parser: + raise StrategyError(f"No parser available for {extension}") + content_bytes = content.encode('utf-8') return parser.parse(content_bytes) - def _collect_symbols_from_tree(self, tree, file_path: str, content: str) -> None: - """Collect symbols from Tree-sitter tree.""" - - def visit_node(node, scope_stack=[]): + """Collect symbols from Tree-sitter tree using integrated visitor.""" + # Use a set to track processed nodes and avoid duplicates + self._processed_nodes = set() + scope_stack = [] + + def visit_node(node, current_scope_stack=None): + if current_scope_stack is None: + current_scope_stack = scope_stack[:] + + # Skip if already processed (by memory address) + node_id = id(node) + if node_id in self._processed_nodes: + return + self._processed_nodes.add(node_id) + node_type = node.type + # Traditional function and class declarations if node_type in ['function_declaration', 'method_definition', 'arrow_function']: - self._register_js_function(node, file_path, scope_stack) + name = self._get_js_function_name(node) + if name: + self._register_function_symbol(node, name, file_path, current_scope_stack) elif node_type in ['class_declaration']: - self._register_js_class(node, file_path, scope_stack) - + name = self._get_js_class_name(node) + if name: + self._register_class_symbol(node, name, file_path, current_scope_stack) + + # Assignment expressions with function expressions (obj.method = function() {}) + elif node_type == 'assignment_expression': + self._handle_assignment_expression(node, file_path, current_scope_stack) + + # Lexical declarations (const, let, var) + elif node_type == 'lexical_declaration': + self._handle_lexical_declaration(node, file_path, current_scope_stack) + + # Expression statements (might contain method chains) + elif node_type == 'expression_statement': + self._handle_expression_statement(node, file_path, current_scope_stack) + # Recursively visit children for child in node.children: - visit_node(child, scope_stack) + visit_node(child, current_scope_stack) visit_node(tree.root_node) - def _analyze_tree_for_document(self, tree, file_path: str, content: str, relationships: Optional[Dict[str, List[tuple]]] = None) -> tuple: + def _analyze_tree_for_document(self, tree, file_path: str, content: str, relationships: Optional[Dict[str, List[tuple]]] = None) -> tuple[List[scip_pb2.Occurrence], List[scip_pb2.SymbolInformation]]: """Analyze Tree-sitter tree to generate occurrences and symbols for SCIP document.""" occurrences = [] symbols = [] + scope_stack = [] + + # Use the same processed nodes set to avoid duplicates + if not hasattr(self, '_processed_nodes'): + self._processed_nodes = set() - def visit_node(node, scope_stack=[]): + def visit_node(node, current_scope_stack=None): + if current_scope_stack is None: + current_scope_stack = scope_stack[:] + node_type = node.type + # Traditional function and class declarations if node_type in ['function_declaration', 'method_definition', 'arrow_function']: name = self._get_js_function_name(node) if name: - symbol_id = self._create_js_function_symbol_id(name, file_path, scope_stack) - occurrence = self._create_js_function_occurrence(node, symbol_id) + symbol_id = self._create_function_symbol_id(name, file_path, current_scope_stack) + occurrence = self._create_function_occurrence(node, symbol_id) symbol_relationships = relationships.get(symbol_id, []) if relationships else [] scip_relationships = self._create_scip_relationships(symbol_relationships) if symbol_relationships else [] - symbol_info = self._create_js_function_symbol_info(node, symbol_id, name, scip_relationships) + symbol_info = self._create_function_symbol_info(node, symbol_id, name, scip_relationships) if occurrence: occurrences.append(occurrence) @@ -270,20 +332,38 @@ def visit_node(node, scope_stack=[]): elif node_type in ['class_declaration']: name = self._get_js_class_name(node) if name: - symbol_id = self._create_js_class_symbol_id(name, file_path, scope_stack) - occurrence = self._create_js_class_occurrence(node, symbol_id) + symbol_id = self._create_class_symbol_id(name, file_path, current_scope_stack) + occurrence = self._create_class_occurrence(node, symbol_id) symbol_relationships = relationships.get(symbol_id, []) if relationships else [] scip_relationships = self._create_scip_relationships(symbol_relationships) if symbol_relationships else [] - symbol_info = self._create_js_class_symbol_info(node, symbol_id, name, scip_relationships) + symbol_info = self._create_class_symbol_info(node, symbol_id, name, scip_relationships) if occurrence: occurrences.append(occurrence) if symbol_info: symbols.append(symbol_info) + + # Assignment expressions with function expressions + elif node_type == 'assignment_expression': + occurrence, symbol_info = self._handle_assignment_for_document(node, file_path, current_scope_stack, relationships) + if occurrence: + occurrences.append(occurrence) + if symbol_info: + symbols.append(symbol_info) + + # Lexical declarations + elif node_type == 'lexical_declaration': + document_symbols = self._handle_lexical_for_document(node, file_path, current_scope_stack, relationships) + for occ, sym in document_symbols: + if occ: + occurrences.append(occ) + if sym: + symbols.append(sym) - # Recursively visit children - for child in node.children: - visit_node(child, scope_stack) + # Recursively visit children only if not in assignment or lexical that we handle above + if node_type not in ['assignment_expression', 'lexical_declaration']: + for child in node.children: + visit_node(child, current_scope_stack) visit_node(tree.root_node) return occurrences, symbols @@ -291,15 +371,20 @@ def visit_node(node, scope_stack=[]): def _extract_relationships_from_tree(self, tree, file_path: str, project_path: str) -> Dict[str, List[tuple]]: """Extract relationships from Tree-sitter tree.""" relationships = {} + scope_stack = [] + relative_path = self._get_relative_path(file_path, project_path) - def visit_node(node, scope_stack=[]): + def visit_node(node, current_scope_stack=None): + if current_scope_stack is None: + current_scope_stack = scope_stack[:] + node_type = node.type if node_type == 'class_declaration': - # Extract inheritance relationships for ES6 classes + # Extract inheritance relationships class_name = self._get_js_class_name(node) if class_name: - class_symbol_id = self._create_js_class_symbol_id(class_name, file_path, scope_stack) + class_symbol_id = self._create_class_symbol_id(class_name, relative_path, current_scope_stack) # Look for extends clause for child in node.children: @@ -308,7 +393,7 @@ def visit_node(node, scope_stack=[]): if heritage_child.type == 'identifier': parent_name = self._get_node_text(heritage_child) if parent_name: - parent_symbol_id = self._create_js_class_symbol_id(parent_name, file_path, scope_stack) + parent_symbol_id = self._create_class_symbol_id(parent_name, relative_path, current_scope_stack) if class_symbol_id not in relationships: relationships[class_symbol_id] = [] relationships[class_symbol_id].append((parent_symbol_id, InternalRelationshipType.INHERITS)) @@ -317,14 +402,14 @@ def visit_node(node, scope_stack=[]): # Extract function call relationships function_name = self._get_js_function_name(node) if function_name: - function_symbol_id = self._create_js_function_symbol_id(function_name, file_path, scope_stack) + function_symbol_id = self._create_function_symbol_id(function_name, relative_path, current_scope_stack) # Find call expressions within this function - self._extract_calls_from_node(node, function_symbol_id, relationships, file_path, scope_stack) + self._extract_calls_from_node(node, function_symbol_id, relationships, relative_path, current_scope_stack) # Recursively visit children for child in node.children: - visit_node(child, scope_stack) + visit_node(child, current_scope_stack) visit_node(tree.root_node) return relationships @@ -340,7 +425,7 @@ def visit_for_calls(n): if function_node.type == 'identifier': target_name = self._get_node_text(function_node) if target_name: - target_symbol_id = self._create_js_function_symbol_id(target_name, file_path, scope_stack) + target_symbol_id = self._create_function_symbol_id(target_name, file_path, scope_stack) if source_symbol_id not in relationships: relationships[source_symbol_id] = [] relationships[source_symbol_id].append((target_symbol_id, InternalRelationshipType.CALLS)) @@ -374,19 +459,10 @@ def _get_js_class_name(self, node) -> Optional[str]: return self._get_node_text(child) return None - # Symbol registration and creation methods - def _register_js_function(self, node, file_path: str, scope_stack: List[str]) -> None: - """Register a JavaScript function symbol definition.""" - name = self._get_js_function_name(node) - if not name: - return - - symbol_id = self.symbol_manager.create_local_symbol( - language="javascript", - file_path=file_path, - symbol_path=scope_stack + [name], - descriptor="()." - ) + # Helper methods + def _register_function_symbol(self, node, name: str, file_path: str, scope_stack: List[str]) -> None: + """Register a function symbol definition.""" + symbol_id = self._create_function_symbol_id(name, file_path, scope_stack) # Create a dummy range for registration dummy_range = scip_pb2.Range() @@ -402,18 +478,9 @@ def _register_js_function(self, node, file_path: str, scope_stack: List[str]) -> documentation=["JavaScript function"] ) - def _register_js_class(self, node, file_path: str, scope_stack: List[str]) -> None: - """Register a JavaScript class symbol definition.""" - name = self._get_js_class_name(node) - if not name: - return - - symbol_id = self.symbol_manager.create_local_symbol( - language="javascript", - file_path=file_path, - symbol_path=scope_stack + [name], - descriptor="#" - ) + def _register_class_symbol(self, node, name: str, file_path: str, scope_stack: List[str]) -> None: + """Register a class symbol definition.""" + symbol_id = self._create_class_symbol_id(name, file_path, scope_stack) # Create a dummy range for registration dummy_range = scip_pb2.Range() @@ -429,35 +496,26 @@ def _register_js_class(self, node, file_path: str, scope_stack: List[str]) -> No documentation=["JavaScript class"] ) - def _create_js_function_symbol_id(self, name: str, file_path: str, scope_stack: List[str]) -> str: - """Create symbol ID for JavaScript function.""" - return self.symbol_manager.create_local_symbol( - language="javascript", - file_path=file_path, - symbol_path=scope_stack + [name], - descriptor="()." - ) + def _create_function_symbol_id(self, name: str, file_path: str, scope_stack: List[str]) -> str: + """Create symbol ID for function.""" + # SCIP standard: local + local_id = ".".join(scope_stack + [name]) if scope_stack else name + return f"local {local_id}()." - def _create_js_class_symbol_id(self, name: str, file_path: str, scope_stack: List[str]) -> str: - """Create symbol ID for JavaScript class.""" - return self.symbol_manager.create_local_symbol( - language="javascript", - file_path=file_path, - symbol_path=scope_stack + [name], - descriptor="#" - ) + def _create_class_symbol_id(self, name: str, file_path: str, scope_stack: List[str]) -> str: + """Create symbol ID for class.""" + # SCIP standard: local + local_id = ".".join(scope_stack + [name]) if scope_stack else name + return f"local {local_id}#" - def _create_js_function_occurrence(self, node, symbol_id: str) -> Optional[scip_pb2.Occurrence]: - """Create SCIP occurrence for JavaScript function.""" + def _create_function_occurrence(self, node, symbol_id: str) -> Optional[scip_pb2.Occurrence]: + """Create SCIP occurrence for function.""" if not self.position_calculator: return None try: - # Convert Tree-sitter node to range (simplified) - range_obj = scip_pb2.Range() - range_obj.start.extend([node.start_point[0], node.start_point[1]]) - range_obj.end.extend([node.end_point[0], node.end_point[1]]) - + # Use Tree-sitter position calculation method + range_obj = self.position_calculator.tree_sitter_node_to_range(node) occurrence = scip_pb2.Occurrence() occurrence.symbol = symbol_id occurrence.symbol_roles = scip_pb2.Definition @@ -467,17 +525,14 @@ def _create_js_function_occurrence(self, node, symbol_id: str) -> Optional[scip_ except: return None - def _create_js_class_occurrence(self, node, symbol_id: str) -> Optional[scip_pb2.Occurrence]: - """Create SCIP occurrence for JavaScript class.""" + def _create_class_occurrence(self, node, symbol_id: str) -> Optional[scip_pb2.Occurrence]: + """Create SCIP occurrence for class.""" if not self.position_calculator: return None try: - # Convert Tree-sitter node to range (simplified) - range_obj = scip_pb2.Range() - range_obj.start.extend([node.start_point[0], node.start_point[1]]) - range_obj.end.extend([node.end_point[0], node.end_point[1]]) - + # Use Tree-sitter position calculation method + range_obj = self.position_calculator.tree_sitter_node_to_range(node) occurrence = scip_pb2.Occurrence() occurrence.symbol = symbol_id occurrence.symbol_roles = scip_pb2.Definition @@ -487,24 +542,433 @@ def _create_js_class_occurrence(self, node, symbol_id: str) -> Optional[scip_pb2 except: return None - def _create_js_function_symbol_info(self, node, symbol_id: str, name: str, relationships: Optional[List[scip_pb2.Relationship]] = None) -> scip_pb2.SymbolInformation: - """Create SCIP symbol information for JavaScript function.""" + def _create_function_symbol_info(self, node, symbol_id: str, name: str, relationships: Optional[List[scip_pb2.Relationship]] = None) -> scip_pb2.SymbolInformation: + """Create SCIP symbol information for function.""" symbol_info = scip_pb2.SymbolInformation() symbol_info.symbol = symbol_id symbol_info.display_name = name symbol_info.kind = scip_pb2.Function + + # Add documentation - check for JSDoc or comments symbol_info.documentation.append("JavaScript function") + + # Add relationships if provided if relationships and self.relationship_manager: self.relationship_manager.add_relationships_to_symbol(symbol_info, relationships) + return symbol_info - def _create_js_class_symbol_info(self, node, symbol_id: str, name: str, relationships: Optional[List[scip_pb2.Relationship]] = None) -> scip_pb2.SymbolInformation: - """Create SCIP symbol information for JavaScript class.""" + def _create_class_symbol_info(self, node, symbol_id: str, name: str, relationships: Optional[List[scip_pb2.Relationship]] = None) -> scip_pb2.SymbolInformation: + """Create SCIP symbol information for class.""" symbol_info = scip_pb2.SymbolInformation() symbol_info.symbol = symbol_id symbol_info.display_name = name symbol_info.kind = scip_pb2.Class + + # Add documentation - check for JSDoc or comments symbol_info.documentation.append("JavaScript class") + + # Add relationships if provided if relationships and self.relationship_manager: self.relationship_manager.add_relationships_to_symbol(symbol_info, relationships) + + return symbol_info + + # JavaScript-specific syntax handlers + def _handle_assignment_expression(self, node, file_path: str, scope_stack: List[str]) -> None: + """Handle assignment expressions like obj.method = function() {}""" + left_child = None + right_child = None + + for child in node.children: + if child.type == 'member_expression': + left_child = child + elif child.type in ['function_expression', 'arrow_function']: + right_child = child + + if left_child and right_child: + # Extract method name from member expression + method_name = self._extract_member_expression_name(left_child) + if method_name: + # Use just the last part as function name for cleaner identification + clean_name = method_name.split('.')[-1] if '.' in method_name else method_name + # Register as function symbol + self._register_function_symbol(right_child, clean_name, file_path, scope_stack + method_name.split('.')[:-1]) + + def _handle_lexical_declaration(self, node, file_path: str, scope_stack: List[str]) -> None: + """Handle lexical declarations like const VAR = value""" + for child in node.children: + if child.type == 'variable_declarator': + # Get variable name and value + var_name = None + var_value = None + + for declarator_child in child.children: + if declarator_child.type == 'identifier': + var_name = self._get_node_text(declarator_child) + elif declarator_child.type in ['object_expression', 'new_expression', 'call_expression']: + var_value = declarator_child + elif declarator_child.type == 'object_pattern': + # Handle destructuring like const { v4: uuidv4 } = require('uuid') + self._handle_destructuring_pattern(declarator_child, file_path, scope_stack) + + if var_name: + # Check if this is an import/require statement + if var_value and var_value.type == 'call_expression': + # Check if it's a require() call + is_require = False + for cc in var_value.children: + if cc.type == 'identifier' and self._get_node_text(cc) == 'require': + is_require = True + break + + if is_require: + self._handle_import_statement(var_name, var_value, file_path, scope_stack) + else: + # Register as variable (like const limiter = rateLimit(...)) + self._register_variable_symbol(child, var_name, file_path, scope_stack, var_value) + + # Extract functions from call_expression (like rateLimit config) + self._extract_functions_from_call_expression(var_value, var_name, file_path, scope_stack) + else: + # Register as constant/variable symbol + self._register_variable_symbol(child, var_name, file_path, scope_stack, var_value) + # Extract functions from object expressions + if var_value and var_value.type == 'object_expression': + self._extract_functions_from_object(var_value, var_name, file_path, scope_stack) + + def _handle_expression_statement(self, node, file_path: str, scope_stack: List[str]) -> None: + """Handle expression statements that might contain method chains""" + for child in node.children: + if child.type == 'call_expression': + # Look for method chain patterns like schema.virtual().get() + self._handle_method_chain(child, file_path, scope_stack) + elif child.type == 'assignment_expression': + # Handle nested assignment expressions + self._handle_assignment_expression(child, file_path, scope_stack) + + def _handle_method_chain(self, node, file_path: str, scope_stack: List[str]) -> None: + """Handle method chains like schema.virtual('name').get(function() {})""" + # Look for chained calls that end with function expressions + for child in node.children: + if child.type == 'member_expression': + # This could be a chained method call + member_name = self._extract_member_expression_name(child) + if member_name: + # Look for function arguments + for sibling in node.children: + if sibling.type == 'arguments': + for arg in sibling.children: + if arg.type in ['function_expression', 'arrow_function']: + # Register the function with a descriptive name + func_name = f"{member_name}_callback" + self._register_function_symbol(arg, func_name, file_path, scope_stack) + + def _extract_member_expression_name(self, node) -> Optional[str]: + """Extract name from member expression like obj.prop.method""" + parts = [] + + def extract_parts(n): + if n.type == 'member_expression': + # Process children in order: object first, then property + object_child = None + property_child = None + + for child in n.children: + if child.type in ['identifier', 'member_expression']: + object_child = child + elif child.type == 'property_identifier': + property_child = child + + # Recursively extract object part first + if object_child: + if object_child.type == 'member_expression': + extract_parts(object_child) + elif object_child.type == 'identifier': + parts.append(self._get_node_text(object_child)) + + # Then add the property + if property_child: + parts.append(self._get_node_text(property_child)) + + elif n.type == 'identifier': + parts.append(self._get_node_text(n)) + + extract_parts(node) + return '.'.join(parts) if parts else None + + def _register_variable_symbol(self, node, name: str, file_path: str, scope_stack: List[str], value_node=None) -> None: + """Register a variable/constant symbol definition.""" + symbol_id = self._create_variable_symbol_id(name, file_path, scope_stack, value_node) + + # Determine symbol type based on value + symbol_kind = scip_pb2.Variable + doc_type = "JavaScript variable" + + if value_node: + if value_node.type == 'object_expression': + symbol_kind = scip_pb2.Object + doc_type = "JavaScript object" + elif value_node.type == 'new_expression': + symbol_kind = scip_pb2.Variable # new expressions create variables, not classes + doc_type = "JavaScript instance" + elif value_node.type == 'call_expression': + # Check if it's a require call vs regular function call + is_require = False + for child in value_node.children: + if child.type == 'identifier' and self._get_node_text(child) == 'require': + is_require = True + break + if is_require: + symbol_kind = scip_pb2.Namespace + doc_type = "JavaScript import" + else: + symbol_kind = scip_pb2.Variable + doc_type = "JavaScript constant" + + # Create a dummy range for registration + dummy_range = scip_pb2.Range() + dummy_range.start.extend([0, 0]) + dummy_range.end.extend([0, 1]) + + self.reference_resolver.register_symbol_definition( + symbol_id=symbol_id, + file_path=file_path, + definition_range=dummy_range, + symbol_kind=symbol_kind, + display_name=name, + documentation=[doc_type] + ) + + def _handle_destructuring_pattern(self, node, file_path: str, scope_stack: List[str]) -> None: + """Handle destructuring patterns like { v4: uuidv4 }""" + for child in node.children: + if child.type == 'shorthand_property_identifier_pattern': + # Simple destructuring like { prop } + var_name = self._get_node_text(child) + if var_name: + self._register_variable_symbol(child, var_name, file_path, scope_stack) + elif child.type == 'pair_pattern': + # Renamed destructuring like { v4: uuidv4 } + for pair_child in child.children: + if pair_child.type == 'identifier': + var_name = self._get_node_text(pair_child) + if var_name: + self._register_variable_symbol(pair_child, var_name, file_path, scope_stack) + + def _handle_import_statement(self, var_name: str, call_node, file_path: str, scope_stack: List[str]) -> None: + """Handle import statements like const lib = require('module')""" + # Check if this is a require() call + callee = None + module_name = None + + for child in call_node.children: + if child.type == 'identifier': + callee = self._get_node_text(child) + elif child.type == 'arguments': + # Get the module name from arguments + for arg in child.children: + if arg.type == 'string': + module_name = self._get_node_text(arg).strip('"\'') + break + + if callee == 'require' and module_name: + # Classify dependency type + self._classify_and_store_dependency(module_name) + + # Create SCIP standard symbol ID + local_id = ".".join(scope_stack + [var_name]) if scope_stack else var_name + symbol_id = f"local {local_id}(import)" + + dummy_range = scip_pb2.Range() + dummy_range.start.extend([0, 0]) + dummy_range.end.extend([0, 1]) + + self.reference_resolver.register_symbol_definition( + symbol_id=symbol_id, + file_path=file_path, + definition_range=dummy_range, + symbol_kind=scip_pb2.Namespace, + display_name=var_name, + documentation=[f"Import from {module_name}"] + ) + + def _handle_assignment_for_document(self, node, file_path: str, scope_stack: List[str], relationships: Optional[Dict[str, List[tuple]]]) -> tuple[Optional[scip_pb2.Occurrence], Optional[scip_pb2.SymbolInformation]]: + """Handle assignment expressions for document generation""" + left_child = None + right_child = None + + for child in node.children: + if child.type == 'member_expression': + left_child = child + elif child.type in ['function_expression', 'arrow_function']: + right_child = child + + if left_child and right_child: + method_name = self._extract_member_expression_name(left_child) + if method_name: + symbol_id = self._create_function_symbol_id(method_name, file_path, scope_stack) + occurrence = self._create_function_occurrence(right_child, symbol_id) + symbol_relationships = relationships.get(symbol_id, []) if relationships else [] + scip_relationships = self._create_scip_relationships(symbol_relationships) if symbol_relationships else [] + symbol_info = self._create_function_symbol_info(right_child, symbol_id, method_name, scip_relationships) + return occurrence, symbol_info + + return None, None + + def _handle_lexical_for_document(self, node, file_path: str, scope_stack: List[str], relationships: Optional[Dict[str, List[tuple]]]) -> List[tuple]: + """Handle lexical declarations for document generation""" + results = [] + + for child in node.children: + if child.type == 'variable_declarator': + var_name = None + var_value = None + + for declarator_child in child.children: + if declarator_child.type == 'identifier': + var_name = self._get_node_text(declarator_child) + elif declarator_child.type in ['object_expression', 'new_expression', 'call_expression']: + var_value = declarator_child + + if var_name: + # Create occurrence and symbol info for variable + symbol_id = self._create_variable_symbol_id(var_name, file_path, scope_stack, var_value) + occurrence = self._create_variable_occurrence(child, symbol_id) + symbol_info = self._create_variable_symbol_info(child, symbol_id, var_name, var_value) + results.append((occurrence, symbol_info)) + + return results + + def _create_variable_symbol_id(self, name: str, file_path: str, scope_stack: List[str], value_node=None) -> str: + """Create symbol ID for variable.""" + # SCIP standard: local + local_id = ".".join(scope_stack + [name]) if scope_stack else name + + # Determine descriptor based on value type + descriptor = "." # Default for variables + if value_node: + if value_node.type == 'object_expression': + descriptor = "{}" + elif value_node.type == 'new_expression': + descriptor = "." # new expressions are still variables, not classes + elif value_node.type == 'call_expression': + # Check if it's a require call vs regular function call + is_require = False + for child in value_node.children: + if child.type == 'identifier' and hasattr(self, '_get_node_text'): + if self._get_node_text(child) == 'require': + is_require = True + break + descriptor = "(import)" if is_require else "." + + return f"local {local_id}{descriptor}" + + def _create_variable_occurrence(self, node, symbol_id: str) -> Optional[scip_pb2.Occurrence]: + """Create SCIP occurrence for variable.""" + if not self.position_calculator: + return None + + try: + range_obj = self.position_calculator.tree_sitter_node_to_range(node) + occurrence = scip_pb2.Occurrence() + occurrence.symbol = symbol_id + occurrence.symbol_roles = scip_pb2.Definition + occurrence.syntax_kind = scip_pb2.IdentifierConstant + occurrence.range.CopyFrom(range_obj) + return occurrence + except: + return None + + def _create_variable_symbol_info(self, node, symbol_id: str, name: str, value_node=None) -> scip_pb2.SymbolInformation: + """Create SCIP symbol information for variable.""" + symbol_info = scip_pb2.SymbolInformation() + symbol_info.symbol = symbol_id + symbol_info.display_name = name + + # Determine kind based on value - correct classification + if value_node: + if value_node.type == 'object_expression': + symbol_info.kind = scip_pb2.Object + symbol_info.documentation.append("JavaScript object literal") + elif value_node.type == 'new_expression': + symbol_info.kind = scip_pb2.Variable # new expressions create variables, not classes + symbol_info.documentation.append("JavaScript instance variable") + elif value_node.type == 'call_expression': + symbol_info.kind = scip_pb2.Namespace + symbol_info.documentation.append("JavaScript import") + elif value_node.type == 'function_expression': + symbol_info.kind = scip_pb2.Function + symbol_info.documentation.append("JavaScript function variable") + else: + symbol_info.kind = scip_pb2.Variable + symbol_info.documentation.append("JavaScript variable") + else: + symbol_info.kind = scip_pb2.Variable + symbol_info.documentation.append("JavaScript variable") + return symbol_info + + def _extract_functions_from_object(self, object_node, parent_name: str, file_path: str, scope_stack: List[str]) -> None: + """Extract functions from object expressions like { handler: function() {} }""" + for child in object_node.children: + if child.type == 'pair': + prop_name = None + prop_value = None + + for pair_child in child.children: + if pair_child.type in ['identifier', 'property_identifier']: + prop_name = self._get_node_text(pair_child) + elif pair_child.type in ['function_expression', 'arrow_function']: + prop_value = pair_child + + if prop_name and prop_value: + # Register function with context-aware name + func_scope = scope_stack + [parent_name] + self._register_function_symbol(prop_value, prop_name, file_path, func_scope) + + def _extract_functions_from_call_expression(self, call_node, parent_name: str, file_path: str, scope_stack: List[str]) -> None: + """Extract functions from call expressions arguments like rateLimit({ handler: function() {} })""" + for child in call_node.children: + if child.type == 'arguments': + for arg in child.children: + if arg.type == 'object_expression': + self._extract_functions_from_object(arg, parent_name, file_path, scope_stack) + elif arg.type in ['function_expression', 'arrow_function']: + # Anonymous function in call - give it a descriptive name + func_name = f"{parent_name}_callback" + self._register_function_symbol(arg, func_name, file_path, scope_stack) + + def _classify_and_store_dependency(self, module_name: str) -> None: + """Classify and store dependency based on module name.""" + # Standard Node.js built-in modules + node_builtins = { + 'fs', 'path', 'http', 'https', 'url', 'crypto', 'os', 'util', 'events', + 'stream', 'buffer', 'child_process', 'cluster', 'dgram', 'dns', 'net', + 'tls', 'zlib', 'readline', 'repl', 'vm', 'worker_threads', 'async_hooks' + } + + if module_name in node_builtins: + category = 'standard_library' + elif module_name.startswith('./') or module_name.startswith('../') or module_name.startswith('/'): + category = 'local' + else: + category = 'third_party' + + # Avoid duplicates + if module_name not in self.dependencies['imports'][category]: + self.dependencies['imports'][category].append(module_name) + + def get_dependencies(self) -> Dict[str, Any]: + """Get collected dependencies for MCP response.""" + return self.dependencies + + def _reset_dependencies(self) -> None: + """Reset dependency tracking for new file analysis.""" + self.dependencies = { + 'imports': { + 'standard_library': [], + 'third_party': [], + 'local': [] + } + } \ No newline at end of file diff --git a/src/code_index_mcp/scip/strategies/objective_c_strategy.py b/src/code_index_mcp/scip/strategies/objective_c_strategy.py index d8bbf7d..c27dc87 100644 --- a/src/code_index_mcp/scip/strategies/objective_c_strategy.py +++ b/src/code_index_mcp/scip/strategies/objective_c_strategy.py @@ -1,51 +1,67 @@ -"""Objective-C SCIP indexing strategy - SCIP standard compliant.""" +""" +Objective-C Strategy for SCIP indexing using libclang. + +This strategy uses libclang to parse Objective-C source files (.m, .mm, .h) +and extract symbol information following SCIP standards. +""" import logging import os -import re -from typing import List, Optional, Dict, Any, Set +from typing import List, Set, Optional, Tuple, Dict, Any from pathlib import Path -import tree_sitter -from tree_sitter_c import language as c_language +try: + import clang.cindex as clang + from clang.cindex import CursorKind, TypeKind + LIBCLANG_AVAILABLE = True +except ImportError: + LIBCLANG_AVAILABLE = False + clang = None + CursorKind = None + TypeKind = None from .base_strategy import SCIPIndexerStrategy, StrategyError from ..proto import scip_pb2 from ..core.position_calculator import PositionCalculator from ..core.relationship_types import InternalRelationshipType - logger = logging.getLogger(__name__) class ObjectiveCStrategy(SCIPIndexerStrategy): - """SCIP-compliant Objective-C indexing strategy using Tree-sitter + regex patterns.""" - - SUPPORTED_EXTENSIONS = {'.m', '.mm'} - + """SCIP indexing strategy for Objective-C using libclang.""" + + SUPPORTED_EXTENSIONS = {'.m', '.mm', '.h'} + def __init__(self, priority: int = 95): """Initialize the Objective-C strategy.""" super().__init__(priority) + self._processed_symbols: Set[str] = set() + self._symbol_counter = 0 + self.project_path: Optional[str] = None - # Initialize C parser (handles Objective-C syntax reasonably well) - c_lang = tree_sitter.Language(c_language()) - self.parser = tree_sitter.Parser(c_lang) - def can_handle(self, extension: str, file_path: str) -> bool: """Check if this strategy can handle the file type.""" + if not LIBCLANG_AVAILABLE: + logger.warning("libclang not available for Objective-C processing") + return False return extension.lower() in self.SUPPORTED_EXTENSIONS - + def get_language_name(self) -> str: """Get the language name for SCIP symbol generation.""" return "objc" - + def is_available(self) -> bool: """Check if this strategy is available.""" - return True - + return LIBCLANG_AVAILABLE + def _collect_symbol_definitions(self, files: List[str], project_path: str) -> None: """Phase 1: Collect all symbol definitions from Objective-C files.""" logger.debug(f"ObjectiveCStrategy Phase 1: Processing {len(files)} files for symbol collection") + + # Store project path for use in import classification + self.project_path = project_path + processed_count = 0 error_count = 0 @@ -67,9 +83,9 @@ def _collect_symbol_definitions(self, files: List[str], project_path: str) -> No logger.info(f"Phase 1 summary: {processed_count} files processed, {error_count} errors") def _generate_documents_with_references(self, files: List[str], project_path: str, relationships: Optional[Dict[str, List[tuple]]] = None) -> List[scip_pb2.Document]: - """Phase 2: Generate complete SCIP documents with resolved references.""" + """Phase 3: Generate complete SCIP documents with resolved references.""" documents = [] - logger.debug(f"ObjectiveCStrategy Phase 2: Generating documents for {len(files)} files") + logger.debug(f"ObjectiveCStrategy Phase 3: Generating documents for {len(files)} files") processed_count = 0 error_count = 0 total_occurrences = 0 @@ -87,89 +103,22 @@ def _generate_documents_with_references(self, files: List[str], project_path: st processed_count += 1 if i % 10 == 0 or i == len(files): - logger.debug(f"Phase 2 progress: {i}/{len(files)} files, " + logger.debug(f"Phase 3 progress: {i}/{len(files)} files, " f"last file: {relative_path}, " f"{len(document.occurrences) if document else 0} occurrences") except Exception as e: error_count += 1 - logger.error(f"Phase 2 failed for {relative_path}: {e}") + logger.error(f"Phase 3 failed for {relative_path}: {e}") continue - logger.info(f"Phase 2 summary: {processed_count} documents generated, {error_count} errors, " + logger.info(f"Phase 3 summary: {processed_count} documents generated, {error_count} errors, " f"{total_occurrences} total occurrences, {total_symbols} total symbols") return documents - def _collect_symbols_from_file(self, file_path: str, project_path: str) -> None: - """Collect symbol definitions from a single Objective-C file.""" - # Read file content - content = self._read_file_content(file_path) - if not content: - logger.debug(f"Empty file skipped: {os.path.relpath(file_path, project_path)}") - return - - # Parse with Tree-sitter - tree = self._parse_content(content) - if not tree: - logger.debug(f"Parse failed: {os.path.relpath(file_path, project_path)}") - return - - # Collect symbols using both Tree-sitter and regex - relative_path = self._get_relative_path(file_path, project_path) - self._collect_symbols_from_tree_and_regex(tree, relative_path, content) - logger.debug(f"Symbol collection - {relative_path}") - - def _analyze_objc_file(self, file_path: str, project_path: str, relationships: Optional[Dict[str, List[tuple]]] = None) -> Optional[scip_pb2.Document]: - """Analyze a single Objective-C file and generate complete SCIP document.""" - # Read file content - content = self._read_file_content(file_path) - if not content: - return None - - # Parse with Tree-sitter - tree = self._parse_content(content) - if not tree: - return None - - # Create SCIP document - document = scip_pb2.Document() - document.relative_path = self._get_relative_path(file_path, project_path) - document.language = 'objc' if file_path.endswith('.m') else 'objcpp' - - # Analyze AST and generate occurrences - self.position_calculator = PositionCalculator(content) - occurrences, symbols = self._analyze_tree_and_regex_for_document(tree, document.relative_path, content, relationships) - - # Add results to document - document.occurrences.extend(occurrences) - document.symbols.extend(symbols) - - logger.debug(f"Analyzed Objective-C file {document.relative_path}: " - f"{len(document.occurrences)} occurrences, {len(document.symbols)} symbols") - - return document - - def _parse_content(self, content: str) -> Optional: - """Parse Objective-C content with tree-sitter C parser.""" - try: - content_bytes = content.encode('utf-8') - return self.parser.parse(content_bytes) - except Exception as e: - logger.error(f"Failed to parse Objective-C content: {e}") - return None - def _build_symbol_relationships(self, files: List[str], project_path: str) -> Dict[str, List[tuple]]: - """ - Build relationships between Objective-C symbols. - - Args: - files: List of file paths to process - project_path: Project root path - - Returns: - Dictionary mapping symbol_id -> [(target_symbol_id, relationship_type), ...] - """ + """Phase 2: Build relationships between Objective-C symbols.""" logger.debug(f"ObjectiveCStrategy: Building symbol relationships for {len(files)} files") all_relationships = {} @@ -185,699 +134,950 @@ def _build_symbol_relationships(self, files: List[str], project_path: str) -> Di logger.debug(f"ObjectiveCStrategy: Built {total_relationships} relationships for {total_symbols_with_relationships} symbols") return all_relationships - - def _extract_relationships_from_file(self, file_path: str, project_path: str) -> Dict[str, List[tuple]]: - """Extract relationships from a single Objective-C file.""" + + def _collect_symbols_from_file(self, file_path: str, project_path: str) -> None: + """Collect symbol definitions from a single Objective-C file using libclang.""" content = self._read_file_content(file_path) if not content: - return {} - - relationships = {} - relative_path = self._get_relative_path(file_path, project_path) - - # Class inheritance patterns - interface_pattern = r"@interface\s+(\w+)\s*:\s*(\w+)" - for match in re.finditer(interface_pattern, content): - child_class = match.group(1) - parent_class = match.group(2) - - child_symbol_id = self.symbol_manager.create_local_symbol( - language="objc", - file_path=relative_path, - symbol_path=[child_class], - descriptor="#" - ) - parent_symbol_id = self.symbol_manager.create_local_symbol( - language="objc", - file_path=relative_path, - symbol_path=[parent_class], - descriptor="#" + logger.debug(f"Empty file skipped: {os.path.relpath(file_path, project_path)}") + return + + try: + # Parse with libclang + index = clang.Index.create() + translation_unit = index.parse( + file_path, + args=['-ObjC', '-x', 'objective-c'], + options=clang.TranslationUnit.PARSE_DETAILED_PROCESSING_RECORD ) - if child_symbol_id not in relationships: - relationships[child_symbol_id] = [] - relationships[child_symbol_id].append((parent_symbol_id, InternalRelationshipType.INHERITS)) - - # Protocol adoption patterns - protocol_pattern = r"@interface\s+(\w+).*?<(.+?)>" - for match in re.finditer(protocol_pattern, content, re.DOTALL): - class_name = match.group(1) - protocols = [p.strip() for p in match.group(2).split(",")] + if not translation_unit: + logger.debug(f"Parse failed: {os.path.relpath(file_path, project_path)}") + return - class_symbol_id = self.symbol_manager.create_local_symbol( - language="objc", - file_path=relative_path, - symbol_path=[class_name], - descriptor="#" - ) + # Reset processed symbols for each file + self._processed_symbols.clear() + self._symbol_counter = 0 + + # Traverse AST to collect symbols + relative_path = self._get_relative_path(file_path, project_path) + self._traverse_clang_ast_for_symbols(translation_unit.cursor, relative_path, content, file_path) + + # Extract imports/dependencies and register with symbol manager + self._extract_and_register_imports(translation_unit.cursor, file_path, project_path) + + logger.debug(f"Symbol collection completed - {relative_path}") + + except Exception as e: + logger.error(f"Error processing {file_path} with libclang: {e}") + + def _extract_and_register_imports(self, cursor: 'clang.Cursor', file_path: str, project_path: str) -> None: + """Extract imports from AST and register them with the symbol manager.""" + try: + # Traverse AST to find all import statements + self._traverse_ast_for_import_registration(cursor, file_path, project_path) + + except Exception as e: + logger.error(f"Error extracting imports from {file_path}: {e}") + + def _traverse_ast_for_import_registration(self, cursor: 'clang.Cursor', file_path: str, project_path: str) -> None: + """Traverse AST specifically to register imports with the symbol manager.""" + try: + # Process current cursor for import registration + if cursor.kind == CursorKind.INCLUSION_DIRECTIVE: + self._register_import_with_symbol_manager(cursor, file_path, project_path) - for protocol in protocols: - if protocol and protocol.replace(" ", "").isidentifier(): - protocol_symbol_id = self.symbol_manager.create_local_symbol( + # Recursively process children + for child in cursor.get_children(): + self._traverse_ast_for_import_registration(child, file_path, project_path) + + except Exception as e: + logger.error(f"Error traversing AST for import registration: {e}") + + def _register_import_with_symbol_manager(self, cursor: 'clang.Cursor', file_path: str, project_path: str) -> None: + """Register a single import with the symbol manager.""" + try: + # Try to get the included file path + include_path = None + framework_name = None + + # Method 1: Try to get the included file (may fail for system headers) + try: + included_file = cursor.get_included_file() + if included_file: + include_path = str(included_file) + logger.debug(f"Got include path from file: {include_path}") + except Exception as e: + logger.debug(f"Failed to get included file: {e}") + + # Method 2: Try to get from cursor spelling (the actual #import statement) + spelling = cursor.spelling + if spelling: + logger.debug(f"Got cursor spelling: {spelling}") + # Extract framework name from spelling like "Foundation/Foundation.h" or "Person.h" + framework_name = self._extract_framework_name_from_spelling(spelling) + if framework_name: + logger.debug(f"Extracted framework name from spelling: {framework_name}") + + # Classify based on spelling pattern + import_type = self._classify_import_from_spelling(spelling) + logger.debug(f"Classified import as: {import_type}") + + # Only register external dependencies (not local files) + if import_type in ['standard_library', 'third_party']: + if not self.symbol_manager: + logger.error("Symbol manager is None!") + return + + # Determine version if possible (for now, leave empty) + version = "" + + logger.debug(f"Registering external symbol: {framework_name}") + + # Register the import with the moniker manager + symbol_id = self.symbol_manager.create_external_symbol( + language="objc", + package_name=framework_name, + module_path=framework_name, + symbol_name="*", # Framework-level import + version=version, + alias=None + ) + + logger.debug(f"Registered external dependency: {framework_name} ({import_type}) -> {symbol_id}") + return + else: + logger.debug(f"Skipping local import: {framework_name} ({import_type})") + return + + # Method 3: Fallback to include_path if we have it + if include_path: + logger.debug(f"Processing include path: {include_path}") + + # Extract framework/module name + framework_name = self._extract_framework_name(include_path, cursor) + if not framework_name: + logger.debug(f"No framework name extracted from {include_path}") + return + + logger.debug(f"Extracted framework name: {framework_name}") + + # Classify the import type + import_type = self._classify_objc_import(include_path) + logger.debug(f"Classified import as: {import_type}") + + # Only register external dependencies (not local files) + if import_type in ['standard_library', 'third_party']: + if not self.symbol_manager: + logger.error("Symbol manager is None!") + return + + # Determine version if possible (for now, leave empty) + version = self._extract_framework_version(include_path) + + logger.debug(f"Registering external symbol: {framework_name}") + + # Register the import with the moniker manager + symbol_id = self.symbol_manager.create_external_symbol( language="objc", - file_path=relative_path, - symbol_path=[protocol.strip()], - descriptor="#" + package_name=framework_name, + module_path=framework_name, + symbol_name="*", # Framework-level import + version=version, + alias=None ) - if class_symbol_id not in relationships: - relationships[class_symbol_id] = [] - relationships[class_symbol_id].append((protocol_symbol_id, InternalRelationshipType.IMPLEMENTS)) - - logger.debug(f"Extracted {len(relationships)} relationships from {relative_path}") - return relationships - - # Symbol collection methods - def _collect_symbols_from_tree_and_regex(self, tree, file_path: str, content: str) -> None: - """Collect symbols using both Tree-sitter and regex patterns.""" - scope_stack = [] - lines = content.split('\n') - - # First, use Tree-sitter for C-like constructs - self._collect_symbols_from_tree_sitter(tree.root_node, file_path, scope_stack, content) - - # Then, use regex for Objective-C specific constructs - self._collect_symbols_from_regex_patterns(file_path, lines, scope_stack) - - def _collect_symbols_from_tree_sitter(self, node, file_path: str, scope_stack: List[str], content: str): - """Collect symbols from Tree-sitter AST for C-like constructs.""" - node_type = node.type - - if node_type == 'function_definition': - self._register_c_function_symbol(node, file_path, scope_stack, content) - elif node_type == 'struct_specifier': - self._register_struct_symbol(node, file_path, scope_stack, content) - elif node_type == 'enum_specifier': - self._register_enum_symbol(node, file_path, scope_stack, content) - elif node_type == 'typedef_declaration': - self._register_typedef_symbol(node, file_path, scope_stack, content) - - # Recursively analyze child nodes - for child in node.children: - self._collect_symbols_from_tree_sitter(child, file_path, scope_stack, content) - - def _collect_symbols_from_regex_patterns(self, file_path: str, lines: List[str], scope_stack: List[str]): - """Collect Objective-C specific symbols using regex patterns.""" - patterns = { - 'interface': re.compile(r'@interface\s+(\w+)(?:\s*:\s*(\w+))?', re.MULTILINE), - 'implementation': re.compile(r'@implementation\s+(\w+)', re.MULTILINE), - 'protocol': re.compile(r'@protocol\s+(\w+)', re.MULTILINE), - 'property': re.compile(r'@property[^;]*?\s+(\w+)\s*;', re.MULTILINE), - 'instance_method': re.compile(r'^[-]\s*\([^)]*\)\s*(\w+)', re.MULTILINE), - 'class_method': re.compile(r'^[+]\s*\([^)]*\)\s*(\w+)', re.MULTILINE), - 'category': re.compile(r'@interface\s+(\w+)\s*\(\s*(\w*)\s*\)', re.MULTILINE), - } - - for line_num, line in enumerate(lines): - line = line.strip() - - # @interface declarations - match = patterns['interface'].match(line) - if match: - class_name = match.group(1) - self._register_objc_class_symbol(class_name, file_path, scope_stack, "Objective-C interface") - continue + logger.debug(f"Registered external dependency: {framework_name} ({import_type}) -> {symbol_id}") + else: + logger.debug(f"Skipping local import: {framework_name} ({import_type})") + else: + logger.debug("No include path or spelling found for cursor") + + except Exception as e: + logger.error(f"Error registering import with symbol manager: {e}") + import traceback + logger.error(f"Traceback: {traceback.format_exc()}") - # @implementation - match = patterns['implementation'].match(line) - if match: - class_name = match.group(1) - self._register_objc_class_symbol(class_name, file_path, scope_stack, "Objective-C implementation") - continue + def _extract_framework_name_from_spelling(self, spelling: str) -> Optional[str]: + """Extract framework name from cursor spelling.""" + try: + # Remove quotes and angle brackets + clean_spelling = spelling.strip('"<>') + + # For framework imports like "Foundation/Foundation.h" + if '/' in clean_spelling: + parts = clean_spelling.split('/') + if len(parts) >= 2: + framework_name = parts[0] + return framework_name + + # For simple includes like "MyHeader.h" + header_name = clean_spelling.replace('.h', '').replace('.m', '').replace('.mm', '') + return header_name + + except Exception as e: + logger.debug(f"Error extracting framework name from spelling {spelling}: {e}") + return None - # @protocol - match = patterns['protocol'].match(line) - if match: - protocol_name = match.group(1) - self._register_objc_protocol_symbol(protocol_name, file_path, scope_stack) - continue + def _classify_import_from_spelling(self, spelling: str) -> str: + """Classify import based on spelling pattern.""" + try: + # Remove quotes and angle brackets + clean_spelling = spelling.strip('"<>') + + # Check if it's a known system framework by name (since cursor.spelling doesn't include brackets) + if '/' in clean_spelling: + framework_name = clean_spelling.split('/')[0] + system_frameworks = { + 'Foundation', 'UIKit', 'CoreData', 'CoreGraphics', 'QuartzCore', + 'AVFoundation', 'CoreLocation', 'MapKit', 'CoreAnimation', + 'Security', 'SystemConfiguration', 'CFNetwork', 'CoreFoundation', + 'AppKit', 'Cocoa', 'WebKit', 'JavaScriptCore', 'Metal', 'MetalKit', + 'GameplayKit', 'SpriteKit', 'SceneKit', 'ARKit', 'Vision', 'CoreML' + } + if framework_name in system_frameworks: + return 'standard_library' + + # Check for single framework names (like just "Foundation.h") + framework_name_only = clean_spelling.replace('.h', '').replace('.framework', '') + system_frameworks = { + 'Foundation', 'UIKit', 'CoreData', 'CoreGraphics', 'QuartzCore', + 'AVFoundation', 'CoreLocation', 'MapKit', 'CoreAnimation', + 'Security', 'SystemConfiguration', 'CFNetwork', 'CoreFoundation', + 'AppKit', 'Cocoa', 'WebKit', 'JavaScriptCore', 'Metal', 'MetalKit', + 'GameplayKit', 'SpriteKit', 'SceneKit', 'ARKit', 'Vision', 'CoreML' + } + if framework_name_only in system_frameworks: + return 'standard_library' + + # Angle brackets indicate system headers (if we had them) + if spelling.startswith('<') and spelling.endswith('>'): + return 'standard_library' + + # Quotes indicate local or third-party headers + elif spelling.startswith('"') and spelling.endswith('"'): + # Check for common third-party patterns + if any(pattern in clean_spelling.lower() for pattern in ['pods/', 'carthage/', 'node_modules/']): + return 'third_party' + + # Default for quoted imports + return 'local' + + # Check for common third-party patterns in the path + if any(pattern in clean_spelling.lower() for pattern in ['pods/', 'carthage/', 'node_modules/']): + return 'third_party' + + # Check if it looks like a local header (simple filename) + if '/' not in clean_spelling and clean_spelling.endswith('.h'): + return 'local' + + # Fallback: if it contains system-like paths, classify as standard_library + if any(pattern in clean_spelling.lower() for pattern in ['/system/', '/usr/', '/applications/xcode']): + return 'standard_library' + + # Default fallback + return 'local' + + except Exception as e: + logger.debug(f"Error classifying import from spelling {spelling}: {e}") + return 'local' - # @property - match = patterns['property'].search(line) - if match: - property_name = match.group(1) - self._register_objc_property_symbol(property_name, file_path, scope_stack) - continue + def _extract_framework_version(self, include_path: str) -> str: + """Extract framework version from include path if available.""" + # For now, return empty string. Could be enhanced to detect versions + # from CocoaPods Podfile.lock, Carthage, or other dependency managers + return "" - # Instance methods - match = patterns['instance_method'].match(line) - if match: - method_name = match.group(1) - self._register_objc_method_symbol(method_name, False, file_path, scope_stack) - continue + def _analyze_objc_file(self, file_path: str, project_path: str, relationships: Optional[Dict[str, List[tuple]]] = None) -> Optional[scip_pb2.Document]: + """Analyze a single Objective-C file and generate complete SCIP document.""" + content = self._read_file_content(file_path) + if not content: + return None - # Class methods - match = patterns['class_method'].match(line) - if match: - method_name = match.group(1) - self._register_objc_method_symbol(method_name, True, file_path, scope_stack) - continue + try: + # Parse with libclang + index = clang.Index.create() + translation_unit = index.parse( + file_path, + args=['-ObjC', '-x', 'objective-c'], + options=clang.TranslationUnit.PARSE_DETAILED_PROCESSING_RECORD + ) + + if not translation_unit: + return None - # Document analysis methods - def _analyze_tree_and_regex_for_document(self, tree, file_path: str, content: str) -> tuple: - """Analyze using both Tree-sitter and regex patterns to generate SCIP data.""" - occurrences = [] - symbols = [] - scope_stack = [] - lines = content.split('\n') - - # First, use Tree-sitter for C-like constructs - tree_occs, tree_syms = self._analyze_tree_sitter_for_document(tree.root_node, file_path, scope_stack, content) - occurrences.extend(tree_occs) - symbols.extend(tree_syms) - - # Then, use regex for Objective-C specific constructs - regex_occs, regex_syms = self._analyze_regex_patterns_for_document(file_path, lines, scope_stack) - occurrences.extend(regex_occs) - symbols.extend(regex_syms) - - return occurrences, symbols + # Create SCIP document + document = scip_pb2.Document() + document.relative_path = self._get_relative_path(file_path, project_path) + document.language = self._get_document_language(file_path) - def _analyze_tree_sitter_for_document(self, node, file_path: str, scope_stack: List[str], content: str) -> tuple: - """Analyze Tree-sitter nodes for C-like constructs and generate SCIP data.""" - occurrences = [] - symbols = [] - - node_type = node.type - - if node_type == 'function_definition': - occ, sym = self._process_c_function_for_document(node, file_path, scope_stack, content) - if occ: occurrences.append(occ) - if sym: symbols.append(sym) - elif node_type == 'struct_specifier': - occ, sym = self._process_struct_for_document(node, file_path, scope_stack, content) - if occ: occurrences.append(occ) - if sym: symbols.append(sym) - elif node_type == 'enum_specifier': - occ, sym = self._process_enum_for_document(node, file_path, scope_stack, content) - if occ: occurrences.append(occ) - if sym: symbols.append(sym) - elif node_type == 'typedef_declaration': - occ, sym = self._process_typedef_for_document(node, file_path, scope_stack, content) - if occ: occurrences.append(occ) - if sym: symbols.append(sym) - elif node_type == 'identifier': - occ = self._process_identifier_reference_for_document(node, file_path, scope_stack, content) - if occ: occurrences.append(occ) - - # Recursively analyze child nodes - for child in node.children: - child_occs, child_syms = self._analyze_tree_sitter_for_document(child, file_path, scope_stack, content) - occurrences.extend(child_occs) - symbols.extend(child_syms) - - return occurrences, symbols + # Initialize position calculator + self.position_calculator = PositionCalculator(content) + + # Reset processed symbols for each file + self._processed_symbols.clear() + self._symbol_counter = 0 + + # Generate occurrences and symbols + occurrences = [] + symbols = [] + + # Traverse AST for document generation + self._traverse_clang_ast_for_document(translation_unit.cursor, content, occurrences, symbols, relationships) - def _analyze_regex_patterns_for_document(self, file_path: str, lines: List[str], scope_stack: List[str]) -> tuple: - """Analyze Objective-C specific patterns using regex for SCIP document generation.""" - occurrences = [] - symbols = [] - - patterns = { - 'interface': re.compile(r'@interface\s+(\w+)(?:\s*:\s*(\w+))?', re.MULTILINE), - 'implementation': re.compile(r'@implementation\s+(\w+)', re.MULTILINE), - 'protocol': re.compile(r'@protocol\s+(\w+)', re.MULTILINE), - 'property': re.compile(r'@property[^;]*?\s+(\w+)\s*;', re.MULTILINE), - 'instance_method': re.compile(r'^[-]\s*\([^)]*\)\s*(\w+)', re.MULTILINE), - 'class_method': re.compile(r'^[+]\s*\([^)]*\)\s*(\w+)', re.MULTILINE), - } - - for line_num, line in enumerate(lines): - line = line.strip() - - # @interface declarations - match = patterns['interface'].match(line) - if match: - class_name = match.group(1) - occ, sym = self._create_objc_class_symbol_for_document(line_num, class_name, file_path, scope_stack, "Objective-C interface") - if occ: occurrences.append(occ) - if sym: symbols.append(sym) - continue + # Add results to document + document.occurrences.extend(occurrences) + document.symbols.extend(symbols) - # @implementation - match = patterns['implementation'].match(line) - if match: - class_name = match.group(1) - occ, sym = self._create_objc_class_symbol_for_document(line_num, class_name, file_path, scope_stack, "Objective-C implementation") - if occ: occurrences.append(occ) - if sym: symbols.append(sym) - continue + logger.debug(f"Analyzed Objective-C file {document.relative_path}: " + f"{len(document.occurrences)} occurrences, {len(document.symbols)} symbols") - # @protocol - match = patterns['protocol'].match(line) - if match: - protocol_name = match.group(1) - occ, sym = self._create_objc_protocol_symbol_for_document(line_num, protocol_name, file_path, scope_stack) - if occ: occurrences.append(occ) - if sym: symbols.append(sym) - continue + return document + + except Exception as e: + logger.error(f"Error analyzing {file_path} with libclang: {e}") + return None - # @property - match = patterns['property'].search(line) - if match: - property_name = match.group(1) - occ, sym = self._create_objc_property_symbol_for_document(line_num, property_name, file_path, scope_stack) - if occ: occurrences.append(occ) - if sym: symbols.append(sym) - continue + def _traverse_clang_ast_for_symbols(self, cursor: 'clang.Cursor', file_path: str, content: str, full_file_path: str) -> None: + """Traverse libclang AST for symbol definitions (Phase 1).""" + try: + # Process current cursor + self._process_cursor_for_symbols(cursor, file_path, content, full_file_path) + + # Recursively process children + for child in cursor.get_children(): + self._traverse_clang_ast_for_symbols(child, file_path, content, full_file_path) + + except Exception as e: + logger.error(f"Error traversing AST for symbols: {e}") - # Instance methods - match = patterns['instance_method'].match(line) - if match: - method_name = match.group(1) - occ, sym = self._create_objc_method_symbol_for_document(line_num, method_name, False, file_path, scope_stack) - if occ: occurrences.append(occ) - if sym: symbols.append(sym) - continue + def _traverse_clang_ast_for_imports(self, cursor: 'clang.Cursor', file_path: str, imports: 'ImportGroup') -> None: + """Traverse libclang AST specifically for import/include statements.""" + try: + # Process current cursor for imports + self._process_cursor_for_imports(cursor, file_path, imports) + + # Recursively process children + for child in cursor.get_children(): + self._traverse_clang_ast_for_imports(child, file_path, imports) + + except Exception as e: + logger.error(f"Error traversing AST for imports: {e}") - # Class methods - match = patterns['class_method'].match(line) - if match: - method_name = match.group(1) - occ, sym = self._create_objc_method_symbol_for_document(line_num, method_name, True, file_path, scope_stack) - if occ: occurrences.append(occ) - if sym: symbols.append(sym) - continue - - return occurrences, symbols - - # Symbol registration methods (Phase 1) - def _register_c_function_symbol(self, node, file_path: str, scope_stack: List[str], content: str): - """Register a C function symbol.""" - declarator = self._find_child_by_type(node, 'function_declarator') - if declarator: - name_node = self._find_child_by_type(declarator, 'identifier') - if name_node: - name = self._get_node_text(name_node, content) - if name: - symbol_id = self.symbol_manager.create_local_symbol( - language="objc", - file_path=file_path, - symbol_path=scope_stack + [name], - descriptor="()." - ) - dummy_range = scip_pb2.Range() - dummy_range.start.extend([0, 0]) - dummy_range.end.extend([0, 1]) - self.reference_resolver.register_symbol_definition( - symbol_id=symbol_id, - file_path=file_path, - definition_range=dummy_range, - symbol_kind=scip_pb2.Function, - display_name=name, - documentation=["C function"] - ) + def _traverse_clang_ast_for_document(self, cursor: 'clang.Cursor', content: str, occurrences: List, symbols: List, relationships: Optional[Dict[str, List[tuple]]] = None) -> None: + """Traverse libclang AST for document generation (Phase 3).""" + try: + # Process current cursor + self._process_cursor_for_document(cursor, content, occurrences, symbols, relationships) + + # Recursively process children + for child in cursor.get_children(): + self._traverse_clang_ast_for_document(child, content, occurrences, symbols, relationships) + + except Exception as e: + logger.error(f"Error traversing AST for document: {e}") - def _register_struct_symbol(self, node, file_path: str, scope_stack: List[str], content: str): - """Register a struct symbol.""" - name_node = self._find_child_by_type(node, 'type_identifier') - if name_node: - name = self._get_node_text(name_node, content) - if name: - symbol_id = self.symbol_manager.create_local_symbol( - language="objc", - file_path=file_path, - symbol_path=scope_stack + [name], - descriptor="#" - ) - dummy_range = scip_pb2.Range() - dummy_range.start.extend([0, 0]) - dummy_range.end.extend([0, 1]) + def _process_cursor_for_symbols(self, cursor: 'clang.Cursor', file_path: str, content: str, full_file_path: str) -> None: + """Process a cursor for symbol registration (Phase 1).""" + try: + # Skip invalid cursors or those outside our file + if not cursor.location.file or cursor.spelling == "": + return + + # Check if cursor is in the file we're processing + cursor_file = str(cursor.location.file) + if not cursor_file.endswith(os.path.basename(full_file_path)): + return + + cursor_kind = cursor.kind + symbol_name = cursor.spelling + + # Map libclang cursor kinds to SCIP symbols + symbol_info = self._map_cursor_to_symbol(cursor, symbol_name) + if not symbol_info: + return + + symbol_id, symbol_kind, symbol_roles = symbol_info + + # Avoid duplicates + duplicate_key = f"{symbol_id}:{cursor.location.line}:{cursor.location.column}" + if duplicate_key in self._processed_symbols: + return + self._processed_symbols.add(duplicate_key) + + # Calculate position + location = cursor.location + if location.line is not None and location.column is not None: + # libclang uses 1-based indexing, convert to 0-based + line = location.line - 1 + column = location.column - 1 + + # Calculate end position (approximate) + end_line = line + end_column = column + len(symbol_name) + + # Register symbol with reference resolver + if self.position_calculator: + range_obj = self.position_calculator.line_col_to_range(line, column, end_line, end_column) + else: + # Create a simple range object if position_calculator is not available + from ..proto.scip_pb2 import Range + range_obj = Range() + range_obj.start.extend([line, column]) + range_obj.end.extend([end_line, end_column]) self.reference_resolver.register_symbol_definition( symbol_id=symbol_id, file_path=file_path, - definition_range=dummy_range, - symbol_kind=scip_pb2.Struct, - display_name=name, - documentation=["C struct"] + definition_range=range_obj, + symbol_kind=symbol_kind, + display_name=symbol_name, + documentation=[f"Objective-C {cursor_kind.name}"] ) + + logger.debug(f"Registered Objective-C symbol: {symbol_name} ({cursor_kind.name}) at {line}:{column}") + + except Exception as e: + logger.error(f"Error processing cursor for symbols {cursor.spelling}: {e}") - def _register_enum_symbol(self, node, file_path: str, scope_stack: List[str], content: str): - """Register an enum symbol.""" - name_node = self._find_child_by_type(node, 'type_identifier') - if name_node: - name = self._get_node_text(name_node, content) - if name: - symbol_id = self.symbol_manager.create_local_symbol( - language="objc", - file_path=file_path, - symbol_path=scope_stack + [name], - descriptor="#" - ) - dummy_range = scip_pb2.Range() - dummy_range.start.extend([0, 0]) - dummy_range.end.extend([0, 1]) - self.reference_resolver.register_symbol_definition( - symbol_id=symbol_id, - file_path=file_path, - definition_range=dummy_range, - symbol_kind=scip_pb2.Enum, - display_name=name, - documentation=["C enum"] - ) + def _process_cursor_for_document(self, cursor: 'clang.Cursor', content: str, occurrences: List, symbols: List, relationships: Optional[Dict[str, List[tuple]]] = None) -> None: + """Process a cursor for document generation (Phase 3).""" + try: + # Skip invalid cursors or those outside our file + if not cursor.location.file or cursor.spelling == "": + return + + cursor_kind = cursor.kind + symbol_name = cursor.spelling + + # Map libclang cursor kinds to SCIP symbols + symbol_info = self._map_cursor_to_symbol(cursor, symbol_name) + if not symbol_info: + return + + symbol_id, symbol_kind, symbol_roles = symbol_info + + # Avoid duplicates + duplicate_key = f"{symbol_id}:{cursor.location.line}:{cursor.location.column}" + if duplicate_key in self._processed_symbols: + return + self._processed_symbols.add(duplicate_key) + + # Calculate position + location = cursor.location + if location.line is not None and location.column is not None: + # libclang uses 1-based indexing, convert to 0-based + line = location.line - 1 + column = location.column - 1 + + # Calculate end position (approximate) + end_line = line + end_column = column + len(symbol_name) + + # Create SCIP occurrence + occurrence = self._create_occurrence(symbol_id, line, column, end_line, end_column, symbol_roles) + if occurrence: + occurrences.append(occurrence) + + # Get relationships for this symbol + symbol_relationships = relationships.get(symbol_id, []) if relationships else [] + scip_relationships = self._create_scip_relationships(symbol_relationships) if symbol_relationships else [] + + # Create SCIP symbol information with relationships + symbol_info_obj = self._create_symbol_information_with_relationships(symbol_id, symbol_name, symbol_kind, scip_relationships) + if symbol_info_obj: + symbols.append(symbol_info_obj) + + logger.debug(f"Added Objective-C symbol: {symbol_name} ({cursor_kind.name}) at {line}:{column} with {len(scip_relationships)} relationships") + + except Exception as e: + logger.error(f"Error processing cursor for document {cursor.spelling}: {e}") - def _register_typedef_symbol(self, node, file_path: str, scope_stack: List[str], content: str): - """Register a typedef symbol.""" - name_node = self._find_child_by_type(node, 'type_identifier') - if name_node: - name = self._get_node_text(name_node, content) - if name: - symbol_id = self.symbol_manager.create_local_symbol( - language="objc", - file_path=file_path, - symbol_path=scope_stack + [name], - descriptor="#" - ) - dummy_range = scip_pb2.Range() - dummy_range.start.extend([0, 0]) - dummy_range.end.extend([0, 1]) - self.reference_resolver.register_symbol_definition( - symbol_id=symbol_id, - file_path=file_path, - definition_range=dummy_range, - symbol_kind=scip_pb2.TypeParameter, - display_name=name, - documentation=["C typedef"] - ) + def _process_cursor_for_imports(self, cursor: 'clang.Cursor', file_path: str, imports: 'ImportGroup') -> None: + """Process a cursor for import/include statements.""" + try: + # Skip invalid cursors or those outside our file + if not cursor.location.file: + return - def _register_objc_class_symbol(self, name: str, file_path: str, scope_stack: List[str], description: str): - """Register an Objective-C class/interface symbol.""" - symbol_id = self.symbol_manager.create_local_symbol( - language="objc", - file_path=file_path, - symbol_path=scope_stack + [name], - descriptor="#" - ) - dummy_range = scip_pb2.Range() - dummy_range.start.extend([0, 0]) - dummy_range.end.extend([0, 1]) - self.reference_resolver.register_symbol_definition( - symbol_id=symbol_id, - file_path=file_path, - definition_range=dummy_range, - symbol_kind=scip_pb2.Class, - display_name=name, - documentation=[description] - ) - - def _register_objc_protocol_symbol(self, name: str, file_path: str, scope_stack: List[str]): - """Register an Objective-C protocol symbol.""" - symbol_id = self.symbol_manager.create_local_symbol( - language="objc", - file_path=file_path, - symbol_path=scope_stack + [name], - descriptor="#" - ) - dummy_range = scip_pb2.Range() - dummy_range.start.extend([0, 0]) - dummy_range.end.extend([0, 1]) - self.reference_resolver.register_symbol_definition( - symbol_id=symbol_id, - file_path=file_path, - definition_range=dummy_range, - symbol_kind=scip_pb2.Interface, - display_name=name, - documentation=["Objective-C protocol"] - ) - - def _register_objc_property_symbol(self, name: str, file_path: str, scope_stack: List[str]): - """Register an Objective-C property symbol.""" - symbol_id = self.symbol_manager.create_local_symbol( - language="objc", - file_path=file_path, - symbol_path=scope_stack + [name], - descriptor="" - ) - dummy_range = scip_pb2.Range() - dummy_range.start.extend([0, 0]) - dummy_range.end.extend([0, 1]) - self.reference_resolver.register_symbol_definition( - symbol_id=symbol_id, - file_path=file_path, - definition_range=dummy_range, - symbol_kind=scip_pb2.Property, - display_name=name, - documentation=["Objective-C property"] - ) - - def _register_objc_method_symbol(self, name: str, is_class_method: bool, file_path: str, scope_stack: List[str]): - """Register an Objective-C method symbol.""" - symbol_id = self.symbol_manager.create_local_symbol( - language="objc", - file_path=file_path, - symbol_path=scope_stack + [name], - descriptor="()." - ) - dummy_range = scip_pb2.Range() - dummy_range.start.extend([0, 0]) - dummy_range.end.extend([0, 1]) - self.reference_resolver.register_symbol_definition( - symbol_id=symbol_id, - file_path=file_path, - definition_range=dummy_range, - symbol_kind=scip_pb2.Method, - display_name=name, - documentation=[f"Objective-C {'Class' if is_class_method else 'Instance'} method"] - ) - - # Document processing methods (Phase 2) - def _process_c_function_for_document(self, node, file_path: str, scope_stack: List[str], content: str) -> tuple: - """Process C function for SCIP document generation.""" - declarator = self._find_child_by_type(node, 'function_declarator') - if declarator: - name_node = self._find_child_by_type(declarator, 'identifier') - if name_node: - name = self._get_node_text(name_node, content) - if name: - return self._create_function_symbol_for_document(node, name_node, name, file_path, scope_stack, "C function") - return None, None - - def _process_struct_for_document(self, node, file_path: str, scope_stack: List[str], content: str) -> tuple: - """Process struct for SCIP document generation.""" - name_node = self._find_child_by_type(node, 'type_identifier') - if name_node: - name = self._get_node_text(name_node, content) - if name: - return self._create_type_symbol_for_document(node, name_node, name, scip_pb2.Struct, file_path, scope_stack, "C struct") - return None, None - - def _process_enum_for_document(self, node, file_path: str, scope_stack: List[str], content: str) -> tuple: - """Process enum for SCIP document generation.""" - name_node = self._find_child_by_type(node, 'type_identifier') - if name_node: - name = self._get_node_text(name_node, content) - if name: - return self._create_type_symbol_for_document(node, name_node, name, scip_pb2.Enum, file_path, scope_stack, "C enum") - return None, None - - def _process_typedef_for_document(self, node, file_path: str, scope_stack: List[str], content: str) -> tuple: - """Process typedef for SCIP document generation.""" - name_node = self._find_child_by_type(node, 'type_identifier') - if name_node: - name = self._get_node_text(name_node, content) - if name: - return self._create_type_symbol_for_document(node, name_node, name, scip_pb2.TypeParameter, file_path, scope_stack, "C typedef") - return None, None - - def _process_identifier_reference_for_document(self, node, file_path: str, scope_stack: List[str], content: str) -> Optional[scip_pb2.Occurrence]: - """Process identifier reference for SCIP document generation.""" - # Only handle if it's not part of a declaration - parent = node.parent - if parent and parent.type not in [ - 'function_definition', 'struct_specifier', 'enum_specifier', 'typedef_declaration' - ]: - name = self._get_node_text(node, content) - if name and len(name) > 1: # Avoid single letters - # Try to resolve the reference - symbol_id = self.reference_resolver.resolve_reference(name, file_path) - if symbol_id: - range_obj = self.position_calculator.tree_sitter_node_to_range(node) - return self._create_occurrence( - symbol_id, range_obj, 0, scip_pb2.Identifier # 0 = reference role - ) - return None - - # Symbol creation helpers for documents - def _create_function_symbol_for_document(self, node, name_node, name: str, file_path: str, scope_stack: List[str], description: str) -> tuple: - """Create a function symbol for SCIP document.""" - symbol_id = self.symbol_manager.create_local_symbol( - language="objc", - file_path=file_path, - symbol_path=scope_stack + [name], - descriptor="()." - ) - - # Create definition occurrence - range_obj = self.position_calculator.tree_sitter_node_to_range(name_node) - occurrence = self._create_occurrence( - symbol_id, range_obj, scip_pb2.Definition, scip_pb2.IdentifierFunction - ) - - # Create symbol information - symbol_info = self._create_symbol_information( - symbol_id, name, scip_pb2.Function, [description] - ) - - return occurrence, symbol_info - - def _create_type_symbol_for_document(self, node, name_node, name: str, symbol_kind: int, file_path: str, scope_stack: List[str], description: str) -> tuple: - """Create a type symbol for SCIP document.""" - symbol_id = self.symbol_manager.create_local_symbol( - language="objc", - file_path=file_path, - symbol_path=scope_stack + [name], - descriptor="#" - ) - - # Create definition occurrence - range_obj = self.position_calculator.tree_sitter_node_to_range(name_node) - occurrence = self._create_occurrence( - symbol_id, range_obj, scip_pb2.Definition, scip_pb2.IdentifierType - ) - - # Create symbol information - symbol_info = self._create_symbol_information( - symbol_id, name, symbol_kind, [description] - ) - - return occurrence, symbol_info - - def _create_objc_class_symbol_for_document(self, line_num: int, name: str, file_path: str, scope_stack: List[str], description: str) -> tuple: - """Create an Objective-C class/interface symbol for SCIP document.""" - symbol_id = self.symbol_manager.create_local_symbol( - language="objc", - file_path=file_path, - symbol_path=scope_stack + [name], - descriptor="#" - ) - - # Create definition occurrence from line position - start_col, end_col = self.position_calculator.find_name_in_line(line_num, name) - range_obj = self.position_calculator.line_col_to_range( - line_num, start_col, line_num, end_col - ) - - occurrence = self._create_occurrence( - symbol_id, range_obj, scip_pb2.Definition, scip_pb2.IdentifierType - ) - - # Create symbol information - symbol_info = self._create_symbol_information( - symbol_id, name, scip_pb2.Class, [description] - ) - - return occurrence, symbol_info - - def _create_objc_protocol_symbol_for_document(self, line_num: int, name: str, file_path: str, scope_stack: List[str]) -> tuple: - """Create an Objective-C protocol symbol for SCIP document.""" - symbol_id = self.symbol_manager.create_local_symbol( - language="objc", - file_path=file_path, - symbol_path=scope_stack + [name], - descriptor="#" - ) - - # Create definition occurrence from line position - start_col, end_col = self.position_calculator.find_name_in_line(line_num, name) - range_obj = self.position_calculator.line_col_to_range( - line_num, start_col, line_num, end_col - ) - - occurrence = self._create_occurrence( - symbol_id, range_obj, scip_pb2.Definition, scip_pb2.IdentifierType - ) - - # Create symbol information - symbol_info = self._create_symbol_information( - symbol_id, name, scip_pb2.Interface, ["Objective-C protocol"] - ) - - return occurrence, symbol_info - - def _create_objc_property_symbol_for_document(self, line_num: int, name: str, file_path: str, scope_stack: List[str]) -> tuple: - """Create an Objective-C property symbol for SCIP document.""" - symbol_id = self.symbol_manager.create_local_symbol( - language="objc", - file_path=file_path, - symbol_path=scope_stack + [name], - descriptor="" - ) - - # Create definition occurrence from line position - start_col, end_col = self.position_calculator.find_name_in_line(line_num, name) - range_obj = self.position_calculator.line_col_to_range( - line_num, start_col, line_num, end_col - ) - - occurrence = self._create_occurrence( - symbol_id, range_obj, scip_pb2.Definition, scip_pb2.IdentifierLocal - ) + cursor_kind = cursor.kind + + # Process inclusion directives (#import, #include, @import) + if cursor_kind == CursorKind.INCLUSION_DIRECTIVE: + self._process_inclusion_directive(cursor, file_path, imports) + + except Exception as e: + logger.error(f"Error processing cursor for imports: {e}") + + def _process_inclusion_directive(self, cursor: 'clang.Cursor', file_path: str, imports: 'ImportGroup') -> None: + """Process a single #import/#include/@import directive.""" + try: + # Get the included file + included_file = cursor.get_included_file() + if not included_file: + return + + include_path = str(included_file) + + # Extract framework/module name + framework_name = self._extract_framework_name(include_path, cursor) + if not framework_name: + return + + # Classify the import type + import_type = self._classify_objc_import(include_path) + + # Add to imports + imports.add_import(framework_name, import_type) + + # Register with moniker manager for external dependencies + if import_type in ['standard_library', 'third_party'] and self.symbol_manager: + self._register_framework_dependency(framework_name, import_type, include_path) + + logger.debug(f"Processed import: {framework_name} ({import_type}) from {include_path}") + + except Exception as e: + logger.error(f"Error processing inclusion directive: {e}") + + def _extract_framework_name(self, include_path: str, cursor: 'clang.Cursor') -> Optional[str]: + """Extract framework/module name from include path.""" + try: + # Get the original spelling from the cursor (what was actually written) + spelling = cursor.spelling + if spelling: + # Remove quotes and angle brackets + clean_spelling = spelling.strip('"<>') + + # For framework imports like + if '/' in clean_spelling: + parts = clean_spelling.split('/') + if len(parts) >= 2: + framework_name = parts[0] + # Common iOS/macOS frameworks + if framework_name in ['Foundation', 'UIKit', 'CoreData', 'CoreGraphics', + 'QuartzCore', 'AVFoundation', 'CoreLocation', 'MapKit']: + return framework_name + # For other frameworks, use the framework name + return framework_name + + # For simple includes like "MyHeader.h" + header_name = clean_spelling.replace('.h', '').replace('.m', '').replace('.mm', '') + return header_name + + # Fallback: extract from full path + if '/' in include_path: + path_parts = include_path.split('/') + + # Look for .framework in path + for i, part in enumerate(path_parts): + if part.endswith('.framework') and i + 1 < len(path_parts): + return part.replace('.framework', '') + + # Look for Headers directory (common in frameworks) + if 'Headers' in path_parts: + headers_idx = path_parts.index('Headers') + if headers_idx > 0: + framework_part = path_parts[headers_idx - 1] + if framework_part.endswith('.framework'): + return framework_part.replace('.framework', '') + + # Use the filename without extension + filename = path_parts[-1] + return filename.replace('.h', '').replace('.m', '').replace('.mm', '') + + return None + + except Exception as e: + logger.debug(f"Error extracting framework name from {include_path}: {e}") + return None + + def _classify_objc_import(self, include_path: str) -> str: + """Classify Objective-C import as system, third-party, or local.""" + try: + # System frameworks (typical macOS/iOS system paths) + system_indicators = [ + '/Applications/Xcode.app/', + '/System/Library/', + '/usr/include/', + 'Platforms/iPhoneOS.platform/', + 'Platforms/iPhoneSimulator.platform/', + 'Platforms/MacOSX.platform/' + ] + + for indicator in system_indicators: + if indicator in include_path: + return 'standard_library' + + # Common system frameworks by name + system_frameworks = { + 'Foundation', 'UIKit', 'CoreData', 'CoreGraphics', 'QuartzCore', + 'AVFoundation', 'CoreLocation', 'MapKit', 'CoreAnimation', + 'Security', 'SystemConfiguration', 'CFNetwork', 'CoreFoundation', + 'AppKit', 'Cocoa', 'WebKit', 'JavaScriptCore' + } + + for framework in system_frameworks: + if f'/{framework}.framework/' in include_path or f'{framework}/' in include_path: + return 'standard_library' + + # Third-party dependency managers + third_party_indicators = [ + '/Pods/', # CocoaPods + '/Carthage/', # Carthage + '/node_modules/', # React Native + '/DerivedData/', # Sometimes used for third-party + ] + + for indicator in third_party_indicators: + if indicator in include_path: + return 'third_party' + + # Check if it's within the project directory + if hasattr(self, 'project_path') and self.project_path: + if include_path.startswith(str(self.project_path)): + return 'local' + + # Check for relative paths (usually local) + if include_path.startswith('./') or include_path.startswith('../'): + return 'local' + + # If path contains common local indicators + if any(indicator in include_path.lower() for indicator in ['src/', 'source/', 'include/', 'headers/']): + return 'local' + + # Default to third-party for unknown external dependencies + return 'third_party' + + except Exception as e: + logger.debug(f"Error classifying import {include_path}: {e}") + return 'third_party' + + def _register_framework_dependency(self, framework_name: str, import_type: str, include_path: str) -> None: + """Register framework dependency with moniker manager.""" + try: + if not self.symbol_manager: + return + + # Determine package manager based on import type and path + if import_type == 'standard_library': + manager = 'system' + elif '/Pods/' in include_path: + manager = 'cocoapods' + elif '/Carthage/' in include_path: + manager = 'carthage' + else: + manager = 'unknown' + + # Register the external symbol for the framework + self.symbol_manager.create_external_symbol( + language="objc", + package_name=framework_name, + module_path=framework_name, + symbol_name="*", # Framework-level import + version="", # Version detection could be added later + alias=None + ) + + logger.debug(f"Registered framework dependency: {framework_name} via {manager}") + + except Exception as e: + logger.error(f"Error registering framework dependency {framework_name}: {e}") + + def _map_cursor_to_symbol(self, cursor: 'clang.Cursor', symbol_name: str) -> Optional[Tuple[str, int, int]]: + """Map libclang cursor to SCIP symbol information.""" + try: + cursor_kind = cursor.kind + + # Map Objective-C specific cursors + if cursor_kind == CursorKind.OBJC_INTERFACE_DECL: + # @interface ClassName + symbol_id = f"local {self._get_local_id_for_cursor(cursor)}" + return (symbol_id, scip_pb2.SymbolKind.Class, scip_pb2.SymbolRole.Definition) + + elif cursor_kind == CursorKind.OBJC_PROTOCOL_DECL: + # @protocol ProtocolName + symbol_id = f"local {self._get_local_id_for_cursor(cursor)}" + return (symbol_id, scip_pb2.SymbolKind.Interface, scip_pb2.SymbolRole.Definition) + + elif cursor_kind == CursorKind.OBJC_CATEGORY_DECL: + # @interface ClassName (CategoryName) + symbol_id = f"local {self._get_local_id_for_cursor(cursor)}" + return (symbol_id, scip_pb2.SymbolKind.Class, scip_pb2.SymbolRole.Definition) + + elif cursor_kind == CursorKind.OBJC_INSTANCE_METHOD_DECL: + # Instance method: - (void)methodName + symbol_id = f"local {self._get_local_id_for_cursor(cursor)}" + return (symbol_id, scip_pb2.SymbolKind.Method, scip_pb2.SymbolRole.Definition) + + elif cursor_kind == CursorKind.OBJC_CLASS_METHOD_DECL: + # Class method: + (void)methodName + symbol_id = f"local {self._get_local_id_for_cursor(cursor)}" + return (symbol_id, scip_pb2.SymbolKind.Method, scip_pb2.SymbolRole.Definition) + + elif cursor_kind == CursorKind.OBJC_PROPERTY_DECL: + # @property declaration + symbol_id = f"local {self._get_local_id_for_cursor(cursor)}" + return (symbol_id, scip_pb2.SymbolKind.Property, scip_pb2.SymbolRole.Definition) + + elif cursor_kind == CursorKind.OBJC_IVAR_DECL: + # Instance variable + symbol_id = f"local {self._get_local_id_for_cursor(cursor)}" + return (symbol_id, scip_pb2.SymbolKind.Field, scip_pb2.SymbolRole.Definition) + + elif cursor_kind == CursorKind.OBJC_IMPLEMENTATION_DECL: + # @implementation ClassName + symbol_id = f"local {self._get_local_id_for_cursor(cursor)}" + return (symbol_id, scip_pb2.SymbolKind.Class, scip_pb2.SymbolRole.Definition) + + elif cursor_kind == CursorKind.OBJC_CATEGORY_IMPL_DECL: + # @implementation ClassName (CategoryName) + symbol_id = f"local {self._get_local_id_for_cursor(cursor)}" + return (symbol_id, scip_pb2.SymbolKind.Class, scip_pb2.SymbolRole.Definition) + + elif cursor_kind == CursorKind.FUNCTION_DECL: + # Regular C function + symbol_id = f"local {self._get_local_id_for_cursor(cursor)}" + return (symbol_id, scip_pb2.SymbolKind.Function, scip_pb2.SymbolRole.Definition) + + elif cursor_kind == CursorKind.VAR_DECL: + # Variable declaration + symbol_id = f"local {self._get_local_id_for_cursor(cursor)}" + return (symbol_id, scip_pb2.SymbolKind.Variable, scip_pb2.SymbolRole.Definition) + + elif cursor_kind == CursorKind.TYPEDEF_DECL: + # Type definition + symbol_id = f"local {self._get_local_id_for_cursor(cursor)}" + return (symbol_id, scip_pb2.SymbolKind.TypeParameter, scip_pb2.SymbolRole.Definition) + + # Add more cursor mappings as needed + return None + + except Exception as e: + logger.error(f"Error mapping cursor {symbol_name}: {e}") + return None + + def _get_local_id(self) -> str: + """Generate unique local symbol ID.""" + self._symbol_counter += 1 + return f"objc_{self._symbol_counter}" + + def _get_local_id_for_cursor(self, cursor: 'clang.Cursor') -> str: + """Generate consistent local symbol ID based on cursor properties.""" + # Create deterministic ID based on cursor type, name, and location + cursor_type = cursor.kind.name.lower() + symbol_name = cursor.spelling or "unnamed" + line = cursor.location.line - # Create symbol information - symbol_info = self._create_symbol_information( - symbol_id, name, scip_pb2.Property, ["Objective-C property"] - ) + return f"{cursor_type}_{symbol_name}_{line}" + + def _create_occurrence(self, symbol_id: str, start_line: int, start_col: int, + end_line: int, end_col: int, symbol_roles: int) -> Optional[scip_pb2.Occurrence]: + """Create SCIP occurrence.""" + try: + occurrence = scip_pb2.Occurrence() + occurrence.symbol = symbol_id + occurrence.symbol_roles = symbol_roles + occurrence.range.start.extend([start_line, start_col]) + occurrence.range.end.extend([end_line, end_col]) + + return occurrence + + except Exception as e: + logger.error(f"Error creating occurrence: {e}") + return None + + def _create_symbol_information(self, symbol_id: str, display_name: str, symbol_kind: int) -> Optional[scip_pb2.SymbolInformation]: + """Create SCIP symbol information.""" + try: + symbol_info = scip_pb2.SymbolInformation() + symbol_info.symbol = symbol_id + symbol_info.kind = symbol_kind + symbol_info.display_name = display_name + + return symbol_info + + except Exception as e: + logger.error(f"Error creating symbol information: {e}") + return None + + def _create_symbol_information_with_relationships(self, symbol_id: str, display_name: str, symbol_kind: int, relationships: List['scip_pb2.Relationship']) -> Optional[scip_pb2.SymbolInformation]: + """Create SCIP symbol information with relationships.""" + try: + symbol_info = scip_pb2.SymbolInformation() + symbol_info.symbol = symbol_id + symbol_info.kind = symbol_kind + symbol_info.display_name = display_name + + # Add relationships if provided + if relationships: + symbol_info.relationships.extend(relationships) + + return symbol_info + + except Exception as e: + logger.error(f"Error creating symbol information with relationships: {e}") + return None + + def _extract_relationships_from_file(self, file_path: str, project_path: str) -> Dict[str, List[tuple]]: + """Extract relationships from a single Objective-C file using libclang.""" + content = self._read_file_content(file_path) + if not content: + return {} - return occurrence, symbol_info - - def _create_objc_method_symbol_for_document(self, line_num: int, name: str, is_class_method: bool, file_path: str, scope_stack: List[str]) -> tuple: - """Create an Objective-C method symbol for SCIP document.""" - symbol_id = self.symbol_manager.create_local_symbol( - language="objc", - file_path=file_path, - symbol_path=scope_stack + [name], - descriptor="()." - ) + try: + # Parse with libclang + index = clang.Index.create() + translation_unit = index.parse( + file_path, + args=['-ObjC', '-x', 'objective-c'], + options=clang.TranslationUnit.PARSE_DETAILED_PROCESSING_RECORD + ) + + if not translation_unit: + return {} + + return self._extract_relationships_from_ast(translation_unit.cursor, file_path, project_path) + + except Exception as e: + logger.error(f"Error extracting relationships from {file_path}: {e}") + return {} + + def _extract_relationships_from_ast(self, cursor: 'clang.Cursor', file_path: str, project_path: str) -> Dict[str, List[tuple]]: + """Extract relationships from libclang AST.""" + relationships = {} + relative_path = self._get_relative_path(file_path, project_path) - # Create definition occurrence from line position - start_col, end_col = self.position_calculator.find_name_in_line(line_num, name) - range_obj = self.position_calculator.line_col_to_range( - line_num, start_col, line_num, end_col - ) + # Track current method context for method calls + current_method_symbol = None - occurrence = self._create_occurrence( - symbol_id, range_obj, scip_pb2.Definition, scip_pb2.IdentifierFunction - ) + def traverse_for_relationships(cursor_node, parent_method=None): + """Recursively traverse AST to find relationships.""" + nonlocal current_method_symbol + + try: + # Skip if cursor is not in our file + if not cursor_node.location.file or cursor_node.spelling == "": + pass + else: + cursor_file = str(cursor_node.location.file) + if cursor_file.endswith(os.path.basename(file_path)): + cursor_kind = cursor_node.kind + + # Track method context + if cursor_kind in (CursorKind.OBJC_INSTANCE_METHOD_DECL, CursorKind.OBJC_CLASS_METHOD_DECL): + method_symbol_id = f"local {self._get_local_id_for_cursor(cursor_node)}" + current_method_symbol = method_symbol_id + parent_method = method_symbol_id + + # Detect Objective-C method calls + elif cursor_kind == CursorKind.OBJC_MESSAGE_EXPR: + if parent_method: + # Get the method being called + called_method = self._extract_method_from_message_expr(cursor_node) + if called_method: + target_symbol_id = f"local objc_call_{called_method}_{cursor_node.location.line}" + + if parent_method not in relationships: + relationships[parent_method] = [] + relationships[parent_method].append((target_symbol_id, InternalRelationshipType.CALLS)) + + logger.debug(f"Found method call: {parent_method} -> {target_symbol_id}") + + # Detect C function calls + elif cursor_kind == CursorKind.CALL_EXPR: + if parent_method: + function_name = cursor_node.spelling + if function_name: + target_symbol_id = f"local c_func_{function_name}_{cursor_node.location.line}" + + if parent_method not in relationships: + relationships[parent_method] = [] + relationships[parent_method].append((target_symbol_id, InternalRelationshipType.CALLS)) + + logger.debug(f"Found function call: {parent_method} -> {target_symbol_id}") + + # Recursively process children + for child in cursor_node.get_children(): + traverse_for_relationships(child, parent_method) + + except Exception as e: + logger.error(f"Error processing cursor for relationships: {e}") - # Create symbol information - method_type = "Class method" if is_class_method else "Instance method" - symbol_info = self._create_symbol_information( - symbol_id, name, scip_pb2.Method, [f"Objective-C {method_type.lower()}"] - ) + # Start traversal + traverse_for_relationships(cursor) - return occurrence, symbol_info - - # Utility methods - def _find_child_by_type(self, node, node_type: str) -> Optional: - """Find first child node of the given type.""" - for child in node.children: - if child.type == node_type: - return child - return None - - def _get_node_text(self, node, content: str) -> str: - """Get text content of a node.""" - return content[node.start_byte:node.end_byte] - - def _create_occurrence(self, symbol_id: str, range_obj: scip_pb2.Range, - symbol_roles: int, syntax_kind: int) -> scip_pb2.Occurrence: - """Create a SCIP occurrence.""" - occurrence = scip_pb2.Occurrence() - occurrence.symbol = symbol_id - occurrence.symbol_roles = symbol_roles - occurrence.syntax_kind = syntax_kind - occurrence.range.CopyFrom(range_obj) - return occurrence - - def _create_symbol_information(self, symbol_id: str, display_name: str, - symbol_kind: int, documentation: List[str] = None) -> scip_pb2.SymbolInformation: - """Create SCIP symbol information.""" - symbol_info = scip_pb2.SymbolInformation() - symbol_info.symbol = symbol_id - symbol_info.display_name = display_name - symbol_info.kind = symbol_kind + return relationships + + def _extract_method_from_message_expr(self, cursor: 'clang.Cursor') -> Optional[str]: + """Extract method name from Objective-C message expression.""" + try: + # Get the selector/method name from the message expression + # This is a simplified extraction - could be enhanced + for child in cursor.get_children(): + if child.kind == CursorKind.OBJC_MESSAGE_EXPR: + return child.spelling + elif child.spelling and len(child.spelling) > 0: + # Try to get method name from any meaningful child + return child.spelling + + # Fallback: use the cursor's own spelling if available + return cursor.spelling if cursor.spelling else None + + except Exception as e: + logger.error(f"Error extracting method from message expression: {e}") + return None + + def _create_scip_relationships(self, relationships: List[tuple]) -> List['scip_pb2.Relationship']: + """Convert internal relationships to SCIP relationships.""" + scip_relationships = [] - if documentation: - symbol_info.documentation.extend(documentation) + for target_symbol, relationship_type in relationships: + try: + relationship = scip_pb2.Relationship() + relationship.symbol = target_symbol + + # Map relationship type to SCIP flags + if relationship_type == InternalRelationshipType.CALLS: + relationship.is_reference = True + elif relationship_type == InternalRelationshipType.INHERITS: + relationship.is_reference = True + elif relationship_type == InternalRelationshipType.IMPLEMENTS: + relationship.is_implementation = True + else: + relationship.is_reference = True # Default fallback + + scip_relationships.append(relationship) + + except Exception as e: + logger.error(f"Error creating SCIP relationship: {e}") + continue - return symbol_info \ No newline at end of file + return scip_relationships + + def _get_document_language(self, file_path: str) -> str: + """Get the document language identifier.""" + if file_path.endswith('.mm'): + return 'objcpp' + return 'objc' + + # Utility methods from base strategy + def _read_file_content(self, file_path: str) -> Optional[str]: + """Read file content safely.""" + try: + with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: + return f.read() + except Exception as e: + logger.warning(f"Failed to read file {file_path}: {e}") + return None + + def _get_relative_path(self, file_path: str, project_path: str) -> str: + """Get relative path from project root.""" + return os.path.relpath(file_path, project_path).replace(os.sep, '/') + + def get_supported_languages(self) -> List[str]: + """Return list of supported language identifiers.""" + return ["objective-c", "objc", "objective-c-header"] + + +class StrategyError(Exception): + """Exception raised when a strategy cannot process files.""" + pass \ No newline at end of file diff --git a/src/code_index_mcp/tools/scip/scip_symbol_analyzer.py b/src/code_index_mcp/tools/scip/scip_symbol_analyzer.py index fa070ab..5bd4e31 100644 --- a/src/code_index_mcp/tools/scip/scip_symbol_analyzer.py +++ b/src/code_index_mcp/tools/scip/scip_symbol_analyzer.py @@ -121,7 +121,7 @@ def analyze_file(self, file_path: str, scip_index) -> FileAnalysis: logger.debug("Completed call relationship extraction") # Step 4: Organize results into final structure - result = self._organize_results(document, symbols) + result = self._organize_results(document, symbols, scip_index) logger.debug(f"Analysis complete: {len(result.functions)} functions, {len(result.classes)} classes") return result @@ -558,13 +558,14 @@ def _extract_call_relationships(self, document, symbols: Dict[str, SymbolDefinit logger.debug(f"Relationship extraction completed for {len(symbols)} symbols") - def _organize_results(self, document, symbols: Dict[str, SymbolDefinition]) -> FileAnalysis: + def _organize_results(self, document, symbols: Dict[str, SymbolDefinition], scip_index=None) -> FileAnalysis: """ Organize extracted symbols into final FileAnalysis structure. Args: document: SCIP document symbols: Extracted symbol definitions + scip_index: Full SCIP index for external symbol extraction Returns: FileAnalysis with organized results @@ -581,9 +582,12 @@ def _organize_results(self, document, symbols: Dict[str, SymbolDefinition]) -> F for symbol in symbols.values(): result.add_symbol(symbol) - # Extract import information + # Extract import information from occurrences self._extract_imports(document, result.imports) + # Also extract imports from external symbols (for strategies like Objective-C) + if scip_index: + self._extract_imports_from_external_symbols(scip_index, result.imports) return result @@ -842,6 +846,7 @@ def _extract_imports(self, document, imports: ImportGroup): try: seen_modules = set() + # Method 1: Extract from occurrences with Import role (traditional approach) for occurrence in document.occurrences: # Only process Import role symbols if not self._is_import_occurrence(occurrence): @@ -872,10 +877,94 @@ def _extract_imports(self, document, imports: ImportGroup): imports.add_import(module_path, 'local') seen_modules.add(module_path) - logger.debug(f"Extracted {len(seen_modules)} unique imports from SCIP data") + logger.debug(f"Extracted {len(seen_modules)} unique imports from SCIP occurrences") except Exception as e: - logger.debug(f"Error extracting imports: {e}") + logger.debug(f"Error extracting imports from occurrences: {e}") + + def _extract_imports_from_external_symbols(self, scip_index, imports: ImportGroup): + """Extract imports from SCIP index external symbols (for strategies like Objective-C).""" + try: + if not hasattr(scip_index, 'external_symbols'): + logger.debug("No external_symbols in SCIP index") + return + + seen_modules = set() + + for symbol_info in scip_index.external_symbols: + if not symbol_info.symbol: + continue + + # Parse the external symbol + parsed_symbol = self._symbol_parser.parse_symbol(symbol_info.symbol) if self._symbol_parser else None + if not parsed_symbol: + # Fallback: try to extract framework name from symbol string + framework_name = self._extract_framework_from_symbol_string(symbol_info.symbol) + if framework_name and framework_name not in seen_modules: + # Classify based on symbol pattern + import_type = self._classify_external_symbol(symbol_info.symbol) + imports.add_import(framework_name, import_type) + seen_modules.add(framework_name) + logger.debug(f"Extracted external dependency: {framework_name} ({import_type})") + continue + + # Handle based on manager type + if parsed_symbol.manager in ['system', 'unknown']: + # For Objective-C system frameworks + package_name = parsed_symbol.package + if package_name and package_name not in seen_modules: + imports.add_import(package_name, 'standard_library') + seen_modules.add(package_name) + + elif parsed_symbol.manager in ['cocoapods', 'carthage']: + # Third-party Objective-C dependencies + package_name = parsed_symbol.package + if package_name and package_name not in seen_modules: + imports.add_import(package_name, 'third_party') + seen_modules.add(package_name) + + logger.debug(f"Extracted {len(seen_modules)} unique imports from external symbols") + + except Exception as e: + logger.debug(f"Error extracting imports from external symbols: {e}") + + def _extract_framework_from_symbol_string(self, symbol_string: str) -> Optional[str]: + """Extract framework name from SCIP symbol string.""" + try: + # Handle symbols like "scip-unknown unknown Foundation Foundation *." + parts = symbol_string.split() + if len(parts) >= 4: + # The package name is typically the 3rd or 4th part + for part in parts[2:5]: # Check parts 2, 3, 4 + if part and part != 'unknown' and not part.endswith('.'): + return part + return None + except Exception: + return None + + def _classify_external_symbol(self, symbol_string: str) -> str: + """Classify external symbol as standard_library, third_party, or local.""" + try: + # Check for known system frameworks + system_frameworks = { + 'Foundation', 'UIKit', 'CoreData', 'CoreGraphics', 'QuartzCore', + 'AVFoundation', 'CoreLocation', 'MapKit', 'CoreAnimation', + 'Security', 'SystemConfiguration', 'CFNetwork', 'CoreFoundation', + 'AppKit', 'Cocoa', 'WebKit', 'JavaScriptCore' + } + + for framework in system_frameworks: + if framework in symbol_string: + return 'standard_library' + + # Check for third-party indicators + if any(indicator in symbol_string.lower() for indicator in ['cocoapods', 'carthage', 'pods']): + return 'third_party' + + return 'standard_library' # Default for external symbols + + except Exception: + return 'standard_library' def _parse_external_module(self, external_symbol: str) -> Optional[Dict[str, str]]: """Parse external SCIP symbol to extract module information.""" diff --git a/src/scripts/inspect_doc_symbols.py b/src/scripts/inspect_doc_symbols.py deleted file mode 100644 index 94ca1b2..0000000 --- a/src/scripts/inspect_doc_symbols.py +++ /dev/null @@ -1,68 +0,0 @@ -import os -import sys -import argparse - -CURRENT_DIR = os.path.dirname(__file__) -SRC_DIR = os.path.abspath(os.path.join(CURRENT_DIR, '..')) -if SRC_DIR not in sys.path: - sys.path.insert(0, SRC_DIR) - -from code_index_mcp.scip.proto import scip_pb2 - - -def normalize(p: str) -> str: - return p.replace('\\', '/') - - -def load_index(path: str) -> scip_pb2.Index: - with open(path, 'rb') as f: - data = f.read() - idx = scip_pb2.Index() - idx.ParseFromString(data) - return idx - - -def main(): - ap = argparse.ArgumentParser() - ap.add_argument('--path', required=True, help='Path to index.scip') - ap.add_argument('--file', required=True, help='Relative path to match (forward slashes)') - args = ap.parse_args() - - idx = load_index(args.path) - target = normalize(args.file) - - print(f'Total docs: {len(idx.documents)}') - - doc = None - for d in idx.documents: - if normalize(d.relative_path) == target: - doc = d - break - if doc is None: - # try case-insensitive - tl = target.lower() - for d in idx.documents: - if normalize(d.relative_path).lower() == tl: - doc = d - print('(case-insensitive hit)') - break - - if doc is None: - print('Document not found') - sys.exit(2) - - print(f'Document: {doc.relative_path} language={doc.language}') - print(f'Occurrences: {len(doc.occurrences)}') - print(f'Symbols: {len(doc.symbols)}') - - for i, s in enumerate(doc.symbols[:200]): - try: - kind_name = scip_pb2.SymbolInformation.Kind.Name(s.kind) - except Exception: - kind_name = str(s.kind) - dn = getattr(s, 'display_name', '') - print(f' [{i}] name={dn!r} kind={s.kind} ({kind_name})') - - -if __name__ == '__main__': - main() diff --git a/uv.lock b/uv.lock index f61a86d..a2c9dde 100644 --- a/uv.lock +++ b/uv.lock @@ -49,14 +49,14 @@ wheels = [ [[package]] name = "code-index-mcp" -version = "2.1.0" +version = "2.1.2" source = { editable = "." } dependencies = [ + { name = "libclang" }, { name = "mcp" }, { name = "pathspec" }, { name = "protobuf" }, { name = "tree-sitter" }, - { name = "tree-sitter-c" }, { name = "tree-sitter-java" }, { name = "tree-sitter-javascript" }, { name = "tree-sitter-typescript" }, @@ -66,11 +66,11 @@ dependencies = [ [package.metadata] requires-dist = [ + { name = "libclang", specifier = ">=16.0.0" }, { name = "mcp", specifier = ">=0.3.0" }, { name = "pathspec", specifier = ">=0.12.1" }, { name = "protobuf", specifier = ">=4.21.0" }, { name = "tree-sitter", specifier = ">=0.20.0" }, - { name = "tree-sitter-c", specifier = ">=0.20.0" }, { name = "tree-sitter-java", specifier = ">=0.20.0" }, { name = "tree-sitter-javascript", specifier = ">=0.20.0" }, { name = "tree-sitter-typescript", specifier = ">=0.20.0" }, @@ -151,6 +151,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, ] +[[package]] +name = "libclang" +version = "18.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/5c/ca35e19a4f142adffa27e3d652196b7362fa612243e2b916845d801454fc/libclang-18.1.1.tar.gz", hash = "sha256:a1214966d08d73d971287fc3ead8dfaf82eb07fb197680d8b3859dbbbbf78250", size = 39612 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/49/f5e3e7e1419872b69f6f5e82ba56e33955a74bd537d8a1f5f1eff2f3668a/libclang-18.1.1-1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:0b2e143f0fac830156feb56f9231ff8338c20aecfe72b4ffe96f19e5a1dbb69a", size = 25836045 }, + { url = "https://files.pythonhosted.org/packages/e2/e5/fc61bbded91a8830ccce94c5294ecd6e88e496cc85f6704bf350c0634b70/libclang-18.1.1-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:6f14c3f194704e5d09769108f03185fce7acaf1d1ae4bbb2f30a72c2400cb7c5", size = 26502641 }, + { url = "https://files.pythonhosted.org/packages/db/ed/1df62b44db2583375f6a8a5e2ca5432bbdc3edb477942b9b7c848c720055/libclang-18.1.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:83ce5045d101b669ac38e6da8e58765f12da2d3aafb3b9b98d88b286a60964d8", size = 26420207 }, + { url = "https://files.pythonhosted.org/packages/1d/fc/716c1e62e512ef1c160e7984a73a5fc7df45166f2ff3f254e71c58076f7c/libclang-18.1.1-py2.py3-none-manylinux2010_x86_64.whl", hash = "sha256:c533091d8a3bbf7460a00cb6c1a71da93bffe148f172c7d03b1c31fbf8aa2a0b", size = 24515943 }, + { url = "https://files.pythonhosted.org/packages/3c/3d/f0ac1150280d8d20d059608cf2d5ff61b7c3b7f7bcf9c0f425ab92df769a/libclang-18.1.1-py2.py3-none-manylinux2014_aarch64.whl", hash = "sha256:54dda940a4a0491a9d1532bf071ea3ef26e6dbaf03b5000ed94dd7174e8f9592", size = 23784972 }, + { url = "https://files.pythonhosted.org/packages/fe/2f/d920822c2b1ce9326a4c78c0c2b4aa3fde610c7ee9f631b600acb5376c26/libclang-18.1.1-py2.py3-none-manylinux2014_armv7l.whl", hash = "sha256:cf4a99b05376513717ab5d82a0db832c56ccea4fd61a69dbb7bccf2dfb207dbe", size = 20259606 }, + { url = "https://files.pythonhosted.org/packages/2d/c2/de1db8c6d413597076a4259cea409b83459b2db997c003578affdd32bf66/libclang-18.1.1-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:69f8eb8f65c279e765ffd28aaa7e9e364c776c17618af8bff22a8df58677ff4f", size = 24921494 }, + { url = "https://files.pythonhosted.org/packages/0b/2d/3f480b1e1d31eb3d6de5e3ef641954e5c67430d5ac93b7fa7e07589576c7/libclang-18.1.1-py2.py3-none-win_amd64.whl", hash = "sha256:4dd2d3b82fab35e2bf9ca717d7b63ac990a3519c7e312f19fa8e86dcc712f7fb", size = 26415083 }, + { url = "https://files.pythonhosted.org/packages/71/cf/e01dc4cc79779cd82d77888a88ae2fa424d93b445ad4f6c02bfc18335b70/libclang-18.1.1-py2.py3-none-win_arm64.whl", hash = "sha256:3f0e1f49f04d3cd198985fea0511576b0aee16f9ff0e0f0cad7f9c57ec3c20e8", size = 22361112 }, +] + [[package]] name = "mcp" version = "1.4.1" @@ -381,21 +398,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ce/33/3591e7b22dd49f46ae4fdee1db316ecefd0486cae880c5b497a55f0ccb24/tree_sitter-0.25.1-cp314-cp314-win_arm64.whl", hash = "sha256:f7b68f584336b39b2deab9896b629dddc3c784170733d3409f01fe825e9c04eb", size = 117376 }, ] -[[package]] -name = "tree-sitter-c" -version = "0.24.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/f5/ba8cd08d717277551ade8537d3aa2a94b907c6c6e0fbcf4e4d8b1c747fa3/tree_sitter_c-0.24.1.tar.gz", hash = "sha256:7d2d0cda0b8dda428c81440c1e94367f9f13548eedca3f49768bde66b1422ad6", size = 228014 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/15/c7/c817be36306e457c2d36cc324789046390d9d8c555c38772429ffdb7d361/tree_sitter_c-0.24.1-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9c06ac26a1efdcc8b26a8a6970fbc6997c4071857359e5837d4c42892d45fe1e", size = 80940 }, - { url = "https://files.pythonhosted.org/packages/7a/42/283909467290b24fdbc29bb32ee20e409a19a55002b43175d66d091ca1a4/tree_sitter_c-0.24.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:942bcd7cbecd810dcf7ca6f8f834391ebf0771a89479646d891ba4ca2fdfdc88", size = 86304 }, - { url = "https://files.pythonhosted.org/packages/94/53/fb4f61d4e5f15ec3da85774a4df8e58d3b5b73036cf167f0203b4dd9d158/tree_sitter_c-0.24.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a74cfd7a11ca5a961fafd4d751892ee65acae667d2818968a6f079397d8d28c", size = 109996 }, - { url = "https://files.pythonhosted.org/packages/5e/e8/fc541d34ee81c386c5453c2596c1763e8e9cd7cb0725f39d7dfa2276afa4/tree_sitter_c-0.24.1-cp310-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6a807705a3978911dc7ee26a7ad36dcfacb6adfc13c190d496660ec9bd66707", size = 98137 }, - { url = "https://files.pythonhosted.org/packages/32/c6/d0563319cae0d5b5780a92e2806074b24afea2a07aa4c10599b899bda3ec/tree_sitter_c-0.24.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:789781afcb710df34144f7e2a20cd80e325114b9119e3956c6bd1dd2d365df98", size = 94148 }, - { url = "https://files.pythonhosted.org/packages/50/5a/6361df7f3fa2310c53a0d26b4702a261c332da16fa9d801e381e3a86e25f/tree_sitter_c-0.24.1-cp310-abi3-win_amd64.whl", hash = "sha256:290bff0f9c79c966496ebae45042f77543e6e4aea725f40587a8611d566231a8", size = 84703 }, - { url = "https://files.pythonhosted.org/packages/22/6a/210a302e8025ac492cbaea58d3720d66b7d8034c5d747ac5e4d2d235aa25/tree_sitter_c-0.24.1-cp310-abi3-win_arm64.whl", hash = "sha256:d46bbda06f838c2dcb91daf767813671fd366b49ad84ff37db702129267b46e1", size = 82715 }, -] - [[package]] name = "tree-sitter-java" version = "0.23.5"