jarvis-ai-assistant 0.3.30__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +289 -87
  3. jarvis/jarvis_agent/agent_manager.py +17 -8
  4. jarvis/jarvis_agent/edit_file_handler.py +374 -86
  5. jarvis/jarvis_agent/event_bus.py +1 -1
  6. jarvis/jarvis_agent/file_context_handler.py +79 -0
  7. jarvis/jarvis_agent/jarvis.py +601 -43
  8. jarvis/jarvis_agent/main.py +32 -2
  9. jarvis/jarvis_agent/rewrite_file_handler.py +141 -0
  10. jarvis/jarvis_agent/run_loop.py +38 -5
  11. jarvis/jarvis_agent/share_manager.py +8 -1
  12. jarvis/jarvis_agent/stdio_redirect.py +295 -0
  13. jarvis/jarvis_agent/task_analyzer.py +5 -2
  14. jarvis/jarvis_agent/task_planner.py +496 -0
  15. jarvis/jarvis_agent/utils.py +5 -1
  16. jarvis/jarvis_agent/web_bridge.py +189 -0
  17. jarvis/jarvis_agent/web_output_sink.py +53 -0
  18. jarvis/jarvis_agent/web_server.py +751 -0
  19. jarvis/jarvis_c2rust/__init__.py +26 -0
  20. jarvis/jarvis_c2rust/cli.py +613 -0
  21. jarvis/jarvis_c2rust/collector.py +258 -0
  22. jarvis/jarvis_c2rust/library_replacer.py +1122 -0
  23. jarvis/jarvis_c2rust/llm_module_agent.py +1300 -0
  24. jarvis/jarvis_c2rust/optimizer.py +960 -0
  25. jarvis/jarvis_c2rust/scanner.py +1681 -0
  26. jarvis/jarvis_c2rust/transpiler.py +2325 -0
  27. jarvis/jarvis_code_agent/build_validation_config.py +133 -0
  28. jarvis/jarvis_code_agent/code_agent.py +1171 -94
  29. jarvis/jarvis_code_agent/code_analyzer/__init__.py +62 -0
  30. jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
  31. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
  32. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +102 -0
  33. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +59 -0
  34. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
  35. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +69 -0
  36. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +38 -0
  37. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +44 -0
  38. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +38 -0
  39. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +50 -0
  40. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +93 -0
  41. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +129 -0
  42. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +54 -0
  43. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +154 -0
  44. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
  45. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +363 -0
  46. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
  47. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
  48. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
  49. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
  50. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
  51. jarvis/jarvis_code_agent/code_analyzer/language_support.py +89 -0
  52. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +31 -0
  53. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +231 -0
  54. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +183 -0
  55. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +219 -0
  56. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +209 -0
  57. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +451 -0
  58. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +77 -0
  59. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +48 -0
  60. jarvis/jarvis_code_agent/lint.py +270 -8
  61. jarvis/jarvis_code_agent/utils.py +142 -0
  62. jarvis/jarvis_code_analysis/code_review.py +483 -569
  63. jarvis/jarvis_data/config_schema.json +97 -8
  64. jarvis/jarvis_git_utils/git_commiter.py +38 -26
  65. jarvis/jarvis_mcp/sse_mcp_client.py +2 -2
  66. jarvis/jarvis_mcp/stdio_mcp_client.py +1 -1
  67. jarvis/jarvis_memory_organizer/memory_organizer.py +1 -1
  68. jarvis/jarvis_multi_agent/__init__.py +239 -25
  69. jarvis/jarvis_multi_agent/main.py +37 -1
  70. jarvis/jarvis_platform/base.py +103 -51
  71. jarvis/jarvis_platform/openai.py +26 -1
  72. jarvis/jarvis_platform/yuanbao.py +1 -1
  73. jarvis/jarvis_platform_manager/service.py +2 -2
  74. jarvis/jarvis_rag/cli.py +4 -4
  75. jarvis/jarvis_sec/__init__.py +3605 -0
  76. jarvis/jarvis_sec/checkers/__init__.py +32 -0
  77. jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
  78. jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
  79. jarvis/jarvis_sec/cli.py +116 -0
  80. jarvis/jarvis_sec/report.py +257 -0
  81. jarvis/jarvis_sec/status.py +264 -0
  82. jarvis/jarvis_sec/types.py +20 -0
  83. jarvis/jarvis_sec/workflow.py +219 -0
  84. jarvis/jarvis_stats/cli.py +1 -1
  85. jarvis/jarvis_stats/stats.py +1 -1
  86. jarvis/jarvis_stats/visualizer.py +1 -1
  87. jarvis/jarvis_tools/cli/main.py +1 -0
  88. jarvis/jarvis_tools/execute_script.py +46 -9
  89. jarvis/jarvis_tools/generate_new_tool.py +3 -1
  90. jarvis/jarvis_tools/read_code.py +275 -12
  91. jarvis/jarvis_tools/read_symbols.py +141 -0
  92. jarvis/jarvis_tools/read_webpage.py +5 -3
  93. jarvis/jarvis_tools/registry.py +73 -35
  94. jarvis/jarvis_tools/search_web.py +15 -11
  95. jarvis/jarvis_tools/sub_agent.py +24 -42
  96. jarvis/jarvis_tools/sub_code_agent.py +14 -13
  97. jarvis/jarvis_tools/virtual_tty.py +1 -1
  98. jarvis/jarvis_utils/config.py +187 -35
  99. jarvis/jarvis_utils/embedding.py +3 -0
  100. jarvis/jarvis_utils/git_utils.py +181 -6
  101. jarvis/jarvis_utils/globals.py +3 -3
  102. jarvis/jarvis_utils/http.py +1 -1
  103. jarvis/jarvis_utils/input.py +78 -2
  104. jarvis/jarvis_utils/methodology.py +25 -19
  105. jarvis/jarvis_utils/utils.py +644 -359
  106. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/METADATA +85 -1
  107. jarvis_ai_assistant-0.7.0.dist-info/RECORD +192 -0
  108. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/entry_points.txt +4 -0
  109. jarvis/jarvis_agent/config.py +0 -92
  110. jarvis/jarvis_tools/edit_file.py +0 -179
  111. jarvis/jarvis_tools/rewrite_file.py +0 -191
  112. jarvis_ai_assistant-0.3.30.dist-info/RECORD +0 -137
  113. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/WHEEL +0 -0
  114. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/licenses/LICENSE +0 -0
  115. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,183 @@
