jarvis-ai-assistant 0.3.30__py3-none-any.whl → 0.7.6__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 (181) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +458 -152
  3. jarvis/jarvis_agent/agent_manager.py +17 -13
  4. jarvis/jarvis_agent/builtin_input_handler.py +2 -6
  5. jarvis/jarvis_agent/config_editor.py +2 -7
  6. jarvis/jarvis_agent/event_bus.py +82 -12
  7. jarvis/jarvis_agent/file_context_handler.py +329 -0
  8. jarvis/jarvis_agent/file_methodology_manager.py +3 -4
  9. jarvis/jarvis_agent/jarvis.py +628 -55
  10. jarvis/jarvis_agent/language_extractors/__init__.py +57 -0
  11. jarvis/jarvis_agent/language_extractors/c_extractor.py +21 -0
  12. jarvis/jarvis_agent/language_extractors/cpp_extractor.py +21 -0
  13. jarvis/jarvis_agent/language_extractors/go_extractor.py +21 -0
  14. jarvis/jarvis_agent/language_extractors/java_extractor.py +84 -0
  15. jarvis/jarvis_agent/language_extractors/javascript_extractor.py +79 -0
  16. jarvis/jarvis_agent/language_extractors/python_extractor.py +21 -0
  17. jarvis/jarvis_agent/language_extractors/rust_extractor.py +21 -0
  18. jarvis/jarvis_agent/language_extractors/typescript_extractor.py +84 -0
  19. jarvis/jarvis_agent/language_support_info.py +486 -0
  20. jarvis/jarvis_agent/main.py +34 -10
  21. jarvis/jarvis_agent/memory_manager.py +7 -16
  22. jarvis/jarvis_agent/methodology_share_manager.py +10 -16
  23. jarvis/jarvis_agent/prompt_manager.py +1 -1
  24. jarvis/jarvis_agent/prompts.py +193 -171
  25. jarvis/jarvis_agent/protocols.py +8 -12
  26. jarvis/jarvis_agent/run_loop.py +105 -9
  27. jarvis/jarvis_agent/session_manager.py +2 -3
  28. jarvis/jarvis_agent/share_manager.py +20 -22
  29. jarvis/jarvis_agent/shell_input_handler.py +1 -2
  30. jarvis/jarvis_agent/stdio_redirect.py +295 -0
  31. jarvis/jarvis_agent/task_analyzer.py +31 -6
  32. jarvis/jarvis_agent/task_manager.py +11 -27
  33. jarvis/jarvis_agent/tool_executor.py +2 -3
  34. jarvis/jarvis_agent/tool_share_manager.py +12 -24
  35. jarvis/jarvis_agent/utils.py +5 -1
  36. jarvis/jarvis_agent/web_bridge.py +189 -0
  37. jarvis/jarvis_agent/web_output_sink.py +53 -0
  38. jarvis/jarvis_agent/web_server.py +786 -0
  39. jarvis/jarvis_c2rust/__init__.py +26 -0
  40. jarvis/jarvis_c2rust/cli.py +575 -0
  41. jarvis/jarvis_c2rust/collector.py +250 -0
  42. jarvis/jarvis_c2rust/constants.py +26 -0
  43. jarvis/jarvis_c2rust/library_replacer.py +1254 -0
  44. jarvis/jarvis_c2rust/llm_module_agent.py +1272 -0
  45. jarvis/jarvis_c2rust/loaders.py +207 -0
  46. jarvis/jarvis_c2rust/models.py +28 -0
  47. jarvis/jarvis_c2rust/optimizer.py +2157 -0
  48. jarvis/jarvis_c2rust/scanner.py +1681 -0
  49. jarvis/jarvis_c2rust/transpiler.py +2983 -0
  50. jarvis/jarvis_c2rust/utils.py +385 -0
  51. jarvis/jarvis_code_agent/build_validation_config.py +132 -0
  52. jarvis/jarvis_code_agent/code_agent.py +1371 -220
  53. jarvis/jarvis_code_agent/code_analyzer/__init__.py +65 -0
  54. jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
  55. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
  56. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +106 -0
  57. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +74 -0
  58. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
  59. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +72 -0
  60. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +70 -0
  61. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +53 -0
  62. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +47 -0
  63. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +61 -0
  64. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +110 -0
  65. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +154 -0
  66. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +110 -0
  67. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +153 -0
  68. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
  69. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +648 -0
  70. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
  71. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
  72. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
  73. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
  74. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
  75. jarvis/jarvis_code_agent/code_analyzer/language_support.py +110 -0
  76. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +49 -0
  77. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +299 -0
  78. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +215 -0
  79. jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +212 -0
  80. jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +254 -0
  81. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +269 -0
  82. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +281 -0
  83. jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +280 -0
  84. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +605 -0
  85. jarvis/jarvis_code_agent/code_analyzer/structured_code.py +556 -0
  86. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +252 -0
  87. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +58 -0
  88. jarvis/jarvis_code_agent/lint.py +501 -8
  89. jarvis/jarvis_code_agent/utils.py +141 -0
  90. jarvis/jarvis_code_analysis/code_review.py +493 -584
  91. jarvis/jarvis_data/config_schema.json +128 -12
  92. jarvis/jarvis_git_squash/main.py +4 -5
  93. jarvis/jarvis_git_utils/git_commiter.py +82 -75
  94. jarvis/jarvis_mcp/sse_mcp_client.py +22 -29
  95. jarvis/jarvis_mcp/stdio_mcp_client.py +12 -13
  96. jarvis/jarvis_mcp/streamable_mcp_client.py +15 -14
  97. jarvis/jarvis_memory_organizer/memory_organizer.py +55 -74
  98. jarvis/jarvis_methodology/main.py +32 -48
  99. jarvis/jarvis_multi_agent/__init__.py +287 -55
  100. jarvis/jarvis_multi_agent/main.py +36 -4
  101. jarvis/jarvis_platform/base.py +524 -202
  102. jarvis/jarvis_platform/human.py +7 -8
  103. jarvis/jarvis_platform/kimi.py +30 -36
  104. jarvis/jarvis_platform/openai.py +88 -25
  105. jarvis/jarvis_platform/registry.py +26 -10
  106. jarvis/jarvis_platform/tongyi.py +24 -25
  107. jarvis/jarvis_platform/yuanbao.py +32 -43
  108. jarvis/jarvis_platform_manager/main.py +66 -77
  109. jarvis/jarvis_platform_manager/service.py +8 -13
  110. jarvis/jarvis_rag/cli.py +53 -55
  111. jarvis/jarvis_rag/embedding_manager.py +13 -18
  112. jarvis/jarvis_rag/llm_interface.py +8 -9
  113. jarvis/jarvis_rag/query_rewriter.py +10 -21
  114. jarvis/jarvis_rag/rag_pipeline.py +24 -27
  115. jarvis/jarvis_rag/reranker.py +4 -5
  116. jarvis/jarvis_rag/retriever.py +28 -30
  117. jarvis/jarvis_sec/__init__.py +305 -0
  118. jarvis/jarvis_sec/agents.py +143 -0
  119. jarvis/jarvis_sec/analysis.py +276 -0
  120. jarvis/jarvis_sec/checkers/__init__.py +32 -0
  121. jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
  122. jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
  123. jarvis/jarvis_sec/cli.py +139 -0
  124. jarvis/jarvis_sec/clustering.py +1439 -0
  125. jarvis/jarvis_sec/file_manager.py +427 -0
  126. jarvis/jarvis_sec/parsers.py +73 -0
  127. jarvis/jarvis_sec/prompts.py +268 -0
  128. jarvis/jarvis_sec/report.py +336 -0
  129. jarvis/jarvis_sec/review.py +453 -0
  130. jarvis/jarvis_sec/status.py +264 -0
  131. jarvis/jarvis_sec/types.py +20 -0
  132. jarvis/jarvis_sec/utils.py +499 -0
  133. jarvis/jarvis_sec/verification.py +848 -0
  134. jarvis/jarvis_sec/workflow.py +226 -0
  135. jarvis/jarvis_smart_shell/main.py +38 -87
  136. jarvis/jarvis_stats/cli.py +2 -2
  137. jarvis/jarvis_stats/stats.py +8 -8
  138. jarvis/jarvis_stats/storage.py +15 -21
  139. jarvis/jarvis_stats/visualizer.py +1 -1
  140. jarvis/jarvis_tools/clear_memory.py +3 -20
  141. jarvis/jarvis_tools/cli/main.py +21 -23
  142. jarvis/jarvis_tools/edit_file.py +1019 -132
  143. jarvis/jarvis_tools/execute_script.py +83 -25
  144. jarvis/jarvis_tools/file_analyzer.py +6 -9
  145. jarvis/jarvis_tools/generate_new_tool.py +14 -21
  146. jarvis/jarvis_tools/lsp_client.py +1552 -0
  147. jarvis/jarvis_tools/methodology.py +2 -3
  148. jarvis/jarvis_tools/read_code.py +1736 -35
  149. jarvis/jarvis_tools/read_symbols.py +140 -0
  150. jarvis/jarvis_tools/read_webpage.py +12 -13
  151. jarvis/jarvis_tools/registry.py +427 -200
  152. jarvis/jarvis_tools/retrieve_memory.py +20 -19
  153. jarvis/jarvis_tools/rewrite_file.py +72 -158
  154. jarvis/jarvis_tools/save_memory.py +3 -15
  155. jarvis/jarvis_tools/search_web.py +18 -18
  156. jarvis/jarvis_tools/sub_agent.py +36 -43
  157. jarvis/jarvis_tools/sub_code_agent.py +25 -26
  158. jarvis/jarvis_tools/virtual_tty.py +55 -33
  159. jarvis/jarvis_utils/clipboard.py +7 -10
  160. jarvis/jarvis_utils/config.py +232 -45
  161. jarvis/jarvis_utils/embedding.py +8 -5
  162. jarvis/jarvis_utils/fzf.py +8 -8
  163. jarvis/jarvis_utils/git_utils.py +225 -36
  164. jarvis/jarvis_utils/globals.py +3 -3
  165. jarvis/jarvis_utils/http.py +1 -1
  166. jarvis/jarvis_utils/input.py +99 -48
  167. jarvis/jarvis_utils/jsonnet_compat.py +465 -0
  168. jarvis/jarvis_utils/methodology.py +52 -48
  169. jarvis/jarvis_utils/utils.py +819 -491
  170. jarvis_ai_assistant-0.7.6.dist-info/METADATA +600 -0
  171. jarvis_ai_assistant-0.7.6.dist-info/RECORD +218 -0
  172. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/entry_points.txt +4 -0
  173. jarvis/jarvis_agent/config.py +0 -92
  174. jarvis/jarvis_agent/edit_file_handler.py +0 -296
  175. jarvis/jarvis_platform/ai8.py +0 -332
  176. jarvis/jarvis_tools/ask_user.py +0 -54
  177. jarvis_ai_assistant-0.3.30.dist-info/METADATA +0 -381
  178. jarvis_ai_assistant-0.3.30.dist-info/RECORD +0 -137
  179. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/WHEEL +0 -0
  180. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/licenses/LICENSE +0 -0
  181. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,269 @@
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
+ # 对于超大文件,限制解析内容长度(避免内存和性能问题)
20
+ # 只解析前 50000 行,通常足够提取主要符号
21
+ max_lines = 50000
22
+ lines = content.split('\n')
23
+ if len(lines) > max_lines:
24
+ content = '\n'.join(lines[:max_lines])
25
+
26
+ tree = ast.parse(content, filename=file_path)
27
+ self._traverse_node(tree, file_path, symbols, parent_name=None)
28
+ except SyntaxError:
29
+ # 静默跳过语法错误文件(可能是部分代码片段或测试文件)
30
+ pass
31
+ except Exception:
32
+ # 静默跳过其他解析错误(如内存不足等)
33
+ pass
34
+ return symbols
35
+
36
+ def _traverse_node(self, node: ast.AST, file_path: str, symbols: List[Symbol], parent_name: Optional[str]):
37
+ if isinstance(node, ast.FunctionDef):
38
+ # Extract decorators before the function
39
+ if node.decorator_list:
40
+ for decorator in node.decorator_list:
41
+ if isinstance(decorator, ast.Name):
42
+ decorator_symbol = Symbol(
43
+ name=decorator.id,
44
+ kind='decorator',
45
+ file_path=file_path,
46
+ line_start=decorator.lineno,
47
+ line_end=decorator.lineno,
48
+ parent=parent_name,
49
+ )
50
+ symbols.append(decorator_symbol)
51
+ symbol = self._create_symbol_from_func(node, file_path, parent_name)
52
+ symbols.append(symbol)
53
+ parent_name = node.name
54
+ elif isinstance(node, ast.AsyncFunctionDef):
55
+ # Extract decorators before the async function
56
+ if node.decorator_list:
57
+ for decorator in node.decorator_list:
58
+ if isinstance(decorator, ast.Name):
59
+ decorator_symbol = Symbol(
60
+ name=decorator.id,
61
+ kind='decorator',
62
+ file_path=file_path,
63
+ line_start=decorator.lineno,
64
+ line_end=decorator.lineno,
65
+ parent=parent_name,
66
+ )
67
+ symbols.append(decorator_symbol)
68
+ symbol = self._create_symbol_from_func(node, file_path, parent_name, is_async=True)
69
+ symbols.append(symbol)
70
+ parent_name = node.name
71
+ elif isinstance(node, ast.ClassDef):
72
+ # Extract decorators before the class
73
+ if node.decorator_list:
74
+ for decorator in node.decorator_list:
75
+ if isinstance(decorator, ast.Name):
76
+ decorator_symbol = Symbol(
77
+ name=decorator.id,
78
+ kind='decorator',
79
+ file_path=file_path,
80
+ line_start=decorator.lineno,
81
+ line_end=decorator.lineno,
82
+ parent=parent_name,
83
+ )
84
+ symbols.append(decorator_symbol)
85
+ symbol = self._create_symbol_from_class(node, file_path, parent_name)
86
+ symbols.append(symbol)
87
+ parent_name = node.name
88
+ elif isinstance(node, (ast.Import, ast.ImportFrom)):
89
+ symbols.extend(self._create_symbols_from_import(node, file_path, parent_name))
90
+
91
+ for child in ast.iter_child_nodes(node):
92
+ self._traverse_node(child, file_path, symbols, parent_name=parent_name)
93
+
94
+ def _create_symbol_from_func(self, node: Union[ast.FunctionDef, ast.AsyncFunctionDef], file_path: str, parent: Optional[str], is_async: bool = False) -> Symbol:
95
+ signature = f"{'async ' if is_async else ''}def {node.name}(...)"
96
+ return Symbol(
97
+ name=node.name,
98
+ kind='function',
99
+ file_path=file_path,
100
+ line_start=node.lineno,
101
+ line_end=node.end_lineno or node.lineno,
102
+ signature=signature,
103
+ docstring=ast.get_docstring(node),
104
+ parent=parent,
105
+ )
106
+
107
+ def _create_symbol_from_class(self, node: ast.ClassDef, file_path: str, parent: Optional[str]) -> Symbol:
108
+ return Symbol(
109
+ name=node.name,
110
+ kind='class',
111
+ file_path=file_path,
112
+ line_start=node.lineno,
113
+ line_end=node.end_lineno or node.lineno,
114
+ docstring=ast.get_docstring(node),
115
+ parent=parent,
116
+ )
117
+
118
+ def _create_symbols_from_import(self, node: Union[ast.Import, ast.ImportFrom], file_path: str, parent: Optional[str]) -> List[Symbol]:
119
+ symbols = []
120
+ for alias in node.names:
121
+ symbols.append(Symbol(
122
+ name=alias.asname or alias.name,
123
+ kind='import',
124
+ file_path=file_path,
125
+ line_start=node.lineno,
126
+ line_end=node.end_lineno or node.lineno,
127
+ parent=parent,
128
+ ))
129
+ return symbols
130
+
131
+
132
+ class PythonDependencyAnalyzer(DependencyAnalyzer):
133
+ """Analyzes Python import dependencies using AST."""
134
+
135
+ def analyze_imports(self, file_path: str, content: str) -> List[Dependency]:
136
+ """Analyzes Python import statements."""
137
+ dependencies: List[Dependency] = []
138
+
139
+ try:
140
+ tree = ast.parse(content, filename=file_path)
141
+ for node in ast.walk(tree):
142
+ if isinstance(node, ast.Import):
143
+ # import module
144
+ for alias in node.names:
145
+ dependencies.append(Dependency(
146
+ from_module=alias.name,
147
+ imported_symbol=None,
148
+ file_path=file_path,
149
+ line=node.lineno,
150
+ ))
151
+ elif isinstance(node, ast.ImportFrom):
152
+ # from module import symbol
153
+ module = node.module or ""
154
+ for alias in node.names:
155
+ dependencies.append(Dependency(
156
+ from_module=module,
157
+ imported_symbol=alias.name,
158
+ file_path=file_path,
159
+ line=node.lineno,
160
+ ))
161
+ except SyntaxError:
162
+ # Skip files with syntax errors
163
+ pass
164
+
165
+ return dependencies
166
+
167
+ def build_dependency_graph(self, project_root: str) -> DependencyGraph:
168
+ """Builds a dependency graph for a Python project."""
169
+ graph = DependencyGraph()
170
+
171
+ # Walk through all Python files
172
+ for root, dirs, files in os.walk(project_root):
173
+ # Skip hidden directories and common ignore patterns
174
+ dirs[:] = filter_walk_dirs(dirs)
175
+
176
+ for file in files:
177
+ if not file.endswith('.py'):
178
+ continue
179
+
180
+ file_path = os.path.join(root, file)
181
+ try:
182
+ with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
183
+ content = f.read()
184
+
185
+ dependencies = self.analyze_imports(file_path, content)
186
+ for dep in dependencies:
187
+ # Resolve module to file path
188
+ dep_path = self._resolve_module_path(project_root, dep.from_module, file_path)
189
+ if dep_path and dep_path != file_path:
190
+ graph.add_dependency(file_path, dep_path)
191
+ except Exception:
192
+ continue
193
+
194
+ return graph
195
+
196
+ def _resolve_module_path(self, project_root: str, module_name: str, from_file: str) -> Optional[str]:
197
+ """Resolve a Python module name to a file path."""
198
+ if not module_name:
199
+ return None
200
+
201
+ # Handle relative imports
202
+ if module_name.startswith('.'):
203
+ # Relative import - resolve from the importing file's directory
204
+ base_dir = os.path.dirname(from_file)
205
+ parts = module_name.split('.')
206
+ depth = len([p for p in parts if p == ''])
207
+ module_parts = [p for p in parts if p]
208
+
209
+ # Navigate up directories
210
+ current_dir = base_dir
211
+ for _ in range(depth - 1):
212
+ current_dir = os.path.dirname(current_dir)
213
+
214
+ if module_parts:
215
+ module_path = os.path.join(current_dir, *module_parts)
216
+ else:
217
+ module_path = current_dir
218
+
219
+ # Try to find the module file
220
+ if os.path.isdir(module_path):
221
+ init_path = os.path.join(module_path, '__init__.py')
222
+ if os.path.exists(init_path):
223
+ return init_path
224
+ elif os.path.exists(module_path + '.py'):
225
+ return module_path + '.py'
226
+ else:
227
+ # Absolute import
228
+ parts = module_name.split('.')
229
+
230
+ # Search in project root
231
+ for root, dirs, files in os.walk(project_root):
232
+ # Skip hidden directories and common ignore patterns
233
+ dirs[:] = filter_walk_dirs(dirs)
234
+
235
+ if parts[0] in dirs or f"{parts[0]}.py" in files:
236
+ module_path = os.path.join(root, *parts)
237
+
238
+ if os.path.isdir(module_path):
239
+ init_path = os.path.join(module_path, '__init__.py')
240
+ if os.path.exists(init_path):
241
+ return init_path
242
+ elif os.path.exists(module_path + '.py'):
243
+ return module_path + '.py'
244
+ break
245
+
246
+ return None
247
+
248
+ def _is_source_file(self, file_path: str) -> bool:
249
+ """Check if a file is a Python source file."""
250
+ return file_path.endswith('.py')
251
+
252
+
253
+ class PythonLanguageSupport(BaseLanguageSupport):
254
+ """Python语言支持类。"""
255
+
256
+ @property
257
+ def language_name(self) -> str:
258
+ return 'python'
259
+
260
+ @property
261
+ def file_extensions(self) -> Set[str]:
262
+ return {'.py', '.pyw', '.pyi'}
263
+
264
+ def create_symbol_extractor(self) -> Optional[SymbolExtractor]:
265
+ return PythonSymbolExtractor()
266
+
267
+ def create_dependency_analyzer(self) -> Optional[DependencyAnalyzer]:
268
+ return PythonDependencyAnalyzer()
269
+
@@ -0,0 +1,281 @@
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
+ (enum_item
35
+ name: (type_identifier) @enum.name)
36
+
37
+ (union_item
38
+ name: (type_identifier) @union.name)
39
+
40
+ (macro_definition
41
+ name: (identifier) @macro.name)
42
+
43
+ (const_item
44
+ name: (identifier) @const.name)
45
+
46
+ (static_item
47
+ name: (identifier) @static.name)
48
+
49
+ (type_item
50
+ name: (type_identifier) @type.name)
51
+
52
+ (extern_block) @extern
53
+
54
+ (attribute_item) @attribute
55
+ """
56
+
57
+ # --- Rust Language Setup ---
58
+
59
+ try:
60
+ import tree_sitter_rust
61
+ RUST_LANGUAGE: Optional[Language] = tree_sitter_rust.language()
62
+ except (ImportError, Exception):
63
+ RUST_LANGUAGE = None
64
+
65
+
66
+ # --- Rust Symbol Extractor ---
67
+
68
+ class RustSymbolExtractor(TreeSitterExtractor):
69
+ """Extracts symbols from Rust code using tree-sitter."""
70
+
71
+ def __init__(self):
72
+ if not RUST_LANGUAGE:
73
+ raise RuntimeError("Rust tree-sitter grammar not available.")
74
+ super().__init__(RUST_LANGUAGE, RUST_SYMBOL_QUERY)
75
+
76
+ def _create_symbol_from_capture(self, node: Node, name: str, file_path: str) -> Optional[Symbol]:
77
+ """Maps a tree-sitter capture to a Symbol object."""
78
+ kind_map = {
79
+ "function.name": "function",
80
+ "struct.name": "struct",
81
+ "trait.name": "trait",
82
+ "impl.name": "impl",
83
+ "module.name": "module",
84
+ "enum.name": "enum",
85
+ "union.name": "union",
86
+ "macro.name": "macro",
87
+ "const.name": "const",
88
+ "static.name": "static",
89
+ "type.name": "type",
90
+ "extern": "extern",
91
+ "attribute": "attribute",
92
+ }
93
+
94
+ symbol_kind = kind_map.get(name)
95
+ if not symbol_kind:
96
+ return None
97
+
98
+ # 对于 attribute,提取属性内容作为名称
99
+ if symbol_kind == "attribute":
100
+ # 提取属性文本
101
+ attr_text = node.text.decode('utf8').strip()
102
+ # 移除开头的 # 或 #!
103
+ if attr_text.startswith('#!'):
104
+ attr_text = attr_text[2:].strip()
105
+ elif attr_text.startswith('#'):
106
+ attr_text = attr_text[1:].strip()
107
+ # 移除外层的 []
108
+ if attr_text.startswith('[') and attr_text.endswith(']'):
109
+ attr_text = attr_text[1:-1].strip()
110
+
111
+ # 提取属性名称(可能是 test, derive(Debug), cfg(test) 等)
112
+ # 对于简单属性如 #[test],直接使用 test
113
+ # 对于复杂属性如 #[derive(Debug)],使用 derive
114
+ # 对于路径属性如 #[cfg(test)],使用 cfg
115
+ attr_name = attr_text.split('(')[0].split('[')[0].split('=')[0].split(',')[0].strip()
116
+
117
+ # 如果属性名称为空或只包含空白,使用整个属性文本(去掉括号)
118
+ if not attr_name or attr_name == '':
119
+ symbol_name = attr_text if attr_text else "attribute"
120
+ else:
121
+ # 使用属性名称,但保留完整文本用于显示
122
+ symbol_name = attr_name
123
+ elif symbol_kind == "extern":
124
+ # 对于 extern 块,提取 extern 关键字后的内容作为名称
125
+ extern_text = node.text.decode('utf8').strip()
126
+ # 提取 extern "C" 或 extern "Rust" 等
127
+ if '"' in extern_text:
128
+ # 提取引号中的内容
129
+ start = extern_text.find('"')
130
+ end = extern_text.find('"', start + 1)
131
+ if end > start:
132
+ symbol_name = f"extern_{extern_text[start+1:end]}"
133
+ else:
134
+ symbol_name = "extern"
135
+ else:
136
+ symbol_name = "extern"
137
+ else:
138
+ symbol_name = node.text.decode('utf8')
139
+
140
+ return Symbol(
141
+ name=symbol_name,
142
+ kind=symbol_kind,
143
+ file_path=file_path,
144
+ line_start=node.start_point[0] + 1,
145
+ line_end=node.end_point[0] + 1,
146
+ )
147
+
148
+
149
+ # --- Rust Dependency Analyzer ---
150
+
151
+ class RustDependencyAnalyzer(DependencyAnalyzer):
152
+ """Analyzes Rust use and mod dependencies."""
153
+
154
+ def analyze_imports(self, file_path: str, content: str) -> List[Dependency]:
155
+ """Analyzes Rust use and mod statements."""
156
+ dependencies: List[Dependency] = []
157
+
158
+ # Match use statements: use crate::module or use std::collections
159
+ use_pattern = re.compile(r'use\s+([^;]+);')
160
+
161
+ # Match mod declarations: mod module_name;
162
+ mod_pattern = re.compile(r'mod\s+(\w+)\s*;')
163
+
164
+ for line_num, line in enumerate(content.split('\n'), start=1):
165
+ # Check for use statements
166
+ use_match = use_pattern.search(line)
167
+ if use_match:
168
+ use_path = use_match.group(1).strip()
169
+ # Extract the crate/module name
170
+ parts = use_path.split('::')
171
+ if parts:
172
+ crate_name = parts[0]
173
+ symbol = '::'.join(parts[1:]) if len(parts) > 1 else None
174
+ dependencies.append(Dependency(
175
+ from_module=crate_name,
176
+ imported_symbol=symbol,
177
+ file_path=file_path,
178
+ line=line_num,
179
+ ))
180
+
181
+ # Check for mod declarations
182
+ mod_match = mod_pattern.search(line)
183
+ if mod_match:
184
+ mod_name = mod_match.group(1)
185
+ dependencies.append(Dependency(
186
+ from_module=mod_name,
187
+ imported_symbol=None,
188
+ file_path=file_path,
189
+ line=line_num,
190
+ ))
191
+
192
+ return dependencies
193
+
194
+ def build_dependency_graph(self, project_root: str) -> DependencyGraph:
195
+ """Builds a dependency graph for a Rust project."""
196
+ graph = DependencyGraph()
197
+
198
+ for root, dirs, files in os.walk(project_root):
199
+ dirs[:] = filter_walk_dirs(dirs)
200
+
201
+ for file in files:
202
+ if not file.endswith('.rs'):
203
+ continue
204
+
205
+ file_path = os.path.join(root, file)
206
+ try:
207
+ with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
208
+ content = f.read()
209
+
210
+ dependencies = self.analyze_imports(file_path, content)
211
+ for dep in dependencies:
212
+ # For Rust, resolve mod declarations to file paths
213
+ if not dep.from_module.startswith(('std', 'core', 'alloc', 'proc_macro')):
214
+ # Try to resolve local modules
215
+ dep_path = self._resolve_module_path(project_root, dep.from_module, file_path)
216
+ if dep_path and dep_path != file_path:
217
+ graph.add_dependency(file_path, dep_path)
218
+ except Exception:
219
+ continue
220
+
221
+ return graph
222
+
223
+ def _resolve_module_path(self, project_root: str, module_name: str, from_file: str) -> Optional[str]:
224
+ """Resolve a Rust module name to a file path."""
225
+ # Rust modules can be:
226
+ # 1. mod.rs in a directory
227
+ # 2. module_name.rs in the same directory
228
+ # 3. module_name/mod.rs
229
+
230
+ base_dir = os.path.dirname(from_file)
231
+
232
+ # Try module_name.rs in same directory
233
+ module_file = os.path.join(base_dir, f"{module_name}.rs")
234
+ if os.path.exists(module_file):
235
+ return module_file
236
+
237
+ # Try module_name/mod.rs
238
+ module_dir = os.path.join(base_dir, module_name)
239
+ mod_rs = os.path.join(module_dir, "mod.rs")
240
+ if os.path.exists(mod_rs):
241
+ return mod_rs
242
+
243
+ # Try in parent directories (for nested modules)
244
+ current_dir = base_dir
245
+ while current_dir != project_root and current_dir != os.path.dirname(current_dir):
246
+ module_file = os.path.join(current_dir, f"{module_name}.rs")
247
+ if os.path.exists(module_file):
248
+ return module_file
249
+ module_dir = os.path.join(current_dir, module_name)
250
+ mod_rs = os.path.join(module_dir, "mod.rs")
251
+ if os.path.exists(mod_rs):
252
+ return mod_rs
253
+ current_dir = os.path.dirname(current_dir)
254
+
255
+ return None
256
+
257
+ def _is_source_file(self, file_path: str) -> bool:
258
+ """Check if a file is a Rust source file."""
259
+ return file_path.endswith('.rs')
260
+
261
+
262
+ class RustLanguageSupport(BaseLanguageSupport):
263
+ """Rust语言支持类。"""
264
+
265
+ @property
266
+ def language_name(self) -> str:
267
+ return 'rust'
268
+
269
+ @property
270
+ def file_extensions(self) -> Set[str]:
271
+ return {'.rs'}
272
+
273
+ def create_symbol_extractor(self) -> Optional[SymbolExtractor]:
274
+ try:
275
+ return RustSymbolExtractor()
276
+ except RuntimeError:
277
+ return None
278
+
279
+ def create_dependency_analyzer(self) -> Optional[DependencyAnalyzer]:
280
+ return RustDependencyAnalyzer()
281
+