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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +458 -152
- jarvis/jarvis_agent/agent_manager.py +17 -13
- jarvis/jarvis_agent/builtin_input_handler.py +2 -6
- jarvis/jarvis_agent/config_editor.py +2 -7
- jarvis/jarvis_agent/event_bus.py +82 -12
- jarvis/jarvis_agent/file_context_handler.py +329 -0
- jarvis/jarvis_agent/file_methodology_manager.py +3 -4
- jarvis/jarvis_agent/jarvis.py +628 -55
- jarvis/jarvis_agent/language_extractors/__init__.py +57 -0
- jarvis/jarvis_agent/language_extractors/c_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/cpp_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/go_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/java_extractor.py +84 -0
- jarvis/jarvis_agent/language_extractors/javascript_extractor.py +79 -0
- jarvis/jarvis_agent/language_extractors/python_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/rust_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/typescript_extractor.py +84 -0
- jarvis/jarvis_agent/language_support_info.py +486 -0
- jarvis/jarvis_agent/main.py +34 -10
- jarvis/jarvis_agent/memory_manager.py +7 -16
- jarvis/jarvis_agent/methodology_share_manager.py +10 -16
- jarvis/jarvis_agent/prompt_manager.py +1 -1
- jarvis/jarvis_agent/prompts.py +193 -171
- jarvis/jarvis_agent/protocols.py +8 -12
- jarvis/jarvis_agent/run_loop.py +105 -9
- jarvis/jarvis_agent/session_manager.py +2 -3
- jarvis/jarvis_agent/share_manager.py +20 -22
- jarvis/jarvis_agent/shell_input_handler.py +1 -2
- jarvis/jarvis_agent/stdio_redirect.py +295 -0
- jarvis/jarvis_agent/task_analyzer.py +31 -6
- jarvis/jarvis_agent/task_manager.py +11 -27
- jarvis/jarvis_agent/tool_executor.py +2 -3
- jarvis/jarvis_agent/tool_share_manager.py +12 -24
- jarvis/jarvis_agent/utils.py +5 -1
- jarvis/jarvis_agent/web_bridge.py +189 -0
- jarvis/jarvis_agent/web_output_sink.py +53 -0
- jarvis/jarvis_agent/web_server.py +786 -0
- jarvis/jarvis_c2rust/__init__.py +26 -0
- jarvis/jarvis_c2rust/cli.py +575 -0
- jarvis/jarvis_c2rust/collector.py +250 -0
- jarvis/jarvis_c2rust/constants.py +26 -0
- jarvis/jarvis_c2rust/library_replacer.py +1254 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +1272 -0
- jarvis/jarvis_c2rust/loaders.py +207 -0
- jarvis/jarvis_c2rust/models.py +28 -0
- jarvis/jarvis_c2rust/optimizer.py +2157 -0
- jarvis/jarvis_c2rust/scanner.py +1681 -0
- jarvis/jarvis_c2rust/transpiler.py +2983 -0
- jarvis/jarvis_c2rust/utils.py +385 -0
- jarvis/jarvis_code_agent/build_validation_config.py +132 -0
- jarvis/jarvis_code_agent/code_agent.py +1371 -220
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +65 -0
- jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +106 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +74 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +72 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +70 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +53 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +47 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +61 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +110 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +154 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +110 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +153 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +648 -0
- jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
- jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
- jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
- jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
- jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +110 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +49 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +299 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +215 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +212 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +254 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +269 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +281 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +280 -0
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +605 -0
- jarvis/jarvis_code_agent/code_analyzer/structured_code.py +556 -0
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +252 -0
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +58 -0
- jarvis/jarvis_code_agent/lint.py +501 -8
- jarvis/jarvis_code_agent/utils.py +141 -0
- jarvis/jarvis_code_analysis/code_review.py +493 -584
- jarvis/jarvis_data/config_schema.json +128 -12
- jarvis/jarvis_git_squash/main.py +4 -5
- jarvis/jarvis_git_utils/git_commiter.py +82 -75
- jarvis/jarvis_mcp/sse_mcp_client.py +22 -29
- jarvis/jarvis_mcp/stdio_mcp_client.py +12 -13
- jarvis/jarvis_mcp/streamable_mcp_client.py +15 -14
- jarvis/jarvis_memory_organizer/memory_organizer.py +55 -74
- jarvis/jarvis_methodology/main.py +32 -48
- jarvis/jarvis_multi_agent/__init__.py +287 -55
- jarvis/jarvis_multi_agent/main.py +36 -4
- jarvis/jarvis_platform/base.py +524 -202
- jarvis/jarvis_platform/human.py +7 -8
- jarvis/jarvis_platform/kimi.py +30 -36
- jarvis/jarvis_platform/openai.py +88 -25
- jarvis/jarvis_platform/registry.py +26 -10
- jarvis/jarvis_platform/tongyi.py +24 -25
- jarvis/jarvis_platform/yuanbao.py +32 -43
- jarvis/jarvis_platform_manager/main.py +66 -77
- jarvis/jarvis_platform_manager/service.py +8 -13
- jarvis/jarvis_rag/cli.py +53 -55
- jarvis/jarvis_rag/embedding_manager.py +13 -18
- jarvis/jarvis_rag/llm_interface.py +8 -9
- jarvis/jarvis_rag/query_rewriter.py +10 -21
- jarvis/jarvis_rag/rag_pipeline.py +24 -27
- jarvis/jarvis_rag/reranker.py +4 -5
- jarvis/jarvis_rag/retriever.py +28 -30
- jarvis/jarvis_sec/__init__.py +305 -0
- jarvis/jarvis_sec/agents.py +143 -0
- jarvis/jarvis_sec/analysis.py +276 -0
- jarvis/jarvis_sec/checkers/__init__.py +32 -0
- jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
- jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
- jarvis/jarvis_sec/cli.py +139 -0
- jarvis/jarvis_sec/clustering.py +1439 -0
- jarvis/jarvis_sec/file_manager.py +427 -0
- jarvis/jarvis_sec/parsers.py +73 -0
- jarvis/jarvis_sec/prompts.py +268 -0
- jarvis/jarvis_sec/report.py +336 -0
- jarvis/jarvis_sec/review.py +453 -0
- jarvis/jarvis_sec/status.py +264 -0
- jarvis/jarvis_sec/types.py +20 -0
- jarvis/jarvis_sec/utils.py +499 -0
- jarvis/jarvis_sec/verification.py +848 -0
- jarvis/jarvis_sec/workflow.py +226 -0
- jarvis/jarvis_smart_shell/main.py +38 -87
- jarvis/jarvis_stats/cli.py +2 -2
- jarvis/jarvis_stats/stats.py +8 -8
- jarvis/jarvis_stats/storage.py +15 -21
- jarvis/jarvis_stats/visualizer.py +1 -1
- jarvis/jarvis_tools/clear_memory.py +3 -20
- jarvis/jarvis_tools/cli/main.py +21 -23
- jarvis/jarvis_tools/edit_file.py +1019 -132
- jarvis/jarvis_tools/execute_script.py +83 -25
- jarvis/jarvis_tools/file_analyzer.py +6 -9
- jarvis/jarvis_tools/generate_new_tool.py +14 -21
- jarvis/jarvis_tools/lsp_client.py +1552 -0
- jarvis/jarvis_tools/methodology.py +2 -3
- jarvis/jarvis_tools/read_code.py +1736 -35
- jarvis/jarvis_tools/read_symbols.py +140 -0
- jarvis/jarvis_tools/read_webpage.py +12 -13
- jarvis/jarvis_tools/registry.py +427 -200
- jarvis/jarvis_tools/retrieve_memory.py +20 -19
- jarvis/jarvis_tools/rewrite_file.py +72 -158
- jarvis/jarvis_tools/save_memory.py +3 -15
- jarvis/jarvis_tools/search_web.py +18 -18
- jarvis/jarvis_tools/sub_agent.py +36 -43
- jarvis/jarvis_tools/sub_code_agent.py +25 -26
- jarvis/jarvis_tools/virtual_tty.py +55 -33
- jarvis/jarvis_utils/clipboard.py +7 -10
- jarvis/jarvis_utils/config.py +232 -45
- jarvis/jarvis_utils/embedding.py +8 -5
- jarvis/jarvis_utils/fzf.py +8 -8
- jarvis/jarvis_utils/git_utils.py +225 -36
- jarvis/jarvis_utils/globals.py +3 -3
- jarvis/jarvis_utils/http.py +1 -1
- jarvis/jarvis_utils/input.py +99 -48
- jarvis/jarvis_utils/jsonnet_compat.py +465 -0
- jarvis/jarvis_utils/methodology.py +52 -48
- jarvis/jarvis_utils/utils.py +819 -491
- jarvis_ai_assistant-0.7.6.dist-info/METADATA +600 -0
- jarvis_ai_assistant-0.7.6.dist-info/RECORD +218 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/entry_points.txt +4 -0
- jarvis/jarvis_agent/config.py +0 -92
- jarvis/jarvis_agent/edit_file_handler.py +0 -296
- jarvis/jarvis_platform/ai8.py +0 -332
- jarvis/jarvis_tools/ask_user.py +0 -54
- jarvis_ai_assistant-0.3.30.dist-info/METADATA +0 -381
- jarvis_ai_assistant-0.3.30.dist-info/RECORD +0 -137
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,215 @@
|
|
|
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
|
+
(type_declaration
|
|
30
|
+
(type_spec
|
|
31
|
+
name: (type_identifier) @interface.name
|
|
32
|
+
type: (interface_type)))
|
|
33
|
+
|
|
34
|
+
(const_declaration) @const
|
|
35
|
+
|
|
36
|
+
(var_declaration) @var
|
|
37
|
+
|
|
38
|
+
(struct_type) @struct
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
# --- Go Language Setup ---
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
import tree_sitter_go
|
|
45
|
+
GO_LANGUAGE: Optional[Language] = tree_sitter_go.language()
|
|
46
|
+
except (ImportError, Exception):
|
|
47
|
+
GO_LANGUAGE = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# --- Go Symbol Extractor ---
|
|
51
|
+
|
|
52
|
+
class GoSymbolExtractor(TreeSitterExtractor):
|
|
53
|
+
"""Extracts symbols from Go code using tree-sitter."""
|
|
54
|
+
|
|
55
|
+
def __init__(self):
|
|
56
|
+
if not GO_LANGUAGE:
|
|
57
|
+
raise RuntimeError("Go tree-sitter grammar not available.")
|
|
58
|
+
super().__init__(GO_LANGUAGE, GO_SYMBOL_QUERY)
|
|
59
|
+
|
|
60
|
+
def _create_symbol_from_capture(self, node: Node, name: str, file_path: str) -> Optional[Symbol]:
|
|
61
|
+
"""Maps a tree-sitter capture to a Symbol object."""
|
|
62
|
+
kind_map = {
|
|
63
|
+
"function.name": "function",
|
|
64
|
+
"method.name": "method",
|
|
65
|
+
"type.name": "type",
|
|
66
|
+
"interface.name": "interface",
|
|
67
|
+
"const": "const",
|
|
68
|
+
"var": "var",
|
|
69
|
+
"struct": "struct",
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
symbol_kind = kind_map.get(name)
|
|
73
|
+
if not symbol_kind:
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
# For const/var/struct, extract the first identifier as name
|
|
77
|
+
if symbol_kind in ("const", "var", "struct"):
|
|
78
|
+
# Try to find the first identifier in the declaration
|
|
79
|
+
node_text = node.text.decode('utf8').strip()
|
|
80
|
+
# Extract first identifier after const/var/struct keyword
|
|
81
|
+
if symbol_kind == "const":
|
|
82
|
+
match = re.search(r'const\s+(\w+)', node_text)
|
|
83
|
+
elif symbol_kind == "var":
|
|
84
|
+
match = re.search(r'var\s+(\w+)', node_text)
|
|
85
|
+
else: # struct
|
|
86
|
+
# For struct, try to find struct name or use a generic name
|
|
87
|
+
match = re.search(r'struct\s+(\w+)', node_text)
|
|
88
|
+
|
|
89
|
+
if match:
|
|
90
|
+
symbol_name = match.group(1)
|
|
91
|
+
else:
|
|
92
|
+
# Fallback: use the kind as name
|
|
93
|
+
symbol_name = symbol_kind
|
|
94
|
+
else:
|
|
95
|
+
symbol_name = node.text.decode('utf8')
|
|
96
|
+
|
|
97
|
+
return Symbol(
|
|
98
|
+
name=symbol_name,
|
|
99
|
+
kind=symbol_kind,
|
|
100
|
+
file_path=file_path,
|
|
101
|
+
line_start=node.start_point[0] + 1,
|
|
102
|
+
line_end=node.end_point[0] + 1,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# --- Go Dependency Analyzer ---
|
|
107
|
+
|
|
108
|
+
class GoDependencyAnalyzer(DependencyAnalyzer):
|
|
109
|
+
"""Analyzes Go import dependencies."""
|
|
110
|
+
|
|
111
|
+
def analyze_imports(self, file_path: str, content: str) -> List[Dependency]:
|
|
112
|
+
"""Analyzes Go import statements."""
|
|
113
|
+
dependencies: List[Dependency] = []
|
|
114
|
+
|
|
115
|
+
# Match import statements
|
|
116
|
+
# Format: import "package" or import ( "package1" "package2" )
|
|
117
|
+
# Also: import alias "package"
|
|
118
|
+
re.compile(
|
|
119
|
+
r'import\s+(?:\(([^)]+)\)|(?:"([^"]+)"|`([^`]+)`)|(\w+)\s+(?:"([^"]+)"|`([^`]+)`)))',
|
|
120
|
+
re.MULTILINE
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Handle single import: import "package"
|
|
124
|
+
single_import = re.compile(r'import\s+(?:"([^"]+)"|`([^`]+)`|(\w+)\s+(?:"([^"]+)"|`([^`]+)`))')
|
|
125
|
+
|
|
126
|
+
# Handle block import: import ( ... )
|
|
127
|
+
block_import = re.compile(r'import\s*\(([^)]+)\)', re.DOTALL)
|
|
128
|
+
|
|
129
|
+
# Try block import first
|
|
130
|
+
block_match = block_import.search(content)
|
|
131
|
+
if block_match:
|
|
132
|
+
block_content = block_match.group(1)
|
|
133
|
+
for line in block_content.split('\n'):
|
|
134
|
+
line = line.strip()
|
|
135
|
+
if not line or line.startswith('//'):
|
|
136
|
+
continue
|
|
137
|
+
# Extract package path
|
|
138
|
+
pkg_match = re.search(r'(?:"([^"]+)"|`([^`]+)`|(\w+)\s+(?:"([^"]+)"|`([^`]+)`))', line)
|
|
139
|
+
if pkg_match:
|
|
140
|
+
pkg = pkg_match.group(1) or pkg_match.group(2) or pkg_match.group(4) or pkg_match.group(5)
|
|
141
|
+
alias = pkg_match.group(3)
|
|
142
|
+
line_num = content[:block_match.start()].count('\n') + line.split('\n')[0].count('\n') + 1
|
|
143
|
+
dependencies.append(Dependency(
|
|
144
|
+
from_module=pkg,
|
|
145
|
+
imported_symbol=alias,
|
|
146
|
+
file_path=file_path,
|
|
147
|
+
line=line_num,
|
|
148
|
+
))
|
|
149
|
+
else:
|
|
150
|
+
# Try single import
|
|
151
|
+
for match in single_import.finditer(content):
|
|
152
|
+
pkg = match.group(1) or match.group(2) or match.group(4) or match.group(5)
|
|
153
|
+
alias = match.group(3)
|
|
154
|
+
line_num = content[:match.start()].count('\n') + 1
|
|
155
|
+
dependencies.append(Dependency(
|
|
156
|
+
from_module=pkg,
|
|
157
|
+
imported_symbol=alias,
|
|
158
|
+
file_path=file_path,
|
|
159
|
+
line=line_num,
|
|
160
|
+
))
|
|
161
|
+
|
|
162
|
+
return dependencies
|
|
163
|
+
|
|
164
|
+
def build_dependency_graph(self, project_root: str) -> DependencyGraph:
|
|
165
|
+
"""Builds a dependency graph for a Go project."""
|
|
166
|
+
graph = DependencyGraph()
|
|
167
|
+
|
|
168
|
+
for root, dirs, files in os.walk(project_root):
|
|
169
|
+
dirs[:] = filter_walk_dirs(dirs)
|
|
170
|
+
|
|
171
|
+
for file in files:
|
|
172
|
+
if not file.endswith('.go'):
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
file_path = os.path.join(root, file)
|
|
176
|
+
try:
|
|
177
|
+
with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
|
|
178
|
+
content = f.read()
|
|
179
|
+
|
|
180
|
+
dependencies = self.analyze_imports(file_path, content)
|
|
181
|
+
for dep in dependencies:
|
|
182
|
+
# For Go, we can resolve to vendor or standard library
|
|
183
|
+
# For now, just track the module name
|
|
184
|
+
# In a real implementation, you'd resolve using go.mod
|
|
185
|
+
pass
|
|
186
|
+
except Exception:
|
|
187
|
+
continue
|
|
188
|
+
|
|
189
|
+
return graph
|
|
190
|
+
|
|
191
|
+
def _is_source_file(self, file_path: str) -> bool:
|
|
192
|
+
"""Check if a file is a Go source file."""
|
|
193
|
+
return file_path.endswith('.go')
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class GoLanguageSupport(BaseLanguageSupport):
|
|
197
|
+
"""Go语言支持类。"""
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def language_name(self) -> str:
|
|
201
|
+
return 'go'
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def file_extensions(self) -> Set[str]:
|
|
205
|
+
return {'.go'}
|
|
206
|
+
|
|
207
|
+
def create_symbol_extractor(self) -> Optional[SymbolExtractor]:
|
|
208
|
+
try:
|
|
209
|
+
return GoSymbolExtractor()
|
|
210
|
+
except RuntimeError:
|
|
211
|
+
return None
|
|
212
|
+
|
|
213
|
+
def create_dependency_analyzer(self) -> Optional[DependencyAnalyzer]:
|
|
214
|
+
return GoDependencyAnalyzer()
|
|
215
|
+
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""Java语言支持实现。"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
from typing import List, Optional, Set
|
|
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
|
+
from ..tree_sitter_extractor import TreeSitterExtractor
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
from tree_sitter import Language, Node
|
|
15
|
+
import tree_sitter_java
|
|
16
|
+
JAVA_LANGUAGE: Optional[Language] = tree_sitter_java.language()
|
|
17
|
+
except (ImportError, Exception):
|
|
18
|
+
JAVA_LANGUAGE = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# --- Java Symbol Extractor ---
|
|
22
|
+
|
|
23
|
+
JAVA_SYMBOL_QUERY = """
|
|
24
|
+
(method_declaration
|
|
25
|
+
name: (identifier) @method.name)
|
|
26
|
+
|
|
27
|
+
(class_declaration
|
|
28
|
+
name: (identifier) @class.name)
|
|
29
|
+
|
|
30
|
+
(interface_declaration
|
|
31
|
+
name: (identifier) @interface.name)
|
|
32
|
+
|
|
33
|
+
(enum_declaration
|
|
34
|
+
name: (identifier) @enum.name)
|
|
35
|
+
|
|
36
|
+
(annotation_type_declaration
|
|
37
|
+
name: (identifier) @annotation.name)
|
|
38
|
+
|
|
39
|
+
(field_declaration
|
|
40
|
+
(variable_declarator
|
|
41
|
+
name: (identifier) @field.name))
|
|
42
|
+
|
|
43
|
+
(constructor_declaration
|
|
44
|
+
name: (identifier) @constructor.name)
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class JavaSymbolExtractor(TreeSitterExtractor):
|
|
49
|
+
"""Extracts symbols from Java code using tree-sitter."""
|
|
50
|
+
|
|
51
|
+
def __init__(self):
|
|
52
|
+
if not JAVA_LANGUAGE:
|
|
53
|
+
raise RuntimeError("Java tree-sitter grammar not available.")
|
|
54
|
+
# 如果传入的是 PyCapsule,需要转换为 Language 对象
|
|
55
|
+
if not isinstance(JAVA_LANGUAGE, Language):
|
|
56
|
+
lang = Language(JAVA_LANGUAGE)
|
|
57
|
+
else:
|
|
58
|
+
lang = JAVA_LANGUAGE
|
|
59
|
+
super().__init__(lang, JAVA_SYMBOL_QUERY)
|
|
60
|
+
|
|
61
|
+
def _create_symbol_from_capture(self, node: Node, name: str, file_path: str) -> Optional[Symbol]:
|
|
62
|
+
"""Maps a tree-sitter capture to a Symbol object."""
|
|
63
|
+
kind_map = {
|
|
64
|
+
"method.name": "method",
|
|
65
|
+
"class.name": "class",
|
|
66
|
+
"interface.name": "interface",
|
|
67
|
+
"enum.name": "enum",
|
|
68
|
+
"annotation.name": "annotation",
|
|
69
|
+
"field.name": "field",
|
|
70
|
+
"constructor.name": "constructor",
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
symbol_kind = kind_map.get(name)
|
|
74
|
+
if not symbol_kind:
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
return Symbol(
|
|
78
|
+
name=node.text.decode('utf8'),
|
|
79
|
+
kind=symbol_kind,
|
|
80
|
+
file_path=file_path,
|
|
81
|
+
line_start=node.start_point[0] + 1,
|
|
82
|
+
line_end=node.end_point[0] + 1,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# --- Java Dependency Analyzer ---
|
|
87
|
+
|
|
88
|
+
class JavaDependencyAnalyzer(DependencyAnalyzer):
|
|
89
|
+
"""Analyzes Java import dependencies."""
|
|
90
|
+
|
|
91
|
+
def analyze_imports(self, file_path: str, content: str) -> List[Dependency]:
|
|
92
|
+
"""Analyzes Java import statements."""
|
|
93
|
+
dependencies: List[Dependency] = []
|
|
94
|
+
|
|
95
|
+
# Java import statements
|
|
96
|
+
# import package.Class;
|
|
97
|
+
# import package.*;
|
|
98
|
+
# import static package.Class.method;
|
|
99
|
+
import_pattern = re.compile(
|
|
100
|
+
r'import\s+(?:static\s+)?([\w.]+)(?:\.\*)?\s*;',
|
|
101
|
+
re.MULTILINE
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
for line_num, line in enumerate(content.split('\n'), start=1):
|
|
105
|
+
import_match = import_pattern.search(line)
|
|
106
|
+
if import_match:
|
|
107
|
+
module_path = import_match.group(1)
|
|
108
|
+
if module_path:
|
|
109
|
+
# Extract class name if it's a specific import
|
|
110
|
+
parts = module_path.split('.')
|
|
111
|
+
imported_symbol = None
|
|
112
|
+
if len(parts) > 1 and not line.strip().endswith('.*;'):
|
|
113
|
+
# Specific class import
|
|
114
|
+
imported_symbol = parts[-1]
|
|
115
|
+
module_path = '.'.join(parts[:-1])
|
|
116
|
+
|
|
117
|
+
dependencies.append(Dependency(
|
|
118
|
+
from_module=module_path,
|
|
119
|
+
imported_symbol=imported_symbol,
|
|
120
|
+
file_path=file_path,
|
|
121
|
+
line=line_num,
|
|
122
|
+
))
|
|
123
|
+
|
|
124
|
+
return dependencies
|
|
125
|
+
|
|
126
|
+
def build_dependency_graph(self, project_root: str) -> DependencyGraph:
|
|
127
|
+
"""Builds a dependency graph for a Java project."""
|
|
128
|
+
graph = DependencyGraph()
|
|
129
|
+
|
|
130
|
+
for root, dirs, files in os.walk(project_root):
|
|
131
|
+
dirs[:] = filter_walk_dirs(dirs)
|
|
132
|
+
|
|
133
|
+
for file in files:
|
|
134
|
+
if not file.endswith('.java'):
|
|
135
|
+
continue
|
|
136
|
+
|
|
137
|
+
file_path = os.path.join(root, file)
|
|
138
|
+
try:
|
|
139
|
+
with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
|
|
140
|
+
content = f.read()
|
|
141
|
+
|
|
142
|
+
dependencies = self.analyze_imports(file_path, content)
|
|
143
|
+
for dep in dependencies:
|
|
144
|
+
# Skip java.* and javax.* standard library packages
|
|
145
|
+
if not dep.from_module.startswith(('java.', 'javax.')):
|
|
146
|
+
dep_path = self._resolve_module_path(project_root, dep.from_module, dep.imported_symbol, file_path)
|
|
147
|
+
if dep_path and dep_path != file_path:
|
|
148
|
+
graph.add_dependency(file_path, dep_path)
|
|
149
|
+
except Exception:
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
return graph
|
|
153
|
+
|
|
154
|
+
def _resolve_module_path(self, project_root: str, package_name: str, class_name: Optional[str], from_file: str) -> Optional[str]:
|
|
155
|
+
"""Resolve a Java package name to a file path."""
|
|
156
|
+
if not package_name:
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
# Convert package name to directory path
|
|
160
|
+
package_path = package_name.replace('.', os.sep)
|
|
161
|
+
|
|
162
|
+
# Try to find the class file
|
|
163
|
+
if class_name:
|
|
164
|
+
# Specific class import
|
|
165
|
+
class_file = class_name + '.java'
|
|
166
|
+
resolved = os.path.normpath(os.path.join(project_root, 'src', 'main', 'java', package_path, class_file))
|
|
167
|
+
if os.path.exists(resolved) and os.path.isfile(resolved):
|
|
168
|
+
return resolved
|
|
169
|
+
|
|
170
|
+
# Try alternative source directories
|
|
171
|
+
for src_dir in ['src', 'source', 'java']:
|
|
172
|
+
resolved = os.path.normpath(os.path.join(project_root, src_dir, package_path, class_file))
|
|
173
|
+
if os.path.exists(resolved) and os.path.isfile(resolved):
|
|
174
|
+
return resolved
|
|
175
|
+
else:
|
|
176
|
+
# Wildcard import - try to find package-info.java or any class in the package
|
|
177
|
+
package_dir = os.path.normpath(os.path.join(project_root, 'src', 'main', 'java', package_path))
|
|
178
|
+
if os.path.exists(package_dir) and os.path.isdir(package_dir):
|
|
179
|
+
# Return the first .java file found (or package-info.java if exists)
|
|
180
|
+
for file in os.listdir(package_dir):
|
|
181
|
+
if file.endswith('.java'):
|
|
182
|
+
resolved = os.path.join(package_dir, file)
|
|
183
|
+
if os.path.isfile(resolved):
|
|
184
|
+
return resolved
|
|
185
|
+
|
|
186
|
+
return None
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
# --- Java Language Support ---
|
|
190
|
+
|
|
191
|
+
class JavaLanguageSupport(BaseLanguageSupport):
|
|
192
|
+
"""Java语言支持。"""
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def language_name(self) -> str:
|
|
196
|
+
return "java"
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def file_extensions(self) -> Set[str]:
|
|
200
|
+
return {'.java'}
|
|
201
|
+
|
|
202
|
+
def create_symbol_extractor(self) -> Optional[SymbolExtractor]:
|
|
203
|
+
if not JAVA_LANGUAGE:
|
|
204
|
+
return None
|
|
205
|
+
try:
|
|
206
|
+
return JavaSymbolExtractor()
|
|
207
|
+
except Exception:
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
def create_dependency_analyzer(self) -> Optional[DependencyAnalyzer]:
|
|
211
|
+
return JavaDependencyAnalyzer()
|
|
212
|
+
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"""JavaScript语言支持实现。"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
from typing import List, Optional, Set
|
|
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
|
+
from ..tree_sitter_extractor import TreeSitterExtractor
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
from tree_sitter import Language, Node
|
|
15
|
+
import tree_sitter_javascript
|
|
16
|
+
JS_LANGUAGE: Optional[Language] = tree_sitter_javascript.language()
|
|
17
|
+
except (ImportError, Exception):
|
|
18
|
+
JS_LANGUAGE = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# --- JavaScript Symbol Extractor ---
|
|
22
|
+
|
|
23
|
+
JS_SYMBOL_QUERY = """
|
|
24
|
+
(function_declaration
|
|
25
|
+
name: (identifier) @function.name)
|
|
26
|
+
|
|
27
|
+
(function_expression
|
|
28
|
+
name: (identifier) @function.name)
|
|
29
|
+
|
|
30
|
+
(generator_function_declaration
|
|
31
|
+
name: (identifier) @generator.name)
|
|
32
|
+
|
|
33
|
+
(generator_function
|
|
34
|
+
name: (identifier) @generator.name)
|
|
35
|
+
|
|
36
|
+
(arrow_function) @arrow.function
|
|
37
|
+
|
|
38
|
+
(method_definition
|
|
39
|
+
name: (property_identifier) @method.name)
|
|
40
|
+
|
|
41
|
+
(class_declaration
|
|
42
|
+
name: (identifier) @class.name)
|
|
43
|
+
|
|
44
|
+
(class_expression
|
|
45
|
+
name: (identifier) @class.name)
|
|
46
|
+
|
|
47
|
+
(variable_declaration
|
|
48
|
+
(variable_declarator
|
|
49
|
+
name: (identifier) @variable.name))
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class JavaScriptSymbolExtractor(TreeSitterExtractor):
|
|
54
|
+
"""Extracts symbols from JavaScript code using tree-sitter."""
|
|
55
|
+
|
|
56
|
+
def __init__(self):
|
|
57
|
+
if not JS_LANGUAGE:
|
|
58
|
+
raise RuntimeError("JavaScript tree-sitter grammar not available.")
|
|
59
|
+
# 如果传入的是 PyCapsule,需要转换为 Language 对象
|
|
60
|
+
if not isinstance(JS_LANGUAGE, Language):
|
|
61
|
+
lang = Language(JS_LANGUAGE)
|
|
62
|
+
else:
|
|
63
|
+
lang = JS_LANGUAGE
|
|
64
|
+
super().__init__(lang, JS_SYMBOL_QUERY)
|
|
65
|
+
|
|
66
|
+
def _create_symbol_from_capture(self, node: Node, name: str, file_path: str) -> Optional[Symbol]:
|
|
67
|
+
"""Maps a tree-sitter capture to a Symbol object."""
|
|
68
|
+
kind_map = {
|
|
69
|
+
"function.name": "function",
|
|
70
|
+
"arrow.function": "function",
|
|
71
|
+
"generator.name": "function",
|
|
72
|
+
"method.name": "method",
|
|
73
|
+
"class.name": "class",
|
|
74
|
+
"variable.name": "variable",
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
symbol_kind = kind_map.get(name)
|
|
78
|
+
if not symbol_kind:
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
# For arrow functions without names, use a generated name
|
|
82
|
+
if name == "arrow.function":
|
|
83
|
+
symbol_name = "<anonymous_arrow_function>"
|
|
84
|
+
elif name == "generator.name":
|
|
85
|
+
# Generator functions are also functions
|
|
86
|
+
symbol_name = node.text.decode('utf8')
|
|
87
|
+
else:
|
|
88
|
+
symbol_name = node.text.decode('utf8')
|
|
89
|
+
|
|
90
|
+
if not symbol_name:
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
return Symbol(
|
|
94
|
+
name=symbol_name,
|
|
95
|
+
kind=symbol_kind,
|
|
96
|
+
file_path=file_path,
|
|
97
|
+
line_start=node.start_point[0] + 1,
|
|
98
|
+
line_end=node.end_point[0] + 1,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# --- JavaScript Dependency Analyzer ---
|
|
103
|
+
|
|
104
|
+
class JavaScriptDependencyAnalyzer(DependencyAnalyzer):
|
|
105
|
+
"""Analyzes JavaScript import/require dependencies."""
|
|
106
|
+
|
|
107
|
+
def analyze_imports(self, file_path: str, content: str) -> List[Dependency]:
|
|
108
|
+
"""Analyzes JavaScript import and require statements."""
|
|
109
|
+
dependencies: List[Dependency] = []
|
|
110
|
+
|
|
111
|
+
# ES6 import statements
|
|
112
|
+
# import module from 'path'
|
|
113
|
+
# import { symbol } from 'path'
|
|
114
|
+
# import * as alias from 'path'
|
|
115
|
+
import_pattern = re.compile(
|
|
116
|
+
r'import\s+(?:(?:\*\s+as\s+(\w+)|(\{[^}]*\})|(\w+))\s+from\s+)?["\']([^"\']+)["\']',
|
|
117
|
+
re.MULTILINE
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# CommonJS require statements
|
|
121
|
+
# const module = require('path')
|
|
122
|
+
# require('path')
|
|
123
|
+
require_pattern = re.compile(
|
|
124
|
+
r'(?:const|let|var)\s+\w+\s*=\s*require\(["\']([^"\']+)["\']\)|require\(["\']([^"\']+)["\']\)',
|
|
125
|
+
re.MULTILINE
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
for line_num, line in enumerate(content.split('\n'), start=1):
|
|
129
|
+
# Check for ES6 imports
|
|
130
|
+
import_match = import_pattern.search(line)
|
|
131
|
+
if import_match:
|
|
132
|
+
module_path = import_match.group(4) or import_match.group(5)
|
|
133
|
+
if module_path:
|
|
134
|
+
# Extract imported symbols if any
|
|
135
|
+
symbols_group = import_match.group(2) or import_match.group(1)
|
|
136
|
+
imported_symbol = None
|
|
137
|
+
if symbols_group and symbols_group.startswith('{'):
|
|
138
|
+
# Extract first symbol from { symbol1, symbol2 }
|
|
139
|
+
symbol_match = re.search(r'\{([^}]+)\}', symbols_group)
|
|
140
|
+
if symbol_match:
|
|
141
|
+
first_symbol = symbol_match.group(1).split(',')[0].strip()
|
|
142
|
+
imported_symbol = first_symbol.split(' as ')[0].strip()
|
|
143
|
+
|
|
144
|
+
dependencies.append(Dependency(
|
|
145
|
+
from_module=module_path,
|
|
146
|
+
imported_symbol=imported_symbol,
|
|
147
|
+
file_path=file_path,
|
|
148
|
+
line=line_num,
|
|
149
|
+
))
|
|
150
|
+
|
|
151
|
+
# Check for require statements
|
|
152
|
+
require_match = require_pattern.search(line)
|
|
153
|
+
if require_match:
|
|
154
|
+
module_path = require_match.group(1) or require_match.group(2)
|
|
155
|
+
if module_path:
|
|
156
|
+
dependencies.append(Dependency(
|
|
157
|
+
from_module=module_path,
|
|
158
|
+
imported_symbol=None,
|
|
159
|
+
file_path=file_path,
|
|
160
|
+
line=line_num,
|
|
161
|
+
))
|
|
162
|
+
|
|
163
|
+
return dependencies
|
|
164
|
+
|
|
165
|
+
def build_dependency_graph(self, project_root: str) -> DependencyGraph:
|
|
166
|
+
"""Builds a dependency graph for a JavaScript project."""
|
|
167
|
+
graph = DependencyGraph()
|
|
168
|
+
|
|
169
|
+
for root, dirs, files in os.walk(project_root):
|
|
170
|
+
dirs[:] = filter_walk_dirs(dirs)
|
|
171
|
+
|
|
172
|
+
for file in files:
|
|
173
|
+
if not file.endswith(('.js', '.jsx', '.mjs', '.cjs')):
|
|
174
|
+
continue
|
|
175
|
+
|
|
176
|
+
file_path = os.path.join(root, file)
|
|
177
|
+
try:
|
|
178
|
+
with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
|
|
179
|
+
content = f.read()
|
|
180
|
+
|
|
181
|
+
dependencies = self.analyze_imports(file_path, content)
|
|
182
|
+
for dep in dependencies:
|
|
183
|
+
# Skip node_modules and external packages
|
|
184
|
+
if dep.from_module.startswith(('.', '/')) or not dep.from_module.startswith('http'):
|
|
185
|
+
dep_path = self._resolve_module_path(project_root, dep.from_module, file_path)
|
|
186
|
+
if dep_path and dep_path != file_path:
|
|
187
|
+
graph.add_dependency(file_path, dep_path)
|
|
188
|
+
except Exception:
|
|
189
|
+
continue
|
|
190
|
+
|
|
191
|
+
return graph
|
|
192
|
+
|
|
193
|
+
def _resolve_module_path(self, project_root: str, module_name: str, from_file: str) -> Optional[str]:
|
|
194
|
+
"""Resolve a JavaScript module name to a file path."""
|
|
195
|
+
if not module_name:
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
# Handle relative imports
|
|
199
|
+
if module_name.startswith('.'):
|
|
200
|
+
base_dir = os.path.dirname(from_file)
|
|
201
|
+
# Resolve relative path
|
|
202
|
+
if module_name.endswith('.js') or module_name.endswith('.jsx'):
|
|
203
|
+
# Direct file reference
|
|
204
|
+
resolved = os.path.normpath(os.path.join(base_dir, module_name))
|
|
205
|
+
else:
|
|
206
|
+
# Try with .js extension
|
|
207
|
+
resolved = os.path.normpath(os.path.join(base_dir, module_name + '.js'))
|
|
208
|
+
if not os.path.exists(resolved):
|
|
209
|
+
# Try with .jsx extension
|
|
210
|
+
resolved = os.path.normpath(os.path.join(base_dir, module_name + '.jsx'))
|
|
211
|
+
if not os.path.exists(resolved):
|
|
212
|
+
# Try index.js
|
|
213
|
+
resolved = os.path.normpath(os.path.join(base_dir, module_name, 'index.js'))
|
|
214
|
+
|
|
215
|
+
if os.path.exists(resolved) and os.path.isfile(resolved):
|
|
216
|
+
return resolved
|
|
217
|
+
|
|
218
|
+
# Handle absolute imports (from project root)
|
|
219
|
+
if module_name.startswith('/'):
|
|
220
|
+
resolved = os.path.normpath(os.path.join(project_root, module_name.lstrip('/')))
|
|
221
|
+
if not resolved.endswith(('.js', '.jsx')):
|
|
222
|
+
resolved += '.js'
|
|
223
|
+
if os.path.exists(resolved) and os.path.isfile(resolved):
|
|
224
|
+
return resolved
|
|
225
|
+
|
|
226
|
+
# For node_modules and external packages, we can't resolve without package.json
|
|
227
|
+
# Return None to skip them
|
|
228
|
+
return None
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# --- JavaScript Language Support ---
|
|
232
|
+
|
|
233
|
+
class JavaScriptLanguageSupport(BaseLanguageSupport):
|
|
234
|
+
"""JavaScript语言支持。"""
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
def language_name(self) -> str:
|
|
238
|
+
return "javascript"
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def file_extensions(self) -> Set[str]:
|
|
242
|
+
return {'.js', '.jsx', '.mjs', '.cjs'}
|
|
243
|
+
|
|
244
|
+
def create_symbol_extractor(self) -> Optional[SymbolExtractor]:
|
|
245
|
+
if not JS_LANGUAGE:
|
|
246
|
+
return None
|
|
247
|
+
try:
|
|
248
|
+
return JavaScriptSymbolExtractor()
|
|
249
|
+
except Exception:
|
|
250
|
+
return None
|
|
251
|
+
|
|
252
|
+
def create_dependency_analyzer(self) -> Optional[DependencyAnalyzer]:
|
|
253
|
+
return JavaScriptDependencyAnalyzer()
|
|
254
|
+
|