1
+ """Go语言支持实现。"""
2
+
3
+ import os
4
+ import re
5
+ from typing import List, Optional, Set
6
+
7
+ from tree_sitter import Language, Node
8
+
9
+ from ..base_language import BaseLanguageSupport
10
+ from ..dependency_analyzer import Dependency, DependencyAnalyzer, DependencyGraph
11
+ from ..file_ignore import filter_walk_dirs
12
+ from ..symbol_extractor import Symbol, SymbolExtractor
13
+ from ..tree_sitter_extractor import TreeSitterExtractor
14
+
15
+
16
+ # --- Go Symbol Query ---
17
+
18
+ GO_SYMBOL_QUERY = """
19
+ (function_declaration
20
+ name: (identifier) @function.name)
21
+
22
+ (method_declaration
23
+ name: (field_identifier) @method.name)
24
+
25
+ (type_declaration
26
+ (type_spec
27
+ name: (type_identifier) @type.name))
28
+
29
+ (interface_declaration
30
+ name: (type_identifier) @interface.name)
31
+ """
32
+
33
+ # --- Go Language Setup ---
34
+
35
+ try:
36
+ import tree_sitter_go
37
+ GO_LANGUAGE: Optional[Language] = tree_sitter_go.language()
38
+ except (ImportError, Exception):
39
+ GO_LANGUAGE = None
40
+
41
+
42
+ # --- Go Symbol Extractor ---
43
+
44
+ class GoSymbolExtractor(TreeSitterExtractor):
45
+ """Extracts symbols from Go code using tree-sitter."""
46
+
47
+ def __init__(self):
48
+ if not GO_LANGUAGE:
49
+ raise RuntimeError("Go tree-sitter grammar not available.")
50
+ super().__init__(GO_LANGUAGE, GO_SYMBOL_QUERY)
51
+
52
+ def _create_symbol_from_capture(self, node: Node, name: str, file_path: str) -> Optional[Symbol]:
53
+ """Maps a tree-sitter capture to a Symbol object."""
54
+ kind_map = {
55
+ "function.name": "function",
56
+ "method.name": "method",
57
+ "type.name": "type",
58
+ "interface.name": "interface",
59
+ }
60
+
61
+ symbol_kind = kind_map.get(name)
62
+ if not symbol_kind:
63
+ return None
64
+
65
+ return Symbol(
66
+ name=node.text.decode('utf8'),
67
+ kind=symbol_kind,
68
+ file_path=file_path,
69
+ line_start=node.start_point[0] + 1,
70
+ line_end=node.end_point[0] + 1,
71
+ )
72
+
73
+
74
+ # --- Go Dependency Analyzer ---
75
+
76
+ class GoDependencyAnalyzer(DependencyAnalyzer):
77
+ """Analyzes Go import dependencies."""
78
+
79
+ def analyze_imports(self, file_path: str, content: str) -> List[Dependency]:
80
+ """Analyzes Go import statements."""
81
+ dependencies: List[Dependency] = []
82
+
83
+ # Match import statements
84
+ # Format: import "package" or import ( "package1" "package2" )
85
+ # Also: import alias "package"
86
+ re.compile(
87
+ r'import\s+(?:\(([^)]+)\)|(?:"([^"]+)"|`([^`]+)`)|(\w+)\s+(?:"([^"]+)"|`([^`]+)`)))',
88
+ re.MULTILINE
89
+ )
90
+
91
+ # Handle single import: import "package"
92
+ single_import = re.compile(r'import\s+(?:"([^"]+)"|`([^`]+)`|(\w+)\s+(?:"([^"]+)"|`([^`]+)`))')
93
+
94
+ # Handle block import: import ( ... )
95
+ block_import = re.compile(r'import\s*\(([^)]+)\)', re.DOTALL)
96
+
97
+ # Try block import first
98
+ block_match = block_import.search(content)
99
+ if block_match:
100
+ block_content = block_match.group(1)
101
+ for line in block_content.split('\n'):
102
+ line = line.strip()
103
+ if not line or line.startswith('//'):
104
+ continue
105
+ # Extract package path
106
+ pkg_match = re.search(r'(?:"([^"]+)"|`([^`]+)`|(\w+)\s+(?:"([^"]+)"|`([^`]+)`))', line)
107
+ if pkg_match:
108
+ pkg = pkg_match.group(1) or pkg_match.group(2) or pkg_match.group(4) or pkg_match.group(5)
109
+ alias = pkg_match.group(3)
110
+ line_num = content[:block_match.start()].count('\n') + line.split('\n')[0].count('\n') + 1
111
+ dependencies.append(Dependency(
112
+ from_module=pkg,
113
+ imported_symbol=alias,
114
+ file_path=file_path,
115
+ line=line_num,
116
+ ))
117
+ else:
118
+ # Try single import
119
+ for match in single_import.finditer(content):
120
+ pkg = match.group(1) or match.group(2) or match.group(4) or match.group(5)
121
+ alias = match.group(3)
122
+ line_num = content[:match.start()].count('\n') + 1
123
+ dependencies.append(Dependency(
124
+ from_module=pkg,
125
+ imported_symbol=alias,
126
+ file_path=file_path,
127
+ line=line_num,
128
+ ))
129
+
130
+ return dependencies
131
+
132
+ def build_dependency_graph(self, project_root: str) -> DependencyGraph:
133
+ """Builds a dependency graph for a Go project."""
134
+ graph = DependencyGraph()
135
+
136
+ for root, dirs, files in os.walk(project_root):
137
+ dirs[:] = filter_walk_dirs(dirs)
138
+
139
+ for file in files:
140
+ if not file.endswith('.go'):
141
+ continue
142
+
143
+ file_path = os.path.join(root, file)
144
+ try:
145
+ with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
146
+ content = f.read()
147
+
148
+ dependencies = self.analyze_imports(file_path, content)
149
+ for dep in dependencies:
150
+ # For Go, we can resolve to vendor or standard library
151
+ # For now, just track the module name
152
+ # In a real implementation, you'd resolve using go.mod
153
+ pass
154
+ except Exception:
155
+ continue
156
+
157
+ return graph
158
+
159
+ def _is_source_file(self, file_path: str) -> bool:
160
+ """Check if a file is a Go source file."""
161
+ return file_path.endswith('.go')
162
+
163
+
164
+ class GoLanguageSupport(BaseLanguageSupport):
165
+ """Go语言支持类。"""
166
+
167
+ @property
168
+ def language_name(self) -> str:
169
+ return 'go'
170
+
171
+ @property
172
+ def file_extensions(self) -> Set[str]:
173
+ return {'.go'}
174
+
175
+ def create_symbol_extractor(self) -> Optional[SymbolExtractor]:
176
+ try:
177
+ return GoSymbolExtractor()
178
+ except RuntimeError:
179
+ return None
180
+
181
+ def create_dependency_analyzer(self) -> Optional[DependencyAnalyzer]:
182
+ return GoDependencyAnalyzer()
183
+
@@ -0,0 +1,219 @@
1
+ """Python语言支持实现。"""
2
+
3
+ import ast
4
+ import os
5
+ from typing import List, Optional, Set, Union
6
+
7
+ from ..base_language import BaseLanguageSupport
8
+ from ..dependency_analyzer import Dependency, DependencyAnalyzer, DependencyGraph
9
+ from ..file_ignore import filter_walk_dirs
10
+ from ..symbol_extractor import Symbol, SymbolExtractor
11
+
12
+
13
+ class PythonSymbolExtractor(SymbolExtractor):
14
+ """Extracts symbols from Python code using the AST module."""
15
+
16
+ def extract_symbols(self, file_path: str, content: str) -> List[Symbol]:
17
+ symbols: List[Symbol] = []
18
+ try:
19
+ tree = ast.parse(content, filename=file_path)
20
+ self._traverse_node(tree, file_path, symbols, parent_name=None)
21
+ except SyntaxError as e:
22
+ print(f"Error parsing Python file {file_path}: {e}")
23
+ return symbols
24
+
25
+ def _traverse_node(self, node: ast.AST, file_path: str, symbols: List[Symbol], parent_name: Optional[str]):
26
+ if isinstance(node, ast.FunctionDef):
27
+ symbol = self._create_symbol_from_func(node, file_path, parent_name)
28
+ symbols.append(symbol)
29
+ parent_name = node.name
30
+ elif isinstance(node, ast.AsyncFunctionDef):
31
+ symbol = self._create_symbol_from_func(node, file_path, parent_name, is_async=True)
32
+ symbols.append(symbol)
33
+ parent_name = node.name
34
+ elif isinstance(node, ast.ClassDef):
35
+ symbol = self._create_symbol_from_class(node, file_path, parent_name)
36
+ symbols.append(symbol)
37
+ parent_name = node.name
38
+ elif isinstance(node, (ast.Import, ast.ImportFrom)):
39
+ symbols.extend(self._create_symbols_from_import(node, file_path, parent_name))
40
+
41
+ for child in ast.iter_child_nodes(node):
42
+ self._traverse_node(child, file_path, symbols, parent_name=parent_name)
43
+
44
+ def _create_symbol_from_func(self, node: Union[ast.FunctionDef, ast.AsyncFunctionDef], file_path: str, parent: Optional[str], is_async: bool = False) -> Symbol:
45
+ signature = f"{'async ' if is_async else ''}def {node.name}(...)"
46
+ return Symbol(
47
+ name=node.name,
48
+ kind='function',
49
+ file_path=file_path,
50
+ line_start=node.lineno,
51
+ line_end=node.end_lineno or node.lineno,
52
+ signature=signature,
53
+ docstring=ast.get_docstring(node),
54
+ parent=parent,
55
+ )
56
+
57
+ def _create_symbol_from_class(self, node: ast.ClassDef, file_path: str, parent: Optional[str]) -> Symbol:
58
+ return Symbol(
59
+ name=node.name,
60
+ kind='class',
61
+ file_path=file_path,
62
+ line_start=node.lineno,
63
+ line_end=node.end_lineno or node.lineno,
64
+ docstring=ast.get_docstring(node),
65
+ parent=parent,
66
+ )
67
+
68
+ def _create_symbols_from_import(self, node: Union[ast.Import, ast.ImportFrom], file_path: str, parent: Optional[str]) -> List[Symbol]:
69
+ symbols = []
70
+ for alias in node.names:
71
+ symbols.append(Symbol(
72
+ name=alias.asname or alias.name,
73
+ kind='import',
74
+ file_path=file_path,
75
+ line_start=node.lineno,
76
+ line_end=node.end_lineno or node.lineno,
77
+ parent=parent,
78
+ ))
79
+ return symbols
80
+
81
+
82
+ class PythonDependencyAnalyzer(DependencyAnalyzer):
83
+ """Analyzes Python import dependencies using AST."""
84
+
85
+ def analyze_imports(self, file_path: str, content: str) -> List[Dependency]:
86
+ """Analyzes Python import statements."""
87
+ dependencies: List[Dependency] = []
88
+
89
+ try:
90
+ tree = ast.parse(content, filename=file_path)
91
+ for node in ast.walk(tree):
92
+ if isinstance(node, ast.Import):
93
+ # import module
94
+ for alias in node.names:
95
+ dependencies.append(Dependency(
96
+ from_module=alias.name,
97
+ imported_symbol=None,
98
+ file_path=file_path,
99
+ line=node.lineno,
100
+ ))
101
+ elif isinstance(node, ast.ImportFrom):
102
+ # from module import symbol
103
+ module = node.module or ""
104
+ for alias in node.names:
105
+ dependencies.append(Dependency(
106
+ from_module=module,
107
+ imported_symbol=alias.name,
108
+ file_path=file_path,
109
+ line=node.lineno,
110
+ ))
111
+ except SyntaxError:
112
+ # Skip files with syntax errors
113
+ pass
114
+
115
+ return dependencies
116
+
117
+ def build_dependency_graph(self, project_root: str) -> DependencyGraph:
118
+ """Builds a dependency graph for a Python project."""
119
+ graph = DependencyGraph()
120
+
121
+ # Walk through all Python files
122
+ for root, dirs, files in os.walk(project_root):
123
+ # Skip hidden directories and common ignore patterns
124
+ dirs[:] = filter_walk_dirs(dirs)
125
+
126
+ for file in files:
127
+ if not file.endswith('.py'):
128
+ continue
129
+
130
+ file_path = os.path.join(root, file)
131
+ try:
132
+ with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
133
+ content = f.read()
134
+
135
+ dependencies = self.analyze_imports(file_path, content)
136
+ for dep in dependencies:
137
+ # Resolve module to file path
138
+ dep_path = self._resolve_module_path(project_root, dep.from_module, file_path)
139
+ if dep_path and dep_path != file_path:
140
+ graph.add_dependency(file_path, dep_path)
141
+ except Exception:
142
+ continue
143
+
144
+ return graph
145
+
146
+ def _resolve_module_path(self, project_root: str, module_name: str, from_file: str) -> Optional[str]:
147
+ """Resolve a Python module name to a file path."""
148
+ if not module_name:
149
+ return None
150
+
151
+ # Handle relative imports
152
+ if module_name.startswith('.'):
153
+ # Relative import - resolve from the importing file's directory
154
+ base_dir = os.path.dirname(from_file)
155
+ parts = module_name.split('.')
156
+ depth = len([p for p in parts if p == ''])
157
+ module_parts = [p for p in parts if p]
158
+
159
+ # Navigate up directories
160
+ current_dir = base_dir
161
+ for _ in range(depth - 1):
162
+ current_dir = os.path.dirname(current_dir)
163
+
164
+ if module_parts:
165
+ module_path = os.path.join(current_dir, *module_parts)
166
+ else:
167
+ module_path = current_dir
168
+
169
+ # Try to find the module file
170
+ if os.path.isdir(module_path):
171
+ init_path = os.path.join(module_path, '__init__.py')
172
+ if os.path.exists(init_path):
173
+ return init_path
174
+ elif os.path.exists(module_path + '.py'):
175
+ return module_path + '.py'
176
+ else:
177
+ # Absolute import
178
+ parts = module_name.split('.')
179
+
180
+ # Search in project root
181
+ for root, dirs, files in os.walk(project_root):
182
+ # Skip hidden directories and common ignore patterns
183
+ dirs[:] = filter_walk_dirs(dirs)
184
+
185
+ if parts[0] in dirs or f"{parts[0]}.py" in files:
186
+ module_path = os.path.join(root, *parts)
187
+
188
+ if os.path.isdir(module_path):
189
+ init_path = os.path.join(module_path, '__init__.py')
190
+ if os.path.exists(init_path):
191
+ return init_path
192
+ elif os.path.exists(module_path + '.py'):
193
+ return module_path + '.py'
194
+ break
195
+
196
+ return None
197
+
198
+ def _is_source_file(self, file_path: str) -> bool:
199
+ """Check if a file is a Python source file."""
200
+ return file_path.endswith('.py')
201
+
202
+
203
+ class PythonLanguageSupport(BaseLanguageSupport):
204
+ """Python语言支持类。"""
205
+
206
+ @property
207
+ def language_name(self) -> str:
208
+ return 'python'
209
+
210
+ @property
211
+ def file_extensions(self) -> Set[str]:
212
+ return {'.py', '.pyw', '.pyi'}
213
+
214
+ def create_symbol_extractor(self) -> Optional[SymbolExtractor]:
215
+ return PythonSymbolExtractor()
216
+
217
+ def create_dependency_analyzer(self) -> Optional[DependencyAnalyzer]:
218
+ return PythonDependencyAnalyzer()
219
+
@@ -0,0 +1,209 @@
1
+ """Rust语言支持实现。"""
2
+
3
+ import os
4
+ import re
5
+ from typing import List, Optional, Set
6
+
7
+ from tree_sitter import Language, Node
8
+
9
+ from ..base_language import BaseLanguageSupport
10
+ from ..dependency_analyzer import Dependency, DependencyAnalyzer, DependencyGraph
11
+ from ..file_ignore import filter_walk_dirs
12
+ from ..symbol_extractor import Symbol, SymbolExtractor
13
+ from ..tree_sitter_extractor import TreeSitterExtractor
14
+
15
+
16
+ # --- Rust Symbol Query ---
17
+
18
+ RUST_SYMBOL_QUERY = """
19
+ (function_item
20
+ name: (identifier) @function.name)
21
+
22
+ (struct_item
23
+ name: (type_identifier) @struct.name)
24
+
25
+ (trait_item
26
+ name: (type_identifier) @trait.name)
27
+
28
+ (impl_item
29
+ type: (type_identifier) @impl.name)
30
+
31
+ (mod_item
32
+ name: (identifier) @module.name)
33
+ """
34
+
35
+ # --- Rust Language Setup ---
36
+
37
+ try:
38
+ import tree_sitter_rust
39
+ RUST_LANGUAGE: Optional[Language] = tree_sitter_rust.language()
40
+ except (ImportError, Exception):
41
+ RUST_LANGUAGE = None
42
+
43
+
44
+ # --- Rust Symbol Extractor ---
45
+
46
+ class RustSymbolExtractor(TreeSitterExtractor):
47
+ """Extracts symbols from Rust code using tree-sitter."""
48
+
49
+ def __init__(self):
50
+ if not RUST_LANGUAGE:
51
+ raise RuntimeError("Rust tree-sitter grammar not available.")
52
+ super().__init__(RUST_LANGUAGE, RUST_SYMBOL_QUERY)
53
+
54
+ def _create_symbol_from_capture(self, node: Node, name: str, file_path: str) -> Optional[Symbol]:
55
+ """Maps a tree-sitter capture to a Symbol object."""
56
+ kind_map = {
57
+ "function.name": "function",
58
+ "struct.name": "struct",
59
+ "trait.name": "trait",
60
+ "impl.name": "impl",
61
+ "module.name": "module",
62
+ }
63
+
64
+ symbol_kind = kind_map.get(name)
65
+ if not symbol_kind:
66
+ return None
67
+
68
+ return Symbol(
69
+ name=node.text.decode('utf8'),
70
+ kind=symbol_kind,
71
+ file_path=file_path,
72
+ line_start=node.start_point[0] + 1,
73
+ line_end=node.end_point[0] + 1,
74
+ )
75
+
76
+
77
+ # --- Rust Dependency Analyzer ---
78
+
79
+ class RustDependencyAnalyzer(DependencyAnalyzer):
80
+ """Analyzes Rust use and mod dependencies."""
81
+
82
+ def analyze_imports(self, file_path: str, content: str) -> List[Dependency]:
83
+ """Analyzes Rust use and mod statements."""
84
+ dependencies: List[Dependency] = []
85
+
86
+ # Match use statements: use crate::module or use std::collections
87
+ use_pattern = re.compile(r'use\s+([^;]+);')
88
+
89
+ # Match mod declarations: mod module_name;
90
+ mod_pattern = re.compile(r'mod\s+(\w+)\s*;')
91
+
92
+ for line_num, line in enumerate(content.split('\n'), start=1):
93
+ # Check for use statements
94
+ use_match = use_pattern.search(line)
95
+ if use_match:
96
+ use_path = use_match.group(1).strip()
97
+ # Extract the crate/module name
98
+ parts = use_path.split('::')
99
+ if parts:
100
+ crate_name = parts[0]
101
+ symbol = '::'.join(parts[1:]) if len(parts) > 1 else None
102
+ dependencies.append(Dependency(
103
+ from_module=crate_name,
104
+ imported_symbol=symbol,
105
+ file_path=file_path,
106
+ line=line_num,
107
+ ))
108
+
109
+ # Check for mod declarations
110
+ mod_match = mod_pattern.search(line)
111
+ if mod_match:
112
+ mod_name = mod_match.group(1)
113
+ dependencies.append(Dependency(
114
+ from_module=mod_name,
115
+ imported_symbol=None,
116
+ file_path=file_path,
117
+ line=line_num,
118
+ ))
119
+
120
+ return dependencies
121
+
122
+ def build_dependency_graph(self, project_root: str) -> DependencyGraph:
123
+ """Builds a dependency graph for a Rust project."""
124
+ graph = DependencyGraph()
125
+
126
+ for root, dirs, files in os.walk(project_root):
127
+ dirs[:] = filter_walk_dirs(dirs)
128
+
129
+ for file in files:
130
+ if not file.endswith('.rs'):
131
+ continue
132
+
133
+ file_path = os.path.join(root, file)
134
+ try:
135
+ with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
136
+ content = f.read()
137
+
138
+ dependencies = self.analyze_imports(file_path, content)
139
+ for dep in dependencies:
140
+ # For Rust, resolve mod declarations to file paths
141
+ if not dep.from_module.startswith(('std', 'core', 'alloc', 'proc_macro')):
142
+ # Try to resolve local modules
143
+ dep_path = self._resolve_module_path(project_root, dep.from_module, file_path)
144
+ if dep_path and dep_path != file_path:
145
+ graph.add_dependency(file_path, dep_path)
146
+ except Exception:
147
+ continue
148
+
149
+ return graph
150
+
151
+ def _resolve_module_path(self, project_root: str, module_name: str, from_file: str) -> Optional[str]:
152
+ """Resolve a Rust module name to a file path."""
153
+ # Rust modules can be:
154
+ # 1. mod.rs in a directory
155
+ # 2. module_name.rs in the same directory
156
+ # 3. module_name/mod.rs
157
+
158
+ base_dir = os.path.dirname(from_file)
159
+
160
+ # Try module_name.rs in same directory
161
+ module_file = os.path.join(base_dir, f"{module_name}.rs")
162
+ if os.path.exists(module_file):
163
+ return module_file
164
+
165
+ # Try module_name/mod.rs
166
+ module_dir = os.path.join(base_dir, module_name)
167
+ mod_rs = os.path.join(module_dir, "mod.rs")
168
+ if os.path.exists(mod_rs):
169
+ return mod_rs
170
+
171
+ # Try in parent directories (for nested modules)
172
+ current_dir = base_dir
173
+ while current_dir != project_root and current_dir != os.path.dirname(current_dir):
174
+ module_file = os.path.join(current_dir, f"{module_name}.rs")
175
+ if os.path.exists(module_file):
176
+ return module_file
177
+ module_dir = os.path.join(current_dir, module_name)
178
+ mod_rs = os.path.join(module_dir, "mod.rs")
179
+ if os.path.exists(mod_rs):
180
+ return mod_rs
181
+ current_dir = os.path.dirname(current_dir)
182
+
183
+ return None
184
+
185
+ def _is_source_file(self, file_path: str) -> bool:
186
+ """Check if a file is a Rust source file."""
187
+ return file_path.endswith('.rs')
188
+
189
+
190
+ class RustLanguageSupport(BaseLanguageSupport):
191
+ """Rust语言支持类。"""
192
+
193
+ @property
194
+ def language_name(self) -> str:
195
+ return 'rust'
196
+
197
+ @property
198
+ def file_extensions(self) -> Set[str]:
199
+ return {'.rs'}
200
+
201
+ def create_symbol_extractor(self) -> Optional[SymbolExtractor]:
202
+ try:
203
+ return RustSymbolExtractor()
204
+ except RuntimeError:
205
+ return None
206
+
207
+ def create_dependency_analyzer(self) -> Optional[DependencyAnalyzer]:
208
+ return RustDependencyAnalyzer()
209
